拓扑排序

 

 

拓扑排序

定义:

拓扑排序指的是有向无环图所有顶点的线性序列

该序列需满足俩个条件:

  • 每个顶点只出现一次
  • 若存在一条从顶点 A 到顶点 B 的路径,那么在序列中顶点 A 出现在顶点 B 的前面

有向无环图(DAG图)才有拓扑排序,且可能不止一种排序,其他的无拓扑排序一说

应用

拓扑排序通常用来“排序”具有依赖关系的任务。

比如,如果用一个DAG图来表示一个工程,其中每个顶点表示工程中的一个任务,用有向边表示在做任务 B 之前必须先完成任务 A。故在这个工程中,任意两个任务要么具有确定的先后关系,要么是没有关系,绝对不存在互相矛盾的关系(即环路)。

实现

拓扑排序的建图采用邻接链表的方式,可以手动数字模拟也可以使用vector来直接实现

先找出所有的入度为0的点,让其入队列,然后对队列的元素进行操作:取队首赋给now,扔队首,让now所有的后继节点的入度减1,如果减到了0, 就扔进对列,一直进行下去直到对列为空就结束

  • 采用vector来实现

#include <cstdio>
#include <cstring>
#include <string>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <vector>
#include <stack>
#include <queue>
#include <stdlib.h>
#include <sstream>
#include <map>
#include <set>
using  namespace std;
#define inf 0x3f3f3f3f
#define MAX 100000 + 50
#define endl '\n'
#define mod 13331
#define io ios::sync_with_stdio(false); cin.tie(0); cout.tie(0)
#define mem(a,b) memset((a),(b),sizeof(a))
typedef  long long ll ;
//不开longlong见祖宗!
inline int IntRead(){char ch = getchar();int s = 0, w = 1;while(ch < '0' || ch > '9'){if(ch == '-') w = -1;ch = getchar();}while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0';ch = getchar();}return s * w;}


int n, m, a, b, now;
vector<int>in;
vector<vector<int>>ma;//建图

int main(){
    io;
    while (cin>>n>>m && (n + m)) {
        ma = vector<vector<int>>(n + 1);//初始化
        in = vector<int>(n + 1);//初始化
        for(int i = 1; i <= m; ++i){
            cin>>a>>b;
            ma[a].push_back(b);//a->b
            ++in[b];//b的入度加1
        }
        queue<int>q;
        for(int i = 1; i <= n; ++i)if(!in[i])q.push(i);
        while (!q.empty()){
            now = q.front();q.pop();
            cout<<now<<' ';
            for(int i = 0; i < ma[now].size(); ++i){
                in[ma[now][i]]--;
                if(!in[ma[now][i]])q.push(ma[now][i]);
            }
        }
        cout<<endl;
    }
    return 0;
}
  • 采用邻接链表来实现

用一个tr结构体来存边的后继节点和next“指针”(指向下一个边的下标)

in数组来记录点的入度

head数组来记录每个点作为边的前继节点时最后一次出现的边的下标

用一个队列来放入度为0的点的下标,⚠️要注意这里放的是点的下标

有的时候可能需要字典序排序,那我们就可以使用优先队列来代替队列

手动模拟:

现在我来展示一下head数组和next指针的用法:

假如现在有三条边1->2, 2->3, 3->4

输入1->2时,进行建图,这是第一条边,所以tot为1,tr[1].to = 2,表示边1的后继节点为2,tr[1].next = head[1] = – 1(初始化为-1),此时更新head[1] = 1, in[2] = 1(更新入度)

输入2->3时,继续建图,这是第二条边了,所以tot = 2,tr[2].to = 3, tr[2].next = head[1] = 1,这个时候我们发现以1为前继节点的边2 的next值其实是以1为前继节点的第一条边的下标1,更新head[1]= 2,in[3] = 1

输入3->4时,继续建图,这是第三条边了,所以tot = 3,tr[3].to = 4, tr[3].next = head[1] = 2, 同样的,以1为前继节点的边3的next值其实是第二条以1为前继节点的边2的下标,更新head[1] = 3, in[4] = 1

当我们对点1进行去边时,就可以进行循环,我们要利用tr[1].next = -1 作为结束条件,再利用其他的next作为索引,来循环找上一个的以点1为前继节点的边,也就是我们先让i = head[1] 也就是i = 3,就获得了以点1为前继节点的第一个边的索引(这个第一指的是从后往前的顺序),对这个边进行一系列操作后,我们就需要去找以点1为前继节点的下一个边,就可以利用当前的边的next值,我们就让i = tr[i].next , 也就是i = 2,获得了下一个边的下标,这时我们就可以去对边2进行操作,同样的再利用第二个边的next值,得到i = 1,继续对边1进行操作,此时tr[i].next = -1 ,说明再没有边是以1为前继节点了,就可以停止了

总的来说,next记录的是上一个以点u为前继节点的边的下标,我们对边进行操作的时候,是从head[u]开始,对当前边进行操作后,利用next获得下一个边的下标,对下一个边进行操作后,再根据这个边的next来获得再下一个边的下标,一环扣一环,就类似于链表的next指针,所以叫做邻接链表,这里插入的方法类似于链表的头插法!

#include <cstdio>
#include <cstring>
#include <string>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <vector>
#include <stack>
#include <queue>
#include <stdlib.h>
#include <sstream>
#include <map>
#include <set>
using  namespace std;
#define inf 0x3f3f3f3f
#define MAX 100000 + 50
#define endl '\n'
#define mod 13331
#define io ios::sync_with_stdio(false); cin.tie(0); cout.tie(0)
#define mem(a,b) memset((a),(b),sizeof(a))
typedef  long long ll ;
//不开longlong见祖宗!
inline int IntRead(){char ch = getchar();int s = 0, w = 1;while(ch < '0' || ch > '9'){if(ch == '-') w = -1;ch = getchar();}while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0';ch = getchar();}return s * w;}

int n, m, tot, now, a, b;
int head[105];
int in[105];

struct ran{
    int to, next;
}tr[105];

queue<int>q;

void add(int u, int v){
    tr[++tot].to = v;//确定新的边的后继节点
    tr[tot].next = head[u];//确定新的边的上一个边的下标
    head[u] = tot;//跟新head数组的值
    ++in[v];//入度加1
}

void init(){//初始化
    mem(in, 0);
    mem(tr, -1);
    mem(head, -1);
    tot = 0;
}

void topo(){
    for(int i = 1; i <= n; ++i)if(!in[i])q.push(i);
    while (!q.empty()) {
        now = q.front();q.pop();
        cout<<now<<' ';
        for(int i = head[now]; i != -1; i = tr[i].next){
            --in[tr[i].to];
            if(!in[tr[i].to])q.push(tr[i].to);
        }
    }
}

int main(){
    io;
    while(cin>>n>>m && (n + m)){
        init();
        for(int i = 1; i <= m; ++i){
                cin>>a>>b;
                add(a, b);
        }
        topo();
        cout<<endl;
    }
    return 0;
}

 

博客内容均系原创,未经允许严禁转载!
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