利用栈完成拓扑排序的基本思想是通过入度(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 条评论