拓扑排序基础 POJ1094 HDU2647 UVA10305

本文深入讲解拓扑排序算法,探讨其原理与应用,通过三个实例(任务调度、奖励分配、字符排序)展示如何解决实际问题,揭示算法在解决依赖关系问题上的强大能力。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

拓扑排序简介:

利用一定数量偏序 求出一个序列的全序

拓扑排序基本知识

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

该算法的几个结论:

1.若在判断局部时发现存在环,那么全序一定也包含这个环,这个图最终不为空,不存在拓扑排序。

2.若对每条边都进行一次topo排序 (例题3)那么当且仅当每次topo排序都不出现 多个无前驱点时,序列是唯一确定的。
后者是因为: 若同时出现多个无前驱点 那么无前驱点之间位置不确定,在满足各自的约束条件的前提下,可以进行全排序。
例如 A<B B<D C<D 中 A C无前驱 那么全序可以有
ABCD、ACBD、CABD这三种

Eg1:UVA 10305

John has n tasks to do. Unfortunately, the tasks are not independent and the execution of one task is
only possible if other tasks have already been executed.
Input
The input will consist of several instances of the problem. Each instance begins with a line containing
two integers, 1 ≤ n ≤ 100 and m. n is the number of tasks (numbered from 1 to n) and m is the
number of direct precedence relations between tasks. After this, there will be m lines with two integers
i and j, representing the fact that task i must be executed before task j.
An instance with n = m = 0 will finish the input.
Output
For each instance, print a line with n integers representing the tasks in a possible order of execution.
Sample Input
5 4
1 2
2 3
1 3
1 5
0 0
Sample Output
1 4 2 5 3

题解

算法的步骤很清晰:
1.找到所有无前驱点
2.删除该点极其延伸出来的边(指向的点入度-1)
3.重复1.2 直至图为空 或 不再存在入度=0的点
代码实现则可以用bfs
1.遍历点 找到所有无前驱点 入队列
2.队顶出队到最终序列ans中, bfs一下 ,将队顶连接的点的入度递减1 若入度变成0 则进队。
3.重复1.2 直到队空
若此时ans序列中元素个数不等于总个数 则说明有部分元素没有入队
也就是说最终有部分元素入度不为0 说明存在环
否则则序列可以确定(但可能不唯一)

#include<iostream>
#include<cstdio>
#include<vector>
#include<iterator>
#include<set>
#include<map>
#include<algorithm>
#include<queue>
#include<cstring>
#include<stack>
#include<cmath>
#include<cctype>
#include<string>
using namespace std;

#define INF 0x7fffffff
#define EPS 1e-12
#define MOD 1000000007
#define PI 3.141592653579798

typedef long long LL;
typedef double DB;

int n,m;

int main()
{

    while(~scanf("%d%d",&n,&m) && (n||m))
    {
        int in[105];   // 存放入度
        queue<int> q;  // 入度为0先进先出
        vector<int> ans; // 存放答案排序
        vector<int> edge[105];  //存放点的对应关系
        memset(in,0,sizeof(in));

        for(int i = 0;i<m;i++)
        {
            int a,b;
            scanf("%d%d",&a,&b);
            edge[a].push_back(b);
            in[b]++;
        }
        for(int i = 1;i<=n;i++)
            if(in[i]==0) q.push(i);  //入度为0的进队

        while(q.size())
        {
            int t = q.front(); q.pop();  //队首出队
            ans.push_back(t);            //存入ans
            for(int i =0;i<edge[t].size();i++) //t映射的元素全部入度-1
            {
                int k = edge[t][i];
                in[k]--;
                if(in[k]==0)  q.push(k);   //若入度-1后 该元素变成入度为0的点  则进队
            }
        }
        if(ans.size()==n)
            for(int i = 0;i<n;i++)
            {
                if(i==n-1) cout << ans[i] << endl;
                else cout << ans[i] <<" ";
            }
        else {cout <<"no answer" << endl;}   //若最终入度为0的点个数不等于总个数 则说明必存在环
    }

    return 0;

}

Eg2:HDU 2647

