拓扑排序初学

拓扑排序初学

拓扑排序:让所有的有向边全都从左指向右,同时将所有顶点排列在一条水平线上(可能有很多种排序顺序,不一定唯一)

1.简单理解
个人理解:可以理解为扒皮,先扒最外层,也就是h[]=0,然后与之相连的点都会h–,
即往外一层,以此类推就会全部扒完。如果存在环的话,就没有拓扑排序,可以通过比较ans.size()和n的关系来判断是否能形成拓扑排序

这是我最常用的板子,感觉是最好理解的
注意多组输入的时候一定要把edge数组清空,还有ans等等

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<string>
#include<vector>
#include<stack>
#include<bitset>
#include<cstdlib>
#include<cmath>
#include<set>
#include<list>
#include<deque>
#include<queue>
#define ll long long
#define pb push_back
using namespace std;
const int maxn=100009;
int h[maxn];
vector<int > edge[maxn];//用来表示连接情况
queue<int> q;
int main()
{
    int n,m,a,b;
    cin>>n>>m;
    for (int i=0; i<m; i++)
    {
        cin>>a>>b;
        edge[a].pb(b);
        h[b]++;
    }
    for (int i=0; i<n; i++)
    {
        if (h[i]==0)
            q.push(i);
    }
    vector<int > ans;
    while(!q.empty())
    {
        int p=q.front();
        q.pop();
        ans.pb(p);
        for (int i=0; i<edge[p].size(); i++)
        {
            int y=edge[p][i];
            h[y]--;
            if (h[y]==0)
                q.push(y);
        }
    }
    for (int i=0; i<ans.size(); i++)
        cout<<ans[i]<<endl;
}

2.拓展应用
例1:HDU 1285 http://acm.hdu.edu.cn/showproblem.php?pid=1285
拓扑排序+优先队列维护

题目大意:一共N个队伍,给出M对数P1,P2,表示P1会战胜P2,最后输出拓扑排序,如果有很多组情况,就尽量把队伍小的放前面

那么就很显然直接把队列改成优先队列(小的优先)就可以了,这样每次取下一层的时候都会优先选择队伍小的了

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<string>
#include<vector>
#include<stack>
#include<bitset>
#include<cstdlib>
#include<cmath>
#include<set>
#include<list>
#include<deque>
#include<queue>
#define ll long long
#define pb push_back
using namespace std;
const int maxn=100009;
int h[maxn];
vector<int > edge[maxn];
priority_queue<int,vector<int >,greater<int> > q;
vector<int > ans;
int main()
{
    int n,m,a,b;
    while(~scanf("%d%d",&n,&m))
    {
        for (int i=1; i<=n; i++)
        {
            edge[i].clear();
            h[i]=0;
        }
        ans.clear();
        while(!q.empty())
        {
            q.pop();
        }
        for (int i=0; i<m; i++)
        {
            scanf("%d%d",&a,&b);
            edge[a].pb(b);
            h[b]++;
        }
        for (int i=1; i<=n; i++)
        {
            if (h[i]==0)
                q.push(i);
        }
        while(!q.empty())
        {
            int p=q.top();
            q.pop();
            ans.pb(p);
            for (int i=0; i<edge[p].size(); i++)
            {
                int y=edge[p][i];
                h[y]--;
                if (h[y]==0)
                    q.push(y);
            }
        }
        //cout<<ans.size()<<endl;
        for (int i=0; i<ans.size(); i++)
        {
            cout<<ans[i];
            if (i!=ans.size()-1)
                cout<<" ";
        }
        cout<<endl;
    }
}

例2:HDU 4857 http://acm.hdu.edu.cn/showproblem.php?pid=4857
反向建图+拓扑排序+优先队列

题目大意:给出M个条件P1,P2,要求P1比P2更早逃生,同时要保证编号小的尽量靠前(1的位置在前,如果1的位置相同,2的位置在前,以此类推),即如果6-1-2-3-4-5和2-3-1-4-5-6这两个排序优先选择前者

坑点:假如我现在给出的数据是5优先于4优先于2,6优先于1优先于3 。如果正向思路去想,肯定是用一个优先队列(小的优先)去维护,但是发现维护到最后的顺序是5-4-2-6-1-3,而更优的则是6-1-5-4-2-3 即说明一个问题,正向建图会忽视后面的数字。

