利用栈完成拓扑排序的基本思想是通过入度(in-degree)记录每个顶点的依赖关系,依次处理入度为0的顶点,并更新其他顶点的入度,直到所有顶点都被处理。
计算方法
- 构建入度数组:
- 通过遍历邻接表,统计每个顶点的入度,并存储在数组中。
- 初始化栈:
- 将所有入度为0的顶点入栈,因为它们不依赖于其他顶点。
- 处理栈:
- 重复以下步骤,直到栈为空:
- 从栈中取出一个顶点,记录为拓扑排序结果。
- 遍历该顶点的邻接表,对其所有邻接点的入度减1。
- 如果某个邻接点的入度减为0,则将该顶点入栈。
- 检查结果:
- 如果处理完所有顶点且拓扑排序结果包含所有顶点,则成功;否则说明图中存在环,不是有向无环图(DAG)。
举例分析
例1
我们有以下有向图,其邻接表表示如下:

步骤1:计算所有顶点的入度
- 遍历邻接表:
- 顶点
0
没有任何入边,入度为0
。 - 顶点
1
被顶点0
指向,入度为1
。 - 顶点
2
被顶点0
指向,入度为1
。 - 顶点
3
被顶点1
和2
指向,入度为2
。
- 顶点
入度结果:
入度数组:0 -> 0, 1 -> 1, 2 -> 1, 3 -> 2
步骤2:将入度为0的顶点入栈
- 检查每个顶点的入度:
- 顶点
0
入度为0
,因此将0
入栈。
- 顶点
初始栈:
栈: [0]
步骤3:开始处理栈中的顶点,逐步更新入度
我们依次弹出栈顶顶点,更新其邻接点的入度。邻接点的入度每次减少1,当入度变为0时将其入栈。
第1次处理栈:
- 弹出顶点
0
。 - 将
0
记录为拓扑排序的第一个顶点。 - 遍历顶点
0
的邻接点1
和2
: - 顶点
1
的入度由1
减为0
,将1
入栈。 - 顶点
2
的入度由1
减为0
,将2
入栈。
结果:
拓扑序列: [0]
栈: [1, 2]
入度更新:0 -> 0, 1 -> 0, 2 -> 0, 3 -> 2
第2次处理栈:
- 弹出顶点
2
。 - 将
2
记录为拓扑排序的第二个顶点。 - 遍历顶点
2
的邻接点3
: - 顶点
3
的入度由2
减为1
(尚未达到0)。
结果:
拓扑序列: [0, 2]
栈: [1]
入度更新:0 -> 0, 1 -> 0, 2 -> 0, 3 -> 1
第3次处理栈:
- 弹出顶点
1
。 - 将
1
记录为拓扑排序的第三个顶点。 - 遍历顶点
1
的邻接点3
: - 顶点
3
的入度由1
减为0
,将3
入栈。
结果:
拓扑序列: [0, 2, 1]
栈: [3]
入度更新:0 -> 0, 1 -> 0, 2 -> 0, 3 -> 0
第4次处理栈:
- 弹出顶点
3
。 - 将
3
记录为拓扑排序的第四个顶点。 - 顶点
3
没有邻接点,因此无需更新其他顶点的入度。
结果:
拓扑序列: [0, 2, 1, 3]
栈: []
入度更新:0 -> 0, 1 -> 0, 2 -> 0, 3 -> 0
步骤4:检查是否所有顶点被处理
- 栈为空,表示所有顶点都已被处理。
- 拓扑排序完成,得到的序列
[0, 2, 1, 3]
是一个有效的拓扑排序。
各顶点的入栈情况
- 顶点
0
:在初始扫描时入栈。 - 顶点
1
:在处理顶点0
时,入度减为0后入栈。 - 顶点
2
:在处理顶点0
时,入度减为0后入栈。 - 顶点
3
:在处理顶点1
时,入度减为0后入栈。
拓扑排序逻辑总结
- 初始扫描图中所有顶点,将入度为0的顶点入栈。
- 每次弹出栈顶顶点,记录为拓扑序列中的一个顶点。
- 更新邻接点的入度,任何入度变为0的顶点立即入栈。
- 循环处理,直到栈为空。如果最后处理的顶点数少于图的顶点数,说明图中存在环。
最终拓扑排序结果为:
[0, 2, 1, 3]
当然也可以为:
[0, 1, 2, 3]
例2
我们有以下有向图,其邻接表表示如下:

步骤1:计算各顶点的入度
遍历邻接表,统计每个顶点的入度:
- 顶点
1
:没有入边,入度为0
。 - 顶点
2
:被顶点1
指向,入度为1
。 - 顶点
3
:被顶点1
指向,入度为1
。 - 顶点
4
:没有入边,入度为0
。 - 顶点
5
:被顶点2
、3
、4
指向,入度为3
。
入度数组:
入度:1 -> 0, 2 -> 1, 3 -> 1, 4 -> 0, 5 -> 3
步骤2:初始入栈
- 找到所有入度为
0
的顶点,将它们入栈:- 顶点
1
和顶点4
入栈。 - 初始栈:
[1, 4]
- 顶点
步骤3:处理栈中顶点
开始弹出栈顶顶点,并更新邻接点的入度。如果某个邻接点的入度变为 0
,将其入栈。
第1次处理栈
- 弹出顶点
4
,记录为拓扑序列的第一个顶点。 - 顶点
4
的邻接点是5
:- 顶点
5
的入度从3
减为2
(尚未达到0
)。
- 顶点
- 栈:
[1]
- 拓扑序列:
[4]
第2次处理栈
- 弹出顶点
1
,记录为拓扑序列的第二个顶点。 - 顶点
1
的邻接点是2
和3
:- 顶点
2
的入度从1
减为0
,将2
入栈。 - 顶点
3
的入度从1
减为0
,将3
入栈。
- 顶点
- 栈:
[2, 3]
- 拓扑序列:
[4, 1]
第3次处理栈
- 弹出顶点
3
,记录为拓扑序列的第三个顶点。 - 顶点
3
的邻接点是5
:- 顶点
5
的入度从2
减为1
(尚未达到0
)。
- 顶点
- 栈:
[2]
- 拓扑序列:
[4, 1, 3]
第4次处理栈
- 弹出顶点
2
,记录为拓扑序列的第四个顶点。 - 顶点
2
的邻接点是5
:- 顶点
5
的入度从1
减为0
,将5
入栈。
- 顶点
- 栈:
[5]
- 拓扑序列:
[4, 1, 3, 2]
第5次处理栈
- 弹出顶点
5
,记录为拓扑序列的第五个顶点。 - 顶点
5
没有邻接点,无需更新。 - 栈:
[]
- 拓扑序列:
[4, 1, 3, 2, 5]
结果
- 入栈顺序(按照入栈时间记录):
- 初始入栈:
[1, 4]
- 处理顶点
1
时,2
和3
入栈。 - 处理顶点
2
和3
时,5
入栈。
- 完整入栈情况:
[1, 4, 2, 3, 5]
- 拓扑排序次序(出栈顺序)
- 拓扑序列:
[4, 1, 3, 2, 5]
- 拓扑序列:
注意:拓扑排序的多种可能性
拓扑排序可能有多种结果,取决于同时满足入度为 0
的顶点在栈中的处理顺序。例如:
- 如果优先处理顶点
1
后再处理顶点4
,结果可能是:[1, 4, 2, 3, 5]
。 - 当前结果
[4, 1, 3, 2, 5]
依然是一个有效的拓扑排序。
评论留言
欢迎您,!您可以在这里畅言您的的观点与见解!
0 条评论