有人可以简单地向我解释什么是有向无环图吗?我看过维基百科,但它并没有真正使我看到它在编程中的用途。
有人可以简单地向我解释什么是有向无环图吗?我看过维基百科,但它并没有真正使我看到它在编程中的用途。
Answers:
graph =由节点组成的结构,这些节点之间相互连接
有向=节点(边缘)之间的连接具有方向:A-> B与B-> A不同
acyclic =“ non-circular” =通过沿着边缘在一个节点之间移动,您将永远不会第二次遇到相同的节点。
有向无环图的一个很好的例子是树。但是请注意,并非所有有向无环图都是树。
我看到许多答案表明DAG(有向无环图)的含义,但在其应用中却没有答案。这是一个非常简单的-
先决条件图 -在工程课程中,每个学生都面临选择符合先决条件等要求的学科的任务。现在很明显,如果没有关于算法[A]的前提课程,就无法上人工智能[B]课程。因此,B依赖于A或更好的说法是A具有指向B的边。因此,要到达节点B,您必须访问节点A。很快就会清楚,将所有主题及其先决条件添加到图中之后,结果将是有向无环图。
如果有一个周期,那么您将永远不会完成课程:p
大学中允许学生注册课程的软件系统可以将学科建模为节点,以确保学生在注册当前课程之前已经修读了必修课。
我的教授给出了一个比喻,它最能帮助我理解DAG,而不是使用一些复杂的概念!
另一个实时示例-> 关于如何在版本系统中使用DAG的实时示例
有向无环图在编程中的示例用法包括或多或少代表连接性和因果关系的任何事物。
例如,假设您有一个可在运行时配置的计算管道。例如,假设计算A,B,C,D,E,F和G相互依赖:A依赖于C,C依赖于E和F,B依赖于D和E,D依赖于F。这可以表示为DAG。将DAG存储在内存中后,您可以将算法编写为:
等等。
在应用程序编程领域之外,任何不错的自动化构建工具(make,ant,scons等)都将使用DAG来确保程序组件的正确构建顺序。
有几个答案给出了使用图形的示例(例如,网络建模),并且您问“与编程有什么关系?”。
该子问题的答案是,它与编程没有任何关系。它与解决问题有关。
就像链表是用于某些类型问题的数据结构一样,图对于表示某些关系也很有用。链表,树,图和其他抽象结构仅与编程有关,因为您可以在代码中实现它们。它们以更高的抽象级别存在。这与编程无关,而在于在解决问题中应用数据结构。
有向无环图(DAG)具有以下特性,可将它们与其他图区分开来:
好吧,我现在可以想到一种用法-DAG(称为Wait-For-Graphs-更多技术细节)在检测死锁方面非常方便,因为它们说明了一组进程和资源之间的依赖关系(两者都是DAG中的节点) 。当检测到周期时,将发生死锁。
我假设您已经了解基本图形术语;否则,您应该从关于图论的文章开始。
定向是指边(连接)具有方向的事实。在图中,这些方向由箭头显示。相反的是无向图,其边缘未指定方向。
非循环意味着,如果您从任意节点X开始并遍历所有可能的边缘,那么您必须返回到已使用的边缘才能返回X。
几种应用:
从源代码甚至三个地址(TAC)代码的角度来看,您都可以在此页面上非常轻松地可视化问题...
http://cgm.cs.mcgill.ca/~hagha/topic30/topic30.html#Exptree
如果转到表达式树部分,然后向下逐页,它将显示树的“拓扑排序”以及如何评估表达式的算法。
因此,在那种情况下,您可以使用DAG来评估表达式,这很方便,因为通常可以解释评估,并且使用这种DAG评估器原则上可以使简单的解释器更快地运行,因为它不会压入并弹出堆栈,并且因为它消除了常见的子表达式。
在非古代埃及(即英语)中计算DAG的基本算法是:
1)像这样使您的DAG对象
您需要一个活动列表,此列表包含所有当前的活动DAG节点和DAG子表达式。DAG子表达式是DAG节点,或者您也可以将其称为内部节点。我所说的实时DAG节点的意思是,如果您分配给变量X,那么它将变为实时。然后使用X的公共子表达式使用该实例。如果再次将X分配给X,则将创建一个NEW DAG NODE并将其添加到活动列表中,并删除旧的X,因此使用X的下一个子表达式将引用新实例,因此不会与该子表达式冲突仅使用相同的变量名。
一旦分配了变量X,由于所有新分配使使用旧值的子表达式的含义无效,因此恰好在赋值点处处于活动状态的所有DAG子表达式节点都变为不活动状态。
class Dag {
TList LiveList;
DagNode Root;
}
// In your DagNode you need a way to refer to the original things that
// the DAG is computed from. In this case I just assume an integer index
// into the list of variables and also an integer index for the opertor for
// Nodes that refer to operators. Obviously you can create sub-classes for
// different kinds of Dag Nodes.
class DagNode {
int Variable;
int Operator;// You can also use a class
DagNode Left;
DagNode Right;
DagNodeList Parents;
}
因此,您要做的就是使用自己的代码遍历树,例如源代码中的表达式树。例如,将现有节点称为XNodes。
因此,对于每个XNode,您需要确定如何将其添加到DAG中,并且可能已经将其添加到DAG中。
这是非常简单的伪代码。不适用于编译。
DagNode XNode::GetDagNode(Dag dag) {
if (XNode.IsAssignment) {
// The assignment is a special case. A common sub expression is not
// formed by the assignment since it creates a new value.
// Evaluate the right hand side like normal
XNode.RightXNode.GetDagNode();
// And now take the variable being assigned to out of the current live list
dag.RemoveDagNodeForVariable(XNode.VariableBeingAssigned);
// Also remove all DAG sub expressions using the variable - since the new value
// makes them redundant
dag.RemoveDagExpressionsUsingVariable(XNode.VariableBeingAssigned);
// Then make a new variable in the live list in the dag, so that references to
// the variable later on will see the new dag node instead.
dag.AddDagNodeForVariable(XNode.VariableBeingAssigned);
}
else if (XNode.IsVariable) {
// A variable node has no child nodes, so you can just proces it directly
DagNode n = dag.GetDagNodeForVariable(XNode.Variable));
if (n) XNode.DagNode = n;
else {
XNode.DagNode = dag.CreateDagNodeForVariable(XNode.Variable);
}
return XNode.DagNode;
}
else if (XNode.IsOperator) {
DagNode leftDagNode = XNode.LeftXNode.GetDagNode(dag);
DagNode rightDagNode = XNode.RightXNode.GetDagNode(dag);
// Here you can observe how supplying the operator id and both operands that it
// looks in the Dags live list to check if this expression is already there. If
// it is then it returns it and that is how a common sub-expression is formed.
// This is called an internal node.
XNode.DagNode =
dag.GetOrCreateDagNodeForOperator(XNode.Operator,leftDagNode,RightDagNode) );
return XNode.DagNode;
}
}
因此,这是一种查看方式。树的基本操作,仅添加和引用Dag节点。dag的根例如是树的根返回的DagNode。
显然,示例过程可以分解为较小的部分,也可以作为具有虚拟功能的子类来实现。
至于对Dag进行排序,您需要从左到右浏览每个DagNode。换句话说,遵循DagNodes的左边缘,然后是右边缘。数字是反向分配的。换句话说,当您到达没有子节点的DagNode时,请为该Node分配当前的排序编号并增加排序编号,以便递归展开时,将按递增顺序分配编号。
此示例仅处理具有零个或两个子节点的节点的树。显然,有些树的节点包含两个以上的子节点,因此逻辑仍然相同。而不是左右计算,而是从左到右计算...
// Most basic DAG topological ordering example.
void DagNode::OrderDAG(int* counter) {
if (this->AlreadyCounted) return;
// Count from left to right
for x = 0 to this->Children.Count-1
this->Children[x].OrderDag(counter)
// And finally number the DAG Node here after all
// the children have been numbered
this->DAGOrder = *counter;
// Increment the counter so the caller gets a higher number
*counter = *counter + 1;
// Mark as processed so will count again
this->AlreadyCounted = TRUE;
}
该名称告诉您有关其定义的大部分知识:这是一张图形,其中每个边仅沿一个方向流动,并且当您沿一条边向下爬时,路径将永远不会使您回到刚离开的顶点。
我不能说所有用法(维基百科在其中提供帮助),但对我而言,DAG在确定资源之间的依赖性时非常有用。例如,我的游戏引擎将所有加载的资源(材质,纹理,着色器,明文,已解析的json等)表示为单个DAG。例:
一种材料是N个GL程序,每个程序需要两个着色器,每个着色器需要一个纯文本着色器源。通过将这些资源表示为DAG,我可以轻松地在图形中查询现有资源,以避免重复负载。假设您希望多种材料使用具有相同源代码的顶点着色器。当您可以为现有资源建立新的边缘时,为每次使用重新加载源并重新编译着色器非常浪费。通过这种方式,您还可以使用该图来确定是否有任何东西完全依赖于资源,如果没有,则删除它并释放其内存,实际上这几乎是自动发生的。
通过扩展,DAG可用于表达数据处理管道。非循环性质意味着您可以安全地编写上下文处理代码,该代码可以跟随指针从顶点向下移动,而无需再次遇到相同的顶点。诸如VVVV,Max MSP或Autodesk Maya的基于节点的界面之类的可视编程语言都依赖于DAG。
有向无环图在您要表示...有向无环图时非常有用!典型的例子是家谱或家谱。