换个角度想,如果小的要尽量在前,那么大的肯定尽量在后面,如果我反向建图,并求一个反向的拓扑序的话,肯定是符合条件的。(虽然不会证明

/*为什么会出现这个结果,我迷迷糊糊口胡两句,
如果从正向建图优先队列找到了当前最外层最小的数字,
但是这个数字比如说是4,我还是要去尽量保证1或者2或者3是最前的,
所以相当于和大局没有任何关系,白费功夫。*/
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<string>
#include<vector>
#include<stack>
#include<bitset>
#include<cstdlib>
#include<cmath>
#include<set>
#include<list>
#include<deque>
#include<queue>
#define ll long long
#define pb push_back
using namespace std;
const int maxn=100009;
int h[maxn];
vector<int > edge[maxn];
priority_queue<int> q;
vector<int > ans;
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        int n,m,a,b;
        scanf("%d%d",&n,&m);
        for (int i=0; i<n; i++)
        {
            edge[i].clear();
            h[i]=0;
        }
        ans.clear();
        while(!q.empty())
        {
            q.pop();
        }
        for (int i=0; i<m; i++)
        {
            scanf("%d%d",&a,&b);
            a--;
            b--;
            edge[b].pb(a);
            h[a]++;
        }
        for (int i=0; i<n; i++)
        {
            if (h[i]==0)
                q.push(i);
        }
        while(!q.empty())
        {
            int p=q.top();
            q.pop();
            ans.pb(p);
            for (int i=0; i<edge[p].size(); i++)
            {
                int y=edge[p][i];
                h[y]--;
                if (h[y]==0)
                    q.push(y);
            }
        }
        //cout<<ans.size()<<endl;
        for (int i=ans.size()-1; i>=0; i--)
        {
            cout<<ans[i]+1;
            if (i!=0)cout<<" ";
        }
        cout<<endl;
    }
    return 0;
}

例3:HDU 2647 http://acm.hdu.edu.cn/showproblem.php?pid=2647

题目大意:发工资每人最少888,一共N个人,给出M组条件,要求a比b工资高,求最少工资

