首页 > 其他分享 >拓扑排序

拓扑排序

时间:2022-12-24 11:00:41浏览次数:60  
标签:graph 拓扑 self 算法 排序 节点

目录

拓扑排序

定义

有向图的拓扑排序是对其顶点的一种线性排序,使得对于从顶点 \(u\) 到顶点 \(v\) 的每个有向边 \(uv\),\(u\) 在排序中都在 \(v\) 之前。

注意:当且仅当图中没有定向环时(即 有向无环图 ),才有可能进行拓扑排序。

拓扑排序主要有Kahn算法和DFS算法两种:

  • Kahn算法采用入度方法,以循环迭代方法实现;
  • DFS算法采用出度方法,以递归方法实现。

Kahn算法和DFS算法的复杂度是一样的,算法过程也是等价的,不分优劣,因为本质上循环迭代 + 栈 = 递归。

算法

Kahn(卡恩)算法

Kahn算法采用入度方法,其算法过程如下:

  • 选择入度为0的节点,输出到结果序列中;
  • 删除该节点以及该节点的边;
  • 重复执行步骤1和2,直到所有节点输出到结果序列中,完成拓扑排序;
  • 如果最后还存在入度不为0的节点,说明有向图中存在环,无法进行拓扑排序。

DFS 算法

深度优先搜索以任意顺序循环遍历图中的每个节点,若搜索进行中碰到之前已经遇到的节点,或碰到叶节点,则中止算法。

DFS算法采用出度算法,其算法过程如下:

  • 对有向图进行深度优先搜索;
  • 在执行深度优先搜索时,若某个顶点不能继续前进,即顶点的出度为0,则将此顶点入栈;
  • 最后对栈中的序列进行逆排序,即完成拓扑排序;如果深度优先搜索时,碰到已遍历的节点,说明存在环。

应用

应用1:Leetcode.207

题目

207. 课程表

解题思路

方法一:深度优先

深度优先搜索以任意顺序循环遍历图中的每个节点。若搜索进行中碰到之前已经遇到的节点,或碰到叶节点,则中止算法。

首先,我们将边转换成邻接表,然后通过深度优先遍历,用数组 \(visited\) 记录顶点是否访问过,同时使用 \(path\) 记录所有访问的路径,如果访问过的路径没有成环,那说明可以完成课程,如果成环,则不能完成所有课程。

方法二:广度优先

假设L是存放结果的列表,先找到那些入度为零的节点,把这些节点放到L中,因为这些节点没有任何的父节点。

然后把与这些节点相连的边从图中去掉,再寻找图中的入度为零的节点。

对于新找到的这些入度为零的节点来说,他们的父节点已经都在L中了,所以也可以放入L。

重复上述操作,直到找不到入度为零的节点。如果此时L中的元素个数和节点总数相同,说明排序完成;如果L中的元素个数和节点总数不同,说明原图中存在环,无法进行拓扑排序。

代码实现

DFS算法实现

from collections import defaultdict
from typing import List


class Solution:
    def __init__(self):
        self._has_cycle = False

    def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:
        self._has_cycle = False
        # 创建一个邻接表
        graph = self.build_graph(prerequisites)
        # 记录是否已经访问过
        visited = [False] * numCourses
        # 记录当前路径
        path = [False] * numCourses
        # 遍历图中所有的节点
        for i in range(numCourses):
            self.dfs(graph, i, visited, path)
        return not self._has_cycle

    def build_graph(self, prerequisites: List[List[int]]):
        graph = defaultdict(list)
        for prerequisite in prerequisites:
            graph[prerequisite[0]].append(prerequisite[1])
        return graph

    def dfs(self, graph, v, visited, path):
        if path[v]:
            self._has_cycle = True

        if visited[v] or self._has_cycle:
            return

        visited[v] = True
        path[v] = True
        for neighbor in graph[v]:
            self.dfs(graph, neighbor, visited, path)
        path[v] = False
        return

Kahn算法实现

from collections import defaultdict, deque
from typing import List

class Solution:
    def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:
        # 用邻接表记录依赖关系
        graph = defaultdict(list)
        # 记录每个顶点的入度
        incoming_edges = [0] * numCourses
        # 创建图
        for prerequisite in prerequisites:
            graph[prerequisite[1]].append(prerequisite[0])
            incoming_edges[prerequisite[0]] += 1
        # 将入度为零的顶点放入队列
        q = deque([u for u in range(numCourses) if incoming_edges[u] == 0])
        # 广度优先搜索
        visited = 0
        while q:
            visited += 1
            u = q.popleft()
            for v in graph[u]:
                incoming_edges[v] -= 1
                if incoming_edges[v] == 0:
                    q.append(v)
        return visited == numCourses

参考:


标签:graph,拓扑,self,算法,排序,节点
From: https://www.cnblogs.com/larry1024/p/17002453.html

相关文章

  • 按字典排序(中文排序)
    //按字典排序(中文排序)constcityArr=['湖北','广州','上海','重庆','厦门'];functionchangeCity(arr,callback){callback(arr.sort((a,b)......
  • 快速排序 python
    步骤在一序列中定一个轴为基轴(通常为了方便定最左那个数),定序列左右指针,右指针开始扫描,比基轴大则指针继续往前扫,当扫到比基轴小时,把这个数放到最左边,再开始扫左......
  • 归并排序
    本题要求实现二路归并排序中的归并操作,待排序列的长度1<=n<=1000。函数接口定义:1voidMerge(SqListL,intlow,intm,inthigh);其中L是待排序表,使排序后的数据从小......
  • 直接插入排序
    本题要求实现直接插入排序函数,待排序列的长度1<=n<=1000。函数接口定义:1voidInsertSort(SqListL);其中L是待排序表,使排序后的数据从小到大排列。###类型定义:1t......
  • 希尔排序的实现
    本题要求实现一趟希尔排序函数,待排序列的长度1<=n<=1000。函数接口定义:1voidShellInsert(SqListL,intdk);其中L是待排序表,使排序后的数据从小到大排列。###类型......
  • python归并排序
    采用了分治法,把序列不断的等分序列,最后分成一个之后,再把它两两合并叠加起来,利用了扑克牌两个正序序列进行排序合并时间复杂度nlogn代码defmerge_sort(lists):if......
  • 快速排序
    本题要求实现快速排序的一趟划分函数,待排序列的长度1<=n<=1000。函数接口定义:1intPartition(SqListL,intlow,inthigh);其中L是待排序表,使排序后的数据从小......
  • 堆排序
    本题要求实现堆排序中的筛选函数,待排序列的长度1<=n<=1000。函数接口定义:1voidHeapAdjust(HeapTypeH,ints,intm);其中L是待排序表,使排序后的数据从小到大排......
  • 冒泡排序
    冒泡排序只会操作相邻的两个数据。每次冒泡操作都会对相邻的两个元素进行比较,看是否满足大小关系要求。如果不满足就让它俩互换。一次冒泡会让至少一个元素移动到它应......
  • js:Object对象按照key的升序排序
    思路:js的Object对象类型,不能直接排序,不过Array是可以排序的将Object类型的key,转为Array排序,再将结果转为Object示例letdata={name:"tom",age:20,};consol......