
大家好,很高兴又和大家见面啦!!!
今天我们将延续图的应用探索,在前两期学习的最小生成树和最短路径基础上,展开图的第三个重要应用方向——**有向无环图(DAG)**。
在前期内容中,我们深入探讨了图的两种经典应用:
🔍 BFS的特殊价值:当边权为1时,其第一次访问路径即最短跳数路径,是理解拓扑排序等高级算法的基础。
今天我们将把目光转向图的另一个精妙应用。当问题涉及执行顺序约束(如任务依赖关系)或计算结构复用(如嵌套表达式)时,DAG凭借其有向无环的特性成为理想解决方案。
💡 为何DAG是自然延伸
下面我们将通过具体案例,展示DAG如何将表达式存储空间缩减42%,马上开始今天的核心内容!
有向无环图:一个有向图中不存在环,则称为有向无环图,简称DAG(Directed Acyclic Graph) 图。
有向无环图是描述含有公共子式的表达式的有效工具,例如表达式:
在这个表达式中,我们如果将运算符作为根结点,两个操作数作为根结点的左右子树,那么我们就可以通过一棵二叉树来表达上式:
graph TB
A[+]
B[*]
C[*]
D[+]
E[+]
F[+]
G[*]
H[*]
I[+]
J[*]
b1[b]
b2[b]
c1[c]
c2[c]
c3[c]
d1[d]
d2[d]
d3[d]
e1[e]
e2[e]
A---->a
A---->b1
D---->c1
D---->d1
C---->b2
C---->D
B---->A
B---->C
F---->c2
F---->d2
G---->F
G---->e1
E---->B
E---->G
I---->c3
I---->d3
J---->I
J---->e2
H---->E
H---->J在这么一棵二叉树中,我们不难发现,虽然表达式中只有3种标点符号和5个字母,但是我们却花费了大量的空间将每一个标点符号和每一个字母都记录了下来。
那有没有一种方法能够很大程度上的节省空间呢?
这个就是我们前面提到的有向无环图的功能了,在有向无环图中,我们可以通过把相同的部分进行共享,以此来缩减存储空间的开销。
为了更好的说明有向无环图,下面我们就来一步一步的合并上图中的相同子式;
这里我们从二叉树的最底层开始,按照从左到右的顺序进行合并。该二叉树共有6层,因此我们先从第6层开始:
graph TB
c
d+,但是第五层的我们目前还不清楚,所有这里先保留:graph TB
a
b
+
c2[c]
d2[d]
+---->c1[c]
+---->d1[d]+,同时又出现了c和d,这一层的c和d我们目前需要保留;+,第一个+的左右子树是元素a和b,第二个+的左右子树是元素c和d,这与第五层中的+的左右子树相同,因此这里的+与第五层的+进行合并graph TB
+1[+]
+2[+]
+1---->a
+1---->b
*---->b
*---->+2
+2---->c
+2---->d
e
c2[c]
d2[d]*/*/+/e*的左右子树分别是第四层的+和*,第二个*的左右子树分别是第四层的+和e,因此这两个*不能合并,需要保留;+的左右子树是c和d,与第四层的第二个+和第五层的+相同,因此这里的+可以与第五层的+进行合并;graph TB
*1[*]---->+1[+]
*1---->*3[*]
+1---->a
+1---->b
*3---->b
*3---->+2[+]
+2---->c
+2---->d
*2[*]---->+2
*2---->e1[e]
e2[e]+/*+的左右子树是第三层的两个*,因此需要保留*的左右子树是第三层的+和e,这与第三层的第二个*的左右子树相同,因此可以合并graph TB
+1[+]---->*1[*]
+1[+]---->*2[*]
*1---->+2[+]
*1---->*3[*]
+2---->a
+2---->b
*3---->b
*3---->+3[+]
+3---->c
+3---->d
*2---->+3
*2---->e*+和*,这里需要保留graph TB
*0[*]---->+1
*0---->*2
+1[+]---->*1[*]
+1[+]---->*2[*]
*1---->+2[+]
*1---->*3[*]
+2---->a
+2---->b
*3---->b
*3---->+3[+]
+3---->c
+3---->d
*2---->+3
*2---->e可以看到,经过合并后,我们目前只使用了12个结点就完成了该表达式的描述,相比于之前的二叉树的21个结点,我们就节省了9个结点的存储空间;
当我们要读取一个DAG图时,也很简单,我们同样只需要从最底层开始即可,如下所示:
()删除后就得到了最终的表示式:通过本文的探讨,我们深入理解了有向无环图(DAG) 的核心价值: 空间高效性:将表达式 ((a+b)(b(c+d))+(c+d)e)((c+d)*e) 的存储节点从二叉树的21个压缩至DAG的12个,节省了42% 的存储空间
💡 核心洞察:DAG通过有向性建立执行顺序,通过无环性保障逻辑可行性,这正是解决复杂依赖问题的关键!
DAG的精妙远不止于此! 在下一篇内容中,我们将解锁DAG的杀手级应用——
🔥 拓扑排序(Topological Sorting)
我们将揭秘: ✅ 如何将DAG转化为线性序列 ✅ Kahn算法与DFS实现的双重解析 ✅ 动态任务调度系统的底层逻辑 剧透亮点:拓扑排序本质上是对DAG的BFS/DFS高阶应用,这与我们在最短路径中讨论的BFS算法形成了奇妙的知识闭环!