Dandelion’s uncle is a boss of a factory. As the spring festival is coming , he wants to distribute rewards to his workers. Now he has a trouble about how to distribute the rewards.
The workers will compare their rewards ,and some one may have demands of the distributing of rewards ,just like a’s reward should more than b’s.Dandelion’s unclue wants to fulfill all the demands, of course ,he wants to use the least money.Every work’s reward will be at least 888 , because it’s a lucky number.
Input
One line with two integers n and m ,stands for the number of works and the number of demands .(n<=10000,m<=20000)
then m lines ,each line contains two integers a and b ,stands for a’s reward should be more than b’s.
Output
For every case ,print the least money dandelion ‘s uncle needs to distribute .If it’s impossible to fulfill all the works’ demands ,print -1.
Sample Input
2 1
1 2
2 2
1 2
2 1
Sample Output
1777
-1

题解

题目大意: 给定n个人 m个比较关系
给每个人发奖金 奖金最少888 且都是整数 在满足m个比较关系的情况下
输出奖金最小值 无解则输出-1
例如: 2个人 第一人多于第二人
那么给第一人889 第二人888 总和1777 就是最优解。

思路:
由于无法直接确定最多的奖金是多少 所以以 A>B、C …去建立边的关系无意义。
可以反着来 以A < B、C…这样去建立边的关系
建立一个vector 数组 edge 下标存放的是比其他人拿得到的人的编号,元素存放的是比这个人拿得多的人的编号。
例如 1<2 1<3 那么 edge[1][0] 就是2 edge[1][1] = 3
如此一来
1.只要找到那些入度为0的人 说明没有规定这个人比其他人多拿
那么就给这个人最少的奖金888
2.按照题1算法 入度为0进队…
3.对于A<B 这样的边的关系 都统一将 B的奖金设为 A的奖金+1;
该方法能保证A<B的约束条件 但对于先前的约束条件 C<B
能否保证 A的奖金+1 仍 =B > C?

答案是肯定的
因为先进队的人 奖金一定比后入队的少或者相等
而我们在处理边的关系时 是队顶元素先处理
这里不妨设队顶元素就是C 处理C时 B = C+1; 然后 C出队
A紧跟其后 处理A时 B = A+1 ; 而A>=C 所以 处理后的B 仍能满足 B>C的约束条件。
所以此算法可行

#include<iostream>
#include<cstdio>
#include<vector>
#include<iterator>
#include<set>
#include<map>
#include<algorithm>
#include<queue>
#include<cstring>
#include<stack>
#include<cmath>
#include<cctype>
#include<string>
using namespace std;

#define INF 0x7fffffff
#define EPS 1e-12
#define MOD 1000000007
#define PI 3.141592653579798

typedef long long LL;
typedef double DB;

int n,m;

int main()
{
//    freopen("in.txt","r",stdin);
 //   freopen("out.txt","w",stdout);
    while(~scanf("%d%d",&n,&m) && n)
    {
        vector<int> edge[10005];
        int in[10005];  memset(in,0,sizeof(in));
        queue<int> q;
        int mon[10005]; memset(in,0,sizeof(in));

        for(int i = 0;i<m;i++)
        {
            int a,b; scanf("%d%d",&a,&b); // a>b  视为 a<-b b指向a
            in[a]++;  // a入度++
            edge[b].push_back(a);
        }
        for(int i=1;i<=n;i++)
        {
            if(in[i]==0)   //对于入度为0的点 代表没有指令表明该点>其他点
            {
                mon[i] = 888; //设为最少 888
                q.push(i);  //入度=0 进队
            }
        }
        while(q.size())
        {
            int x = q.front(); q.pop();
            for(int i=0;i<edge[x].size();i++)
            {
                int y = edge[x][i];
                mon[y]= mon[x]+1;
                in[y]--;
                if(in[y]==0) q.push(y);
            }
        }
        int is_dag = 1;
        for(int i = 1;i<=n;i++)  if(in[i]) is_dag = 0;
        if(is_dag)
        {
            int ans = 0; for(int i = 1;i<=n;i++) ans+=mon[i];
            cout <<ans<<endl;
        }
        else cout << -1 <<endl;

    }
    return 0;
}

Eg3:POJ 1094

An ascending sorted sequence of distinct values is one in which some form of a less-than operator is used to order the elements from smallest to largest. For example, the sorted sequence A, B, C, D implies that A < B, B < C and C < D. in this problem, we will give you a set of relations of the form A < B and ask you to determine whether a sorted order has been specified or not.

