拓扑排序:判断有向图是否有环(超级详细剖析!!)+207. Course Schedule实例

本文先给定义,接着以举例的形式讲解算法原理,最后使用python实践

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

定义:

直接copy一下 百科上面的吧:

对一个有向无环图(Directed Acyclic Graph简称DAG)G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若边(u,v)∈E(G),则u在线性序列中出现在v之前。通常,这样的线性序列称为满足拓扑次序(Topological Order)的序列,简称拓扑序列。简单的说,由某个集合上的一个偏序得到该集合上的一个全序,这个操作称之为拓扑排序。

使用的算法也很简单步骤大概就是:这里使用bfs(dfs遍历也可以)

一:先计算所有节点的入度

二:将所有入度为0的点进入一个队列

三:然后出队,得到一个元素

四:将该元素指向的所有元素的入度减1

五:如果减后其入度为0则将该元素入队列

六:继续出队重复步骤三,直到队列为空

还是举个例子吧:

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

假设队列为queue

依据图可以得到每个节点的入度:

{A:0,B:1,C:1,D:2,E:1,F:1,G:1,H:1}

一开始只有入度为0的节点入队即A节点入队

queue= [A]

依次遍历其所有指向元素:

首先是B,将其入度减为0,然后其入度为1-1=0,入度为0入队

queue = [B],然后是E,入度减1为0,入度为0入队queue = [B,E]

此时{A:0,B:0,C:1,D:2,E:0,F:1,G:1,H:1}

为了可视化,上面的入度减一的过程其实可以看成擦除边的过程,即上面这一个循环结束后,拓扑图就变成:

好了,B出队列,其指向的C的入度减一,C的入度为0,进入队列,此时:

queue = [E,C]

{A:0,B:0,C:0,D:2,E:0,F:1,G:1,H:1}

拓扑图变为:

E出队,F入度减一入队

queue = [C,F]

{A:0,B:0,C:0,D:2,E:0,F:0,G:1,H:1}

C出队,首先遍历D,D入度减一变为1,不为0,不如队列,

              接着遍历H,H入度减一变为0,入队列

queue = [F,H]

{A:0,B:0,C:0,D:1,E:0,F:0,G:1,H:0}

F出队,遍历D,D的入度减一变为0,入队列

queue = [H,D]

{A:0,B:0,C:0,D:0,E:0,F:0,G:1,H:0}

H出队列,其没有所指向的元素,直接跳过,此时:

queue = [D]

{A:0,B:0,C:0,D:0,E:0,F:0,G:1,H:0}

D出队列,遍历到G,其入度减一为0,入队

queue = [G]

{A:0,B:0,C:0,D:0,E:0,F:0,G:0,H:0}

G出队列,其没有所指的元素,直接跳过

 

再次出队发现队列为空结束。

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

上述出入队列的顺序就是拓扑排序的顺序即ABECHFDG

还没完呢!!!!!!!!!!!!!!!!!

其实它的更多用途是用来判断有向图是否存在环当出队的个数等于图节点个数的时候是无环图,相反便是有环图

比如上面出队的个数是8个(ABECHFDG),图上节点也是8个(ABCDEFGH)所以上图就是无环图,为了对比,下面我们再举一个有环图看一下:很明显红圈的部分就是一个环

我们按上面的算法走一下,这里我就不再详细叙述了,直接给出每一步的结果

开始的时候

queue=[A]

{A:0,B:2,C:1,D:1,E:1}

A出队操作后:

queue=[D]

{A:0,B:1,C:1,D:0,E:1}

D出队列,发现没有所指元素,直接跳过

queue=[]

{A:0,B:1,C:1,D:0,E:1}

此时发现队列为空,没有元素可出,结束

 

最后发现出队的元素个数是2(A,D),而图上的个数是5个(ABCDE),两者不相同证明有环,同时注意有环的时候是没有拓扑排序的即没有拓扑排序的

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

这里给一下判断有向图是否有环的模板吧

queue<int>q;
    for(int i=0;i<n;i++)  //n  节点的总数
        if(in[i]==0) q.push(i);  //将入度为0的点入队列
    vector<int>ans;   //ans 为拓扑序列
    while(!q.empty())
    {
        int p=q.top(); q.pop(); // 选一个入度为0的点,出队列
        ans.push_back(p);
        for(int i=0;i<edge[p].size();i++)
        {
            int y=edge[p][i];
            in[y]--;
            if(in[y]==0)
                q.push(y);  
        }
    }
    if(ans.size()==n)   
    {
        for(int i=0;i<ans.size();i++)
            printf( "%d ",ans[i] );
        printf("\n");
    }
    else printf("No Answer!\n");   //  ans 中的长度与n不相等,就说明无拓扑序列

来至于:https://blog.csdn.net/qq_41713256/article/details/80805338

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

上面部分是理论部分,下面看一下具体实践

很多题目其实可以转化为判断一个有向图是否有环的问题,比如leetcode上面的207. Course Schedule

根据上面的理论,解决该问题就轻而易举啦,代码如下(python)

class Solution(object):
    def canFinish(self, numCourses, prerequisites):
        """
        :type numCourses: int
        :type prerequisites: List[List[int]]
        :rtype: bool
        """
        queue = []
        ins = [0 for i in range(numCourses)]
        for prerequisite in prerequisites:
            ins[prerequisite[0]]+=1
        for i in range(numCourses):
            if ins[i]==0:
                queue.append(i)
        count = len(queue)
        while queue:
            n = queue.pop(0)
            for prerequisite in prerequisites:
                if n== prerequisite[1]:
                    ins[prerequisite[0]]-=1
                    if ins[prerequisite[0]]==0:
                        queue.append(prerequisite[0])
                        count+=1
        return numCourses==count

同理下面这道题也很简单:210. Course Schedule II

它要求在判断有无环的同时,同时要求如果是无环,要返回我们上面说的拓扑序列,否者返回空

这里其实只需要再增加一个列表比如result,用其记录每次出队列的元素,如果最后判断是无环的话,就返回该列表,否者返回空列表即可

拓扑排序是一种常用的有向无环图(DAG)排序算法,可用于判断有向图是否存在环。下面是拓扑排序判断有向图是否有环的概要设计: 1. 统计每个节点的入度,入度为 0 的节点加入队列中。 2. 从队列中取出一个入度为 0 的节点,并将该节点从图中删除。 3. 遍历该节点的所有邻居节点,将其入度减 1。 4. 如果有邻居节点入度为 0,则加入队列中。 5. 重复步骤 2-4 直到队列为空。 6. 如果所有节点都被访问过,则说明不存在环;否则存在环。 具体步骤如下: 1. 初始化一个队列 Q,并统计每个节点的入度。 2. 将入度为 0 的节点加入队列 Q 中。 3. 当队列 Q 不为空时,执行以下操作: a. 从队列 Q 中取出一个入度为 0 的节点; b. 将该节点从图中删除; c. 遍历该节点的所有邻居节点,将其入度减 1; d. 如果有邻居节点入度为 0,则加入队列 Q 中。 4. 如果所有节点都被访问过,则说明不存在环;否则存在环。 需要注意的是,如果图中存在环,则无法进行拓扑排序。因此,在实现拓扑排序算法时,需要先判断图中是否存在环,如果存在环,则直接返回有环的结果,否则进行拓扑排序。 总之,拓扑排序是一种非常实用的图算法,可以用于判断有向图是否存在环,也可以用于确定任务的执行顺序和依赖关系等。在实际应用中,我们可以根据具体的问题场景,灵活地运用拓扑排序算法,提高工作效率和准确性。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值