拓扑排序(Kahn算法)

拓扑排序(Kahn算法)

拓扑排序是针对有向无环图(DAG)的排序算法,目标是得到一个节点序列 ,使图中所有有向边的起点都在终点之前 ,进而可以解决一系列依赖调度类问题(如课程先修,任务执行顺序,依赖包安装等)

一、拓扑排序的核心概念

  1. 有向无环图(DAG) (Directed Acyclic Graph)
    拓扑排序的唯一适用场景 ,图中所有边都是有向的,而且不存在任何环。有环图一定没有拓扑序
  2. 入度 (In-degree)
    某个节点的入度 = 指向该节点的有向边的数量(该节点的前驱依赖数)
  • 例如有向边a->b,则该节点b入度+1
  • 入度为 0 的节点无任何前驱依赖,可以作为拓扑排序的起始节点
  1. 出度 (Out-degree)
    某个节点的出度 = 该节点出发的有向边数量
  • 例如:有向边a->b、a->c,节点a的出度为2

二、拓扑排序核心原理

kahn算法是基于入度的贪心策略

反复寻找并处理入度为0的节点,移除其出边,若邻接节点入度变为0则加入处理队列,直到所有节点处理完毕或者发现环

算法核心步骤拆解

  1. 初始化 :遍历所有节点,将入度为0的节点加入队列,作为拓扑排序的起始点,无任何依赖
  2. 处理队列 :取出队首入度为0的节点,加入拓扑排序
  3. 更新邻接节点 :遍历该节点所有出边,对所有邻接节点入度减一,即移除依赖边
  4. 重新判断入度:若邻接节点入度变为0,则说明所有前驱依赖已经处理完毕,则加入队列等待处理
  5. 环的判断 :若最终处理完毕的节点数小于总节点数n 则说明图中存在环(环中不存在入度为0的节点 无法加入队列),即无拓扑序,否则队列的处理顺序就是合法拓扑序

三、代码讲解

1. 图存储方式(邻接表)

拓扑排序处理有向图 ,因此邻接表添加单向边 a->b即可,且加边时更新终点入度

复制代码
void add(int a,int b)
{
    e[idx] = b,ne[idx] = h[a],h[a] = idx++; // 邻接表头插法,添加a→b的有向边
}
  • 与无向图的区别:无向图即双向图,再添加b->a的边即可,拓扑序中仅存储单向信息
  • 入度更新:主函数中add(a,b)后执行in[b]++,更新b点的入度

2.拓扑排序函数topsort()(kahn算法实现)

复制代码
bool topsort()
{
    int hh = 0,tt = -1;
    // 步骤1:初始化队列,将所有入度为0的节点入队(拓扑排序起始点)
    for(int i = 1;i<=n;i++) if(!in[i]) q[++tt] = i;
    
    // 步骤2:队列非空时,循环处理入度为0的节点
    while(hh<=tt)
    {
        int t = q[hh++]; // 取出队首节点t(处理当前入度为0的节点)
        // 步骤3:遍历t的所有出边,移除出边并更新邻接节点入度
        for(int i = h[t];~i;i = ne[i])  // ~i 等效 i!=-1,遍历t的所有出边
        {
            int j = e[i]; // j是t的邻接节点(t→j)
            // 步骤4:j的入度减1(解除t对j的依赖),若入度为0则入队
            if(--in[j] == 0) q[++tt] = j;
        }
    }
    // 关键:判断是否处理完所有节点(无环)
    // tt初始为-1,入队n个节点则tt = n-1,说明所有节点都入队(存在拓扑序)
    return tt == n-1;
}
核心要点
  • 队列初始化 for(int i = 1;i<=n;i++) if(!in[i]) q[++tt] = i;
    找到入度为0的所有节点并全部入队才符合贪心策略,且拓扑序不唯一
  • 出边遍历及入度更新 :·--in[j] == 0
    先对j入度减1 ,表示移除t->j的依赖边,若减后为0,则说明j的前驱依赖处理完毕,可以入队
  • 环的判断逻辑return tt == n-1
    队列指针tt初始为-1,每入队一个节点tt++,若最终tt == n-1,则恰好n个节点入队并处理,则无环,否则存在环
  • 拓扑序列的存储 :数组q[]为拓扑序列
    由于利用数组模拟队列的特性,节点入队顺序 就是kahn算法处理顺序 ,因此q[0~n-1]就是拓扑排序

四、题目链接

https://www.acwing.com/problem/content/850/

题解

复制代码
//topsort拓扑排序  拓扑图:有向无环图,入度:一个点的进边数量,出度:一个点的出边数量
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>

using namespace std;
const int N = 1e5+10;

int idx,h[N],e[N],ne[N],in[N],q[N];
int n,m;

void add(int a,int b)
{
    e[idx] = b,ne[idx] = h[a],h[a] = idx++;
}

bool topsort()
{
    int hh = 0,tt = -1;
    for(int i = 1;i<=n;i++) if(!in[i]) q[++tt] = i;  //找到入度为0的点  即为起点 入队
    while(hh<=tt)
    {
        int t = q[hh++];
        for(int i = h[t];~i;i = ne[i])  //遍历该点所有出边
        {
            int j = e[i];
            if(--in[j] == 0) q[++tt] = j;  //每次出队则减少当前点的入度 若入度为0则为新的起点再入队
        }
    }
    return tt == n-1;  //tt起点为-1  若最终为n-1则刚好覆盖所有节点,即存在拓扑排序(能够进入队列的都满足拓扑排序要求,tt移动了n则说明全部满足)
}

int main()
{
    memset(h,-1,sizeof h);
    scanf("%d%d",&n,&m);
    while(m--)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        add(a,b);
        in[b]++;  //a->b则b的入度++
    }
    if(!topsort()) puts("-1");
    else
    {
        for(int i = 0;i<n;i++) printf("%d ",q[i]);  //数组模拟队列的特性,队列中前n个为拓扑排序后的结果
    }
    return 0;
}

寒假效率好低~

相关推荐
得物技术28 分钟前
从狂野代码到按目标生产:得物推荐 AI Harness 的工程化实践|AICon 演讲整理
人工智能·算法·架构
AI小老六4 小时前
SkillOpt 架构拆解:把 Skill 文本当参数,用执行轨迹训练 Agent
后端·算法·ai编程
胡萝卜术4 小时前
从“分数打架”到“排名投票”:为什么你的ChatBI必须用RRF?
算法·设计模式·面试
Asize5 小时前
初识DFS 与 BFS:递归、队列与图遍历
算法
罗西的思考19 小时前
机器人 / 强化学习】HIL-SERL:人类在环驱动的具身智能进化框架
人工智能·算法·机器学习
CSharp精选营21 小时前
关系型 vs 非关系型:从原理到选型,一文搞定数据库核心分类
数据结构·nosql·关系型数据库·非关系型数据库·技术选型
美团技术团队1 天前
LongCat 开源 VitaBench 2.0:长期动态智能体基准新标杆
人工智能·算法
用户805533698031 天前
不止三件套:QObject 属性系统全关键字与运行时反射!
c++·qt
To_OC2 天前
LC 207 课程表:刚学图论那会儿,我连这是拓扑排序都没看出来
javascript·算法·leetcode
To_OC2 天前
LC 208 实现 Trie 前缀树:曾被名字劝退,写完发现是送分题
javascript·算法·leetcode