有人可以简单地向我解释什么是有向无环图吗?


109

有人可以简单地向我解释什么是有向无环图吗?我看过维基百科,但它并没有真正使我看到它在编程中的用途。


26
Wikipedia经常包含压倒性的技术内容,这些内容将使初学者进行大量的学习来理解。在这方面,许多数学帮助站点都比较出色,但是不幸的是,它们往往不会进入与计算相关的主题。
乔纳森·浮士德

1
谁使用git谁实际上在不知情的情况下使用DAG,ericsink.com
vcbe / html /

Answers:


86

点指向其他点的线


23
这是最好的答案之一,因为它是描述埋在复杂术语中的简单概念的简单方法(如果我们问这个问题,我们可能不知道图论...甚至需要知道)。我的变体将类似于“跳栏,您永远都不会两次去同一条栏”。尽管从另一个答案中得出的家谱例子可能在概念上更简单,尤其是对于那些不是大学生或酗酒者的人。
汤姆·哈里森

27
…朝着一个方向前进
马克·罗布森

3
这是一个很好的例子,它未能用尽可能少的术语来表达一个固有的复杂概念。这就是为什么欧几里得的第五条假设仍然存在的原因。
Xaqron '18

4
您必须包括“线不形成循环的地方”,否则,您只是在描述有向图,而不是有向无环图。
法拉普

“带线的点指向其他点,没有循环”将是一种改进。
John DeRegnaucourt

172

graph =由节点组成的结构,这些节点之间相互连接

有向=节点(边缘)之间的连接具有方向:A-> B与B-> A不同

acyclic =“ non-circular” =通过沿着边缘在一个节点之间移动,您将永远不会第二次遇到相同的节点。

有向无环图的一个很好的例子是树。但是请注意,并非所有有向无环图都是树。


我了解什么是节点。当您说“ edge”时,是指从节点A指向节点B的箭头吗?
appshare.co 2010年

更好的解释。那么,这与编程有什么关系呢?它与函数式编程有关吗?
appshare.co 2010年

2
它通常用箭头表示,但实际上只是A和B之间存在关系。在您的程序中,这可能是邻接矩阵中代表这两个节点的索引中的真值。
tvanfosson

42
所有定向树都是DAG,但并非所有DAG都是树。由于节点C具有多个父节点,因此DAG A-> B,A-> C,B-> C不能表示为树。
杰森·S

2
边缘的方向性不是将DAG与树分开的唯一功能。与树不同,DAG可以具有多于| V | -1个边缘。例如,A-> B,A-> C,B-> D,C-> D是DAG,但显然不是树,因为它具有相同数量的边和节点。
匿名Mus

49

我看到许多答案表明DAG(有向无环图)的含义,但在其应用中却没有答案。这是一个非常简单的-

先决条件图 -在工程课程中,每个学生都面临选择符合先决条件等要求的学科的任务。现在很明显,如果没有关于算法[A]的前提课程,就无法上人工智能[B]课程。因此,B依赖于A或更好的说法是A具有指向B的边。因此,要到达节点B,您必须访问节点A。很快就会清楚,将所有主题及其先决条件添加到图中之后,结果将是有向无环图。

如果有一个周期,那么您将永远不会完成课程:p

大学中允许学生注册课程的软件系统可以将学科建模为节点,以确保学生在注册当前课程之前已经修读了必修课。

我的教授给出了一个比喻,它最能帮助我理解DAG,而不是使用一些复杂的概念!

另一个实时示例-> 关于如何在版本系统中使用DAG的实时示例


4
这应该是排名最高的答案。简单的类比,并且不使用教科书的定义,OP无法轻松理解。
kimathie

25

有向无环图在编程中的示例用法包括或多或少代表连接性和因果关系的任何事物。

