拓扑排序
定义:将有向图中的顶点以线性方式进行排序。即对于任何连接自顶点u到顶点v的有向边uv,在最后的排序结果中,顶点u总是在顶点v的前面。
前提条件: 必须是有向无环图(可以不连通)
偏序/全序关系:
所谓偏序,就是图中没有环(自己出发不能回到自己)
所谓全序,就是在偏序的基础之上,有向无环图中的任意一对顶点还需要有明确的关系(有唯一拓扑序)(反映在图中,就是单向连通的关系,注意不能双向连通,那就成环了)。
题目:http://acm.hdu.edu.cn/showproblem.php?pid=1285
算法—:Kahn算法(可加第二关键字)
该算法的实现十分直观,关键在于需要维护一个入度为0的顶点的集合:
每次从该集合中取出(没有特殊的取出规则)一个顶点,将该顶点放入保存结果的List中。
紧接着循环遍历由该顶点引出的所有边,从图中移除这条边,同时获取该边的另外一个顶点,如果该顶点的入度在减去本条边之后为0,那么也将这个顶点放到入度为0的集合中。然后继续从集合中取出一个顶点直到集合为空。
当集合为空之后,检查图中是否还存在任何边,如果存在的话,说明图中至少存在一条环路。不存在的话则返回结果List,此List中的顺序就是对图进行拓扑排序的结果。
/**************************************************************************************************** 拓扑排序 Kahn算法 ********************************************************************************************************/ #include<cstdio> #include<queue> #include<cstring> #include<vector> using namespace std; priority_queue<int>a; vector<int>v[505]; int size[505],list[505],top=0;//size为每个点入度,list为结果数组 int main(){ int n,m,x,y; while(scanf("%d%d",&n,&m)!=EOF){ top=0; memset(size,0,sizeof(size)); while(!a.empty())a.pop(); for(int i=0;i<m;i++){ scanf("%d%d",&x,&y); v[x].push_back(y); size[y]++; } for(int i=1;i<=n;i++){//将所有入度为0的顶点加入队列 if(!size[i])a.push(-i); } while(!a.empty()){ int u=-a.top(); a.pop(); list[top++]=u; for(int i=0;i<v[u].size();i++){//u的每个邻接点v size[v[u][i]]--;//删除边(u, v); if(!size[v[u][i]])a.push(-v[u][i]);//如果删除边后v入度为0,则将v加入队列 } v[u].clear(); } bool d=0; for(int i=1;i<=n;i++){//如果图中还有边存在则存在环 if(!v[i].empty())v[i].clear(),d=1; } if(d)printf("wrong\n"); else{ for(int i=0;i<top-1;i++){ printf("%d ",list[i]); } printf("%d",list[top-1]); printf("\n"); } } return 0; }
二.基于bfs的拓扑排序(不可加第二关键字)
对于每个节点进行一遍dfs,dfs到的节点都在当前节点前面,没有遍历到的都与当前节点无关
http://acm.hdu.edu.cn/showproblem.php?pid=3342
/**************************************************************************************************** 拓扑排序 基于DFS的拓扑排序 ********************************************************************************************************/ #include<cstdio> #include<queue> #include<cstring> #include<vector> using namespace std; vector<int>g[505]; int vis[505],list[505],top=0;//vis表示节点访问状态,list为结果数组 bool dfs(int u) { vis[u] = -1;//-1用来表示顶点u正在访问 for(int i = 0 ; i < g[u].size() ; i ++) { if(vis[g[u][i]] == -1)//表示这个点进入了两次,肯定出现了环 return false; else if(vis[g[u][i]] == 0) { if(!dfs(g[u][i]))return false; } } vis[u] = 1; list[top++] = u;//放到结果数组里,输出的时候记得倒序输出,(回溯的原因) return true; } int main(){ int n,m,x,y; while(scanf("%d%d",&n,&m)!=EOF&&(n||m)){ bool d=0; memset(vis,0,sizeof(vis)); for(int i=0;i<n;i++)g[i].clear(); top=0; for(int i=0;i<m;i++){ scanf("%d%d",&x,&y); g[x].push_back(y); } for(int i=0;i<n;i++){ if(!vis[i])if(!dfs(i))d=1; } if(d)printf("NO\n"); else printf("YES\n"); } return 0; }