前几天数据结构课上老师给我们留了一道思考题:如何求出拓扑排序的所有可能路径。说实话,自己的第一感觉就是深搜DFS,但是到最后又被自己推翻了,本来周三的时候想到了一个算法,后来又被自己推翻了。在BestCoder群里问了几个大神,他们也没给出什么好的方法,印象深刻的是有人说我问这种题有意义吗,问得我竟然无法反驳,也是,大多数的人总是要为自己的无知做一些辩解。
后来整理了一下自己之前所想的,虽然有很大一个漏洞(这就是我推翻我算法的原因),不过自己所想到的算法自己都用代码实现了,也算是一件成功的事情。
大致算法描述:
按照图的结构给每个结点分一下优先级,设最开始的入度为0的顶点的优先级为1;
然后接下来将这些入度为0的点射出的边删除,此次操作会产生几个新的入度为的顶点,设这几个顶点的优先级为2;
继续删除边生成优先级为3的几个顶点,直到图被遍历完;
最后所有优先级的各顶点全排列就可得到拓扑排序的所有可能路径。
如图:
第1优先级:v0
删除v0射出的三条边,得到的入度为0的点为v1 v2 v3 , 所以第2优先级:v1 v2 v3
删除v1 v2 v3射出的三条边,得到入度为0的点为v4 v5 , 所以第3优先级: v4 v5
删除v4 v5射出的三条边,得到入度为0的点为v6 , v7 , 所以第4优先级:v6 v7
最后第5优先级:v8
可知优先级靠前的顶点的拓扑排序结果一定是在优先级靠后的顶点之前的,而优先级相同的顶点的前后顺序是没有影响的,所以同一优先级的顶点可以进行全排列,最后将每个优先级的全排列结合在一起就可以得到拓扑排序的所有结果。
具体实现:
优先级的信息要用二维数组Rank[i][j]保存,Rank[i]表示的是第i优先级里的顶点;
图的信息用二维数组graph[i][j]保存,graph[i][j]表示的是从顶点i连接出去的第j个顶点;
path[]表示的是拓扑排序的一条路径;
degree[i]表示的是第i个顶点的入度;
vis[i]来表示第i个顶点是否被访问过。
step1 : 先用拓扑排序图中判断是否有环,如果有环输出-1并且结束,无环的话可以进入下一步;
step2 : 每次用队列来保存入度为0的顶点,然后在Rank数组中保存这些顶点后将这些顶点都弹出,并且删除相关联的边;
step3 : 最后保存的优先级信息全存储在Rank数组中,这时候需要用DFS即深度优先搜索来遍历Rank数组,输出所有的全排列结果,这时候需要注意的有两点:
1.每一优先级的全排列用next_permutation()要方便很多;
2.由于DFS是试探性搜索,所以要注意到回溯。
#include <iostream> #include <stdio.h> #include <algorithm> #include <vector> #include <queue> using namespace std; const int maxn = 1005; //默认最大的n的值 vector <int> Rank[maxn] , graph[maxn]; //两个二维数组 int degree[maxn] , n , m , cnt , vis[maxn]; //n是顶点的个数 , m是边的条数 , cnt用来表示有多少个优先级 int path[maxn]; //拓扑排序的路径 bool toposort() //普通的拓扑排序,即判断是否有环 { int i , j , u , v , tot; tot = 0; memset(degree , 0 ,sizeof(degree)); for(i = 1 ; i <= n ; i++) for(j = 0 ; j < graph[i].size() ; j++) degree[ graph[i][j] ]++; queue <int> que; //STL模板实现队列 for(i = 1 ; i <= n ; i++) { if(degree[i] == 0) { que.push(i); tot++; } } while(!que.empty()) { int k = que.front(); que.pop(); for(j = 0 ; j < graph[k].size() ; j++) { int tmp = graph[k][j]; degree[tmp]--; if(degree[tmp] == 0) { que.push(tmp); tot++; } } } if(tot == n) return true; //返回true表示无环 else return false; } void printAll(int n) { //输出一条路径中的结果 for(int i = 0 ; i < n ; i++) printf("%d " , path[i]); puts(""); } void DFS(int i , int &tot) { //深搜,i表示当前为第i优先级,tot表示这时候path[]数组中有多少元素 if(tot >= n && i >= cnt) { //递归的终止条件 printAll(n); //输出当前的一条路径 return; } do { for(int j = 0 ; j < Rank[i].size() ; j++) path[tot++] = Rank[i][j]; //记录当前优先级的排列信息 DFS(i + 1 , tot); //深搜进入下一步 tot -= Rank[i].size(); //回溯,tot回到这一层的初始状态值 } while(next_permutation(Rank[i].begin() , Rank[i].end())); //STL中的全排列函数 } void findAll() { int i , j , tot; memset(degree , 0 ,sizeof(degree)); //初始化degree[]和vis[]都为0 memset(vis , 0 , sizeof(vis)); for(i = 1 ; i <= n ; i++) for(j = 0 ; j < graph[i].size() ; j++) degree[ graph[i][j] ]++; //整理每个顶点的入度信息 cnt = tot = 0; queue <int> que; while(tot < n) { //由于建立每一层的优先级的时候队列都是要清空一次的,所以这里用这个循环结束条件 for(i = 1 ; i <= n ; i++) { if(degree[i] == 0 && !vis[i]) { que.push(i); tot++; vis[i]++; Rank[cnt].push_back(i); } } while(!que.empty()) { int k = que.front(); que.pop(); for(j = 0 ; j < graph[k].size() ; j++) { int tmp = graph[k][j]; degree[tmp]--; } } cnt++; } for(i = 0 ; i < cnt ; i++) sort(Rank[i].begin() , Rank[i].end()); //这里sort是为了在DFS的全排列函数中输出所有 int tmp = 0; DFS(0 , tmp); //i和tot的初始值都是0 } int main() { int i , j , u , v; scanf("%d %d",&n,&m); //输入顶点数和边数 while(m--) { scanf("%d %d",&u,&v); //输入每条边的顶点与终点 graph[u].push_back(v); } if(toposort()) { //无环,可进行拓扑排序 findAll(); } else { //有环,输出-1 cout<<"-1"<<endl; } return 0; }
用老师课件上的图来进行验证:
出于方便,所有顶点都有数字来表示,即1来表示a,2来表示b...所以输入下面一组数据:
(第一行是n和m,表示有n个顶点和m条边,接下来的n行是每条边的顶点与终点)
8 10
1 2
2 3
3 4
3 7
5 2
5 3
5 6
6 7
7 4
7 8
程序的输出结果是:
1 5 2 6 3 7 4 8
1 5 2 6 3 7 8 4
1 5 6 2 3 7 4 8
1 5 6 2 3 7 8 4
5 1 2 6 3 7 4 8
5 1 2 6 3 7 8 4
5 1 6 2 3 7 4 8
5 1 6 2 3 7 8 4
其实这个思路是有漏洞的,因为刚开始的时候如果是两个入度为0的顶点的话那就这两条拓扑排序的路径相互独立了,所以中间还有一些情况无法输出,比如说上图是a和e两个顶点出发的,那么a与f这两点就是相互独立的,f是可以出现a前面的,所以这点很纠结,一直也想不出很好的解决方法,所以就先将这个思路实现了。希望数据结构课上老师能给出很完美的算法。
ACM中关于拓扑排序的题不多,记得做过的拓扑排序的题是BestCoder第一场的A题,杭电的4857,逆向构图+拓扑排序+优先队列,在这里放一下题目的地址与AC代码:
题目来源:http://acm.hdu.edu.cn/showproblem.php?pid=4857
#include <iostream> #include <iomanip> #include <stdio.h> #include <stdlib.h> #include <algorithm> #include <functional> #include <vector> #include <cmath> #include <string> #include <stack> #include <queue> using namespace std; const int maxn=100000+5; vector<int> g[maxn]; int du[maxn],n,m,L[maxn]; void toposort() { memset(du,0,sizeof(du)); for(int i=1;i<=n;i++) for(int j=0;j<g[i].size();j++) du[g[i][j]]++; int tot=0; priority_queue<int> Q; for (int i = 1; i <= n; i++) if (!du[i]) Q.push(i); while (!Q.empty()) { int x=Q.top();; Q.pop(); for (int i = 0; i < g[x].size(); i++) { int t=g[x][i]; du[t]--; if (du[t] == 0) Q.push(t); } L[tot++]=x; } } int main() { int u,v,T,i,j; cin>>T; while(T--) { scanf("%d%d",&n,&m); for(i=1;i<=n;i++) g[i].clear(); while(m--) { scanf("%d%d",&u,&v); g[v].push_back(u); } toposort(); printf("%d",L[n-1]); for(i=n-2;i>=0;i--) printf(" %d",L[i]); printf("\n"); } return 0; }
滕雄
2014-11-30