Python实现拓扑排序(借助深度搜索,用颜色表示搜索状态)

Python实现拓扑排序--借助深度搜索,用颜色表示搜索状态


[声明: 本文系学校作业之一,仅供学习交流使用,请勿上传至任何平台。欢迎大家改进优化,并在讨论区留言,共同提高。]

举例简介拓扑排序

举个例子:

A:内裤
B:秋裤
C:牛仔裤
D:袜子
E:鞋子
F:秋衣
G:外套

该图表示穿衣顺序,根据生活经验我们知道要保证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))

测试结果:

['秋衣', '外套', '内裤', '秋裤', '袜子', '牛仔裤', '鞋子']

下载链接:gitee

至此,完工大吉!

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值