(这题其实不用拓扑序也可以做。。
需要求每一个人的add[i],表示比888多多少钱
在这里插入图片描述
比如说一共五个人,会出现这种情况,需要考虑到

我们可以看出,一个人的add[x]是和他相连的所有人的(add[y]+1)取max,不难理解

那么我们就跑一遍拓扑排序,在找邻边的过程中不断修改add[]值就可以了

#include<stdio.h>
#include<cstring>
#include<iostream>
#include<queue>
#include<algorithm>
#include<set>
#include<stdlib.h>
#define ll long long
#define pb push_back
using namespace std;
const int maxn=10009;
vector<int >E[maxn];
vector<int >ans;
int h[maxn];
int add[maxn];
queue<int > q;
int main()
{
    int n,m,a,b;
    while(~scanf("%d%d",&n,&m))
    {
        for (int i=1; i<=n; i++)
        {
            h[i]=0;
            E[i].clear();
        }
        while(!q.empty()){q.pop();}
        ans.clear();
        memset(add,0,sizeof add);

        for (int i=0; i<m; i++)
        {
            scanf("%d%d",&a,&b);
            E[b].pb(a);
            h[a]++;
        }
        for (int i=1; i<=n; i++)
        {
            if (h[i]==0)
                q.push(i);
        }
        while(!q.empty())
        {
            int now=q.front();q.pop();
            ans.pb(now);
            for (int i=0; i<E[now].size(); i++)
            {
                int y=E[now][i];
                h[y]--;
                if (h[y]==0)
                {
                    add[y]=max(add[y],add[now]+1);
                    q.push(y);
                }
            }
        }
        if (ans.size()!=n)
            cout<<-1<<endl;
        else
        {
            int cnt=0;
            for (int i=1; i<=n; i++)
                cnt+=add[i];
            cout<<cnt+n*888<<endl;
        }
    }
}
/*5 5
5 1
3 1
4 3
2 4
2 5*/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,让我们先来了解一下什么是拓扑排序和关键路径。 拓扑排序是对有向无环图(DAG)进行排序的一种方法。它可以将一个DAG的顶点排成一条线性序列,使得对于任何一条有向边 (u, v),顶点 u 在序列中都排在顶点 v 的前面。 关键路径是指在一个有向无环图中,从起点到终点的所有路径中,耗时最长的那条路径。在实际应用中,关键路径可以用来确定项目的最短工期,以及哪些任务是关键任务,不能延误。 下面是用C语言实现拓扑排序和关键路径的代码,注释中有详细的解释。 ```c #include <stdio.h> #include <stdlib.h> #define MAX_VERTEX_NUM 100 // 图中最大顶点数 #define MAX_EDGE_NUM 100 // 图中最大边数 // 边的结构体,包含起点和终点 typedef struct { int from; // 起点 int to; // 终点 } Edge; // 顶点的结构体,包含入度和出度 typedef struct { int in; // 入度 int out; // 出度 } Vertex; // 图的结构体,包含顶点数组、边数组、顶点数和边数 typedef struct { Vertex vertices[MAX_VERTEX_NUM]; // 顶点数组 Edge edges[MAX_EDGE_NUM]; // 边数组 int vertex_num; // 顶点数 int edge_num; // 边数 } Graph; // 始化图 void init_graph(Graph *g) { int i; g->vertex_num = 0; g->edge_num = 0; for (i = 0; i < MAX_VERTEX_NUM; i++) { g->vertices[i].in = 0; g->vertices[i].out = 0; } } // 添加边 void add_edge(Graph *g, int from, int to) { g->edges[g->edge_num].from = from; g->edges[g->edge_num].to = to; g->edge_num++; g->vertices[from].out++; // 起点出度加1 g->vertices[to].in++; // 终点入度加1 } // 拓扑排序 void topological_sort(Graph *g) { int i, j, k, n; int queue[MAX_VERTEX_NUM]; // 存储入度为0的顶点 int head = 0, tail = 0; // 队列头和尾 int count = 0; // 已排序的顶点数 Edge *e; // 将入度为0的顶点加入队列 for (i = 0; i < g->vertex_num; i++) { if (g->vertices[i].in == 0) { queue[tail++] = i; } } // 循环直到队列为空 while (head != tail) { n = tail - head; // 当前队列中的顶点数 for (i = 0; i < n; i++) { j = queue[head++]; // 取出队列头 printf("%d ", j); // 输出已排序的顶点 count++; // 已排序的顶点数加1 for (k = 0; k < g->edge_num; k++) { e = &g->edges[k]; if (e->from == j) { // 找到以j为起点的边 g->vertices[e->to].in--; // 对应终点的入度减1 if (g->vertices[e->to].in == 0) { // 如果终点入度为0,则加入队列 queue[tail++] = e->to; } } } } } if (count < g->vertex_num) { // 如果已排序的顶点数小于总顶点数,则存在环路 printf("The graph has a cycle\n"); } } // 计算关键路径 void critical_path(Graph *g) { int i, j, k, m = 0, n = 0; int earliest[MAX_VERTEX_NUM] = {0}; // 存储最早开始时间 int latest[MAX_VERTEX_NUM] = {0}; // 存储最晚开始时间 Edge *e; // 计算最早开始时间 for (i = 0; i < g->vertex_num; i++) { for (j = 0; j < g->edge_num; j++) { e = &g->edges[j]; if (e->from == i) { // 找到以i为起点的边 if (earliest[e->to] < earliest[i] + 1) { // 更新终点的最早开始时间 earliest[e->to] = earliest[i] + 1; } } } } // 计算最晚开始时间 for (i = g->vertex_num - 1; i >= 0; i--) { latest[i] = earliest[g->vertex_num - 1]; // 先始化为总工期 for (j = 0; j < g->edge_num; j++) { e = &g->edges[j]; if (e->from == i) { // 找到以i为起点的边 if (latest[i] > latest[e->to] - 1) { // 更新起点的最晚开始时间 latest[i] = latest[e->to] - 1; } } } } printf("The critical path is: "); for (i = 0; i < g->edge_num; i++) { e = &g->edges[i]; if (earliest[e->to] - earliest[e->from] == latest[e->to] - latest[e->from]) { // 如果边的最早开始时间和最晚开始时间相等,则为关键边 printf("(%d,%d) ", e->from, e->to); if (earliest[e->to] > m) { // 找到最大的最早开始时间 m = earliest[e->to]; } if (latest[e->from] < n) { // 找到最小的最晚开始时间 n = latest[e->from]; } } } printf("\nThe minimum time to finish the project is %d\n", m - n); } int main() { Graph g; init_graph(&g); g.vertex_num = 7; // 设置顶点数 add_edge(&g, 0, 1); add_edge(&g, 0, 2); add_edge(&g, 1, 3); add_edge(&g, 1, 4); add_edge(&g, 2, 3); add_edge(&g, 2, 5); add_edge(&g, 3, 6); add_edge(&g, 4, 6); add_edge(&g, 5, 6); topological_sort(&g); critical_path(&g); return 0; } ``` 上述代码中,我们定义了一个Graph的结构体来表示图,包含顶点数组、边数组、顶点数和边数。同时定义了一个Vertex的结构体来表示顶点,包含入度和出度。定义了一个Edge的结构体来表示边,包含起点和终点。然后分别实现了始化图、添加边、拓扑排序和计算关键路径的函数。 在main函数中,我们先始化图,然后添加边,设置顶点数为7。然后调用拓扑排序和计算关键路径的函数来输出结果。 这段代码可能对于数据结构初学者来说有些难度,但是只要认真看注释,理解了拓扑排序和关键路径的原理,就能够理解代码的实现过程。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值