基于Python的拓扑排序

什么是拓扑排序?

在图论中,拓扑排序(Topological Sorting)是一个有向无环图(DAG, Directed Acyclic Graph)的所有顶点的线性序列,该序列必须满足下列两个条件:

     1. 每个顶点仅出现一次

     2. 若存在一条从顶点 A 到顶点 B 的路径,那么在序列中顶点 A 出现在顶点 B 的前面。

通俗的说,只有有向无环图(DAG)才有拓扑一说,非DAG没有拓扑排序一说。典型的DAG图如下:

 

                                         基于Python的拓扑排序_第1张图片

 

那么如何对这个有向无环图(DAG)进行拓扑排序呢?

     1. 找出入度数为0的顶点

     2. 将入度数为0的顶点值取出,作为拓扑排序后列表的元素,再将该顶点指向的顶点入度减1

     3. 重复1,2步骤,直到图中没有入度为0且未被访问的顶点,得出的拓扑排序列表即为所求

上图进行拓扑排序的详细图解如下:

 

                   基于Python的拓扑排序_第2张图片

 

上图经过拓扑排序后的结果为{1 , 2 , 3 , 4 , 5}或者{1 , 2 , 4 , 3 , 5},值得注意的是,拓扑排序的结果可能不是唯一的,因为可能存在同时入度为0的顶点。

 

拓扑排序的应用场景

一、课程表的安排

     在安排课程表时,有的课程必须基于其他的课程才能学习,例如,学习数据结构与算法之前,必须学习一门编程语言,因为没有编程语言的基础,无法用代码实现数据结构与算法。同理,与其他科目也是如此。这就是典型的有向无环图的一个缩影,在安排课程表时,基于拓扑排序来给学生安排课程,这样才合理。

二、衣服的穿搭

     举个简单的例子,一个人在早上起床时,必须先穿内衣再穿外衣,必须先穿袜子再穿鞋子,必须先穿内裤再穿外裤,这也是拓扑排序的一个缩影,如果穿搭的顺序不按照拓扑排序来进行,会出现非常尴尬的局面,大家脑补!

 

实例(Leecode真题)

问题需求:

现在你总共有 n 门课需要选,记为 0 到 n-1。

在选修某些课程之前需要一些先修课程。 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示他们: [0,1]

给定课程总量以及它们的先决条件,判断是否可能完成所有课程的学习?

示例 1:

输入: 2, [[1,0]] 
输出: true
解释: 总共有 2 门课程。学习课程 1 之前,你需要完成课程 0。所以这是可能的。


示例 2:

输入: 2, [[1,0],[0,1]]
输出: false
解释: 总共有 2 门课程。学习课程 1 之前,你需要先完成​课程 0;并且学习课程 0 之前,你还应先完成课程 1。这是不可能的。

 

解题思路

     1)、将图进行加工,得到更直观的图。初始化一个列表graph,该列表的元素长度为课程数,列表元素是一个子列表,子列表由三个元素,第一个元素是该顶点的入度数,第二个元素是该顶点的顶点值,第三个元素是包含指向所有的顶点元素的顶点值的列表集合。遍历给出的列表prerequisites,获得第一个元素的值i,j,将graph[i][0]加1,即graph中顶点值为i的子列表的第一个元素(入度数)加1,将graph[j][2].append(i),即将graph中顶点值为j的顶点指向的顶点列表集合添加顶点值为i的顶点。

     2)、得到加工后的图,定义辅助栈,初始化将graph中入度数为0的顶点压入栈中,再定义一个记录已经访问的顶点的set集合

     3)、以栈不为空作为循环条件,进入循环,弹出栈顶元素,将顶点值添加进拓扑排序表,将其指向的顶点入度数减1;再对graph进行遍历,将入度数为0且顶点值不在记录已经访问的set集合里面的顶点入栈。

     4)、重复第三步,退出循环后,判断拓扑排序表的长度是否等于课程数,若是,则代表拓扑排序成功,若不等于,则代表图不是有向无环图,拓扑排序失败

这个算法的成本较大,有待优化,给出这个算法,是为了更好地让大家理解拓扑排序。话不多说,代码如下:

 

class Solution:
    def canFinish(self, numCourses, prerequisites):
        if prerequisites == []:
            return True
        grap = [[0,_,[]] for _ in range(numCourses)]
        for i in prerequisites:
            grap[i[0]][0]+=1
            grap[i[1]][2].append(i[0])
        queue = []
        visited = set()
        for i in grap:
            if i[0] == 0:
                queue.append(i)
                visited.add(i[1])
        if queue == []:
            return False
        results = []
        while queue:
            cur = queue.pop()
            results.append(cur[1])
            for j in cur[2]:
                grap[j][0]-=1
            for n in grap:
                if n[0]==0 and n[1] not in visited:
                    queue.append(n)
                    visited.add(n[1])
        if len(results) == numCourses:
            return True
        return False

 

 

你可能感兴趣的:(python算法)