Input
Input consists of multiple problem instances. Each instance starts with a line containing two positive integers n and m. the first value indicated the number of objects to sort, where 2 <= n <= 26. The objects to be sorted will be the first n characters of the uppercase alphabet. The second value m indicates the number of relations of the form A < B which will be given in this problem instance. Next will be m lines, each containing one such relation consisting of three characters: an uppercase letter, the character “<” and a second uppercase letter. No letter will be outside the range of the first n letters of the alphabet. Values of n = m = 0 indicate end of input.
Output
For each problem instance, output consists of one line. This line should be one of the following three:

Sorted sequence determined after xxx relations: yyy…y.
Sorted sequence cannot be determined.
Inconsistency found after xxx relations.

where xxx is the number of relations processed at the time either a sorted sequence is determined or an inconsistency is found, whichever comes first, and yyy…y is the sorted, ascending sequence.
Sample Input
4 6
A<B
A<C
B<C
C<D
B<D
A<B
3 2
A<B
B<A
26 1
A<Z
0 0
Sample Output
Sorted sequence determined after 4 relations: ABCD.
Inconsistency found after 2 relations.
Sorted sequence cannot be determined.

题解

因为要求给出根据几个关系判断结果。
所以此时必须对于每个关系进行一次拓扑排序。
根据给出的结果(环、多解、唯一解) 去输出
此时推荐用一个函数去代替topo过程 用返回值提供结果信息

#include<iostream>
#include<cstdio>
#include<vector>
#include<iterator>
#include<set>
#include<map>
#include<algorithm>
#include<queue>
#include<cstring>
#include<stack>
#include<cmath>
#include<cctype>
#include<string>
using namespace std;

#define INF 0x7fffffff
#define EPS 1e-12
#define MOD 1000000007
#define PI 3.141592653579798

typedef long long LL;
typedef double DB;

int n,m;
int in[30];
vector<int> edge[30];
vector<int> ans;

int topo(int n)
{
    ans.clear();
    queue<int> q;
    int temp_in[30]; memcpy(temp_in,in,sizeof(in));  //使用的是in的副本
    int is_mul = 0 ;

    for(int i = 0;i<n;i++)
    {
        if(temp_in[i] == 0) q.push(i);
    }
    while(!q.empty())
    {
        if(q.size()>1) is_mul = 1;  // 在topo排序中 找到存在两个入度同时为0的节点 标记
        int t  = q.front(); q.pop();
        ans.push_back(t);
        for(int i = 0;i<edge[t].size();i++)
        {
            int temp = edge[t][i];
            if(--temp_in[temp] ==0 ) q.push(temp);
        }
    }

    if(ans.size() !=n)  return 0;  //有环  此时全序必定也有环
    else if(ans.size() == n && is_mul)   return 1;  // 多解  此时全序不一定多解 需再度判断
    else return -1;  //唯一解   此时全序已经确定下来


}

int main()
{
//    freopen("in.txt","r",stdin);
  //  freopen("out.txt","w",stdout);
    while(~scanf("%d%d",&n,&m) && n)
    {
        memset(in,0,sizeof(in));
        for(int i = 0;i<n;i++) edge[i].clear();


        string s; int k = 1;
        int is_circle = 0 , is_only = 0,step = 0;
        for(int i = 1;i<=m;i++)
        {
            cin>>s;
            if(is_circle==0&&is_only==0)  // 若其中一个判断出来 则全序就确定下来了  无需继续判断
            {
                int a = s[0] - 'A' ,b = s[2] - 'A';
                edge[a].push_back(b);
                in[b]++;
                k = topo(n);
                if(k==0)
                {
                    is_circle = 1; step = i;
                }
                else if(k==-1)
                {
                    is_only = 1; step = i;
                }
                else ;
            }
        }
        if(is_circle) printf("Inconsistency found after %d relations.\n", step);
        else if(is_only)
        {
            printf("Sorted sequence determined after %d relations: ", step);
            for(auto i:ans) printf("%c",(i+'A')); cout << '.' << endl;
        }
        else printf("Sorted sequence cannot be determined.\n");

    }
    return 0;
}





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值