例如,假设您有一个可在运行时配置的计算管道。例如,假设计算A,B,C,D,E,F和G相互依赖:A依赖于C,C依赖于E和F,B依赖于D和E,D依赖于F。这可以表示为DAG。将DAG存储在内存中后,您可以将算法编写为:

  • 确保以正确的顺序评估计算(拓扑排序
  • 如果可以并行执行计算,但每个计算都有最大执行时间,则可以计算整个集合的最大执行时间

等等。

在应用程序编程领域之外,任何不错的自动化构建工具(make,ant,scons等)都将使用DAG来确保程序组件的正确构建顺序。


+1提及因果关系。当您需要表示一个流程的输出是一个或多个其他流程的输入的复杂系统时,这会出现很多问题。
Alex Feinman '02

14

有几个答案给出了使用图形的示例(例如,网络建模),并且您问“与编程有什么关系?”。

该子问题的答案是,它与编程没有任何关系。它与解决问题有关。

就像链表是用于某些类型问题的数据结构一样,图对于表示某些关系也很有用。链表,树,图和其他抽象结构仅与编程有关,因为您可以在代码中实现它们。它们以更高的抽象级别存在。这与编程无关,而在于在解决问题中应用数据结构。


可以在编程中实现。是的,我喜欢那样,因为图形存在于现实世界中,与计算机无关!
appshare.co 2010年

13

有向无环图(DAG)具有以下特性,可将它们与其他图区分开来:

  1. 它们的边缘显示方向。
  2. 他们没有周期。

好吧,我现在可以想到一种用法-DAG(称为Wait-For-Graphs-更多技术细节)在检测死锁方面非常方便,因为它们说明了一组进程和资源之间的依赖关系(两者都是DAG中的节点) 。当检测到周期时,将发生死锁。


1
Andriyev,死锁示例为+1。实际上,这是MySQL的InnoDB引擎使用的,他们称其为“等待图形”,例如,“该行必须等待该行的锁被释放”
Roland Bouman 2010年

是的,您的名字完全正确-等待图表。有些人错过了。更新了响应。:)
Arnkrishn

他们怎么知道有依赖性?是否通过检查两个节点是否具有共同祖先来进行检查?
appshare.co 2010年


11

我假设您已经了解基本图形术语;否则,您应该从关于图论的文章开始。

定向是指边(连接)具有方向的事实。在图中,这些方向由箭头显示。相反的是无向图,其边缘未指定方向。

非循环意味着,如果您从任意节点X开始并遍历所有可能的边缘,那么您必须返回到已使用的边缘才能返回X。

几种应用:

  • 电子表格;DAG文章对此进行了解释。
  • 修订控制:如果您查看该页面中的图表,您将看到修订控制代码的定向是定向的(在此图中为“下降”),并且是非循环的(从未返回“上”)。 。
  • 家谱:它是定向的(您是父母的孩子,而不是相反)和非循环的(您的祖先永远不会是您的后代)。

4

各种图形都用于编程中,以对各种不同的现实世界关系进行建模。例如,社交网络通常由图形表示(在这种情况下为循环)。同样,网络拓扑,族谱,航线,...


4

DAG是一种图形,其中所有内容都沿相同方向流动,并且任何节点都无法引用其自身。

想想祖先的树木;它们实际上是DAG。

所有DAG都有

  • 节点(存储数据的地方)
  • 定向边(指向同一方向)
  • 祖节点(没有父节点的节点)
  • 叶子(没有子节点)

DAG与树不同。在树状结构中,每两个节点之间必须有一条唯一的路径。在DAG中,一个节点可以有两个父节点。

这是一篇有关DAG好文章。希望对您有所帮助。


2

从源代码甚至三个地址(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;
}

1

如果您知道编程中的树,则编程中的DAG相似,但是它们允许一个节点具有多个父节点。当您想让一个节点不仅仅局限于一个父节点时,而又又不存在带有循环的一般图的打结混乱的问题时,这可能很方便。您仍然可以轻松导航DAG,但是有多种方法可以返回到根目录(因为可以有多个父级)。一个DAG通常可以具有多个根,但实际上,最好只坚持一个根,例如一棵树。如果您了解OOP中的单继承与多继承,那么您将了解树与DAG。我已经在这里回答


1

该名称告诉您有关其定义的大部分知识:这是一张图形,其中每个边仅沿一个方向流动,并且当您沿一条边向下爬时,路径将永远不会使您回到刚离开的顶点。

我不能说所有用法(维基百科在其中提供帮助),但对我而言,DAG在确定资源之间的依赖性时非常有用。例如,我的游戏引擎将所有加载的资源(材质,纹理,着色器,明文,已解析的json等)表示为单个DAG。例:

一种材料是N个GL程序,每个程序需要两个着色器,每个着色器需要一个纯文本着色器源。通过将这些资源表示为DAG,我可以轻松地在图形中查询现有资源,以避免重复负载。假设您希望多种材料使用具有相同源代码的顶点着色器。当您可以为现有资源建立新的边缘时,为每次使用重新加载源并重新编译着色器非常浪费。通过这种方式,您还可以使用该图来确定是否有任何东西完全依赖于资源,如果没有,则删除它并释放其内存,实际上这几乎是自动发生的。

通过扩展,DAG可用于表达数据处理管道。非循环性质意味着您可以安全地编写上下文处理代码,该代码可以跟随指针从顶点向下移动,而无需再次遇到相同的顶点。诸如VVVVMax MSP或Autodesk Maya的基于节点的界面之类的可视编程语言都依赖于DAG。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.