[声明: 本文系学校作业之一,仅供学习交流使用,请勿上传至任何平台。欢迎大家改进优化,并在讨论区留言,共同提高。]
举例简介拓扑排序
举个例子:
该图表示穿衣顺序,根据生活经验我们知道要保证D袜子要在E鞋子的前面穿,ABC内裤秋裤牛仔裤的相对顺序不能变,C牛仔裤也要在E鞋子的前面,F秋衣也要在G外套的前面。而D袜子和G外套的顺序没有要求。所以这里能满足以上条件的排序都是拓扑排序。比如: ABCDEFG, ADFBGCE。
Python实现的原理
- 既然是图,[给定,已知] 定义一个
类
Node,它有Attribute: name, color(字符串:借助颜色来判断一个节点的访问状态), successors(列表: 给出该节点指向的所有节点)
# 给定
A = Node()
B = Node()
A.name = "A"
B.name = "B"
A.color = B.color = "white"
A.successors = [B]
B.successors = []
G = [A,B]
# 任务: 对G拓扑排序
要求
- 我们的拓扑排序程序的输入为 包含所有需要排序的名字的列表 (比如 [C, D, B, A, E, G, F] )
- 输出为 一个正确的拓扑排序。
- 如果给的图
不存在拓扑排序
,比如上例中增加E指向A,图中出现了环
,ABCDE就没有相对顺序(因为A要在E的前面,E也要在A的前面), 程序返回 [-1]
方法
借助深度搜索, 根据颜色
判断节点的状态
,比如白色表示没有被访问,灰色表示还在访问中,黑色表示该节点访问结束。被访问完的节点的逆序即为一个拓扑排序!
- 拿上举例: 输入序列随机,假设这里为 [C, D, B, A, E, G, F],(它们已经被设置为Node了,颜色初始化为白色,连接顺序也已经通过successors设置好了)
- 按照输入顺序从C开始访问,此时正在访问C,把C颜色由白色改为灰色,按顺序访问C.successors的节点,这里只有一个E,E还没有被访问(因为它是白色),把E的颜色由白色改为灰色,访问E.successors,此处为空(因为是最后一个节点),把E的颜色由灰色改为黑色。把E记录下来
- 回溯至C节点,C.successors全部被访问过了(因为唯一的子节点E颜色为黑色),把C的颜色由灰色改为黑色。把C记录下来
- 此时由输入列表的第一个节点C触发的所有信息都已经处理完毕,查看输入列表的第二个节点D,它是白色,还没有被访问,那么访问D,颜色由白色改为灰色,D.successors为E,但是E为黑色,表面D已经被访问过,那么把D的颜色由灰色改为黑色。把D记录下来输入列表的第三个节点B为白色,没有被访问,改为灰色,successors全部被访问,改为黑色。把B记录下来
- 同理,把A记录下来
- E是黑色,已经被访问过,直接跳过
- 同理,先后把G和F记录下来
- 此时会发现记录顺序 [E, C, D, B, A, G, F] 是一个逆序的拓扑排序。因为如果这个图中的箭头代表优先级,越往下优先级越低,那么借助深度搜索最先记录的是最末端的节点,即优先级最低的节点,那么拓扑排序代表的是优先级从高到低,与记录顺序
正好相反
。
如何判断是否有环
如果有环,例如添加E指向A。那么在以上相同输入 [C, D, B, A, E, G, F] 下有如下操作顺序:
* C(白变灰), E(白变灰),A(白变灰),B(白变灰),此时查看B.successors发现其中C的颜色为灰色!!!。据此判断是否有环。
Python代码
首先给出初始化节点的定义:
class Node:
def __init__(self, name = None, color = 'white', successors = []):
self.name = name
self.color = color
self.successors = successors
定义深度搜索,输入为一个列表和一个起始点,从起始点开始搜索,改变途中的节点的状态,返回记录的节点。我们根据输入列表依次进行如下dfs,然后把输出的记录节点拼接在一起,最后取逆序即为拓扑排序。
def dfs(G, r):
besuchtList = [] # 用于记录访问过的节点的一个列表
stack = [] # 用于存储需要被访问的节点
r.color = 'gray' # 改变当前节点的颜色
stack.append(r)
while stack != []:
v = stack[-1]
a = False
for n in v.successors:
if n.color == 'gray':# 如果有环,直接返回[-1]
return [-1]
for n in v.successors:
if n.color == 'white':
n.color = 'gray'
stack.append(n)
a = True
break
if not a: # 说明successors中不再有没有被访问的节点->变为黑色,记录该节点
v.color = 'bleak'
besuchtList.append(stack.pop())
return besuchtList
定义主程序拓扑排序,输出拓扑 排序好的节点的内容(名字)。
def top_order(G):
L = []
nameList = []
for ele in G:
if ele.color == 'white':
L.extend(tiefsuche(G,ele))
if -1 not in L: # 没有环
L.reverse() # 逆序
for knoten in L:
nameList.append(knoten.name) # 显示节点的名字,而不是存储地址
#或nameList = list(map(lambda x: x.name, L))
return nameList
else:
return [-1]
测试例子
A = Node("内裤",'white',[])
B = Node("秋裤",'white',[])
C = Node("牛仔裤",'white',[])
D = Node("袜子",'white',[])
E = Node("鞋子",'white',[])
F = Node("秋衣",'white',[])
G = Node("外套",'white',[])
A.successors = [B]
B.successors = [C]
C.successors = [E]
D.successors = [E]
F.successors = [G]
G = [E,C,D,B,A,G,F]
print(top_order(G))
测试结果:
['秋衣', '外套', '内裤', '秋裤', '袜子', '牛仔裤', '鞋子']
至此,完工大吉!