考研复试系列——第八节 拓扑排序
前言
拓扑排序最适于解决判断一个有向图是不是有向无环图的问题。在考研机试中也是经常出现的,但是题目又各不相同,只要抓住问题的根本,
即拓扑排序判断有向无环图的本质就可以以不变应万变。本节依然和前面依然,侧重于说明算法的编程实现。
拓扑排序——Kahn
看了网上其他的资料大多是以图的变化讲解拓扑排序,并没有说明算法实现的过程,所以下面以表格的形式来说明算法的过程以及如何把表格的
转换过程实现为具体的算法。
给出这样一个图,我们知道它的拓扑排序序列是1 2 3 4 或者 1 3 2 4 。但是在算法实现中具体该如何实现这个过程呢?
我们先使用自然语言藐视整个过程。首先建立这样一个表格:
开始时节点的入度初始化为0,后继节点也是空。
现在假设输入一条边 1—>2 (边的先后输入顺序不影响的),OK 开始更新这个表格,输入边是1到2,所以2的入度加1,1的后继节点加入节点2,此时表格如下:
然后依次其他的边,最终表格如下:
然后我们就开始基于这个表格进行处理。然后我们扫描一遍入度,如果入度为0,说明对应节点没有前驱,那就把它加入到一个队列中(其实用栈什么的都可以):
OK,接下来取队列中的队头元素,也就是节点1 ,并将其出队列,然后查表它的后继节点是2和3 ,于是将2和3的入度都减去1 ,并判断节点2和3的入度是否是0,若是0则加入队列(显然因为节点2和3的入度发生了改变,所以我们只需要判断节点2和3的入度就可以了)此时表格以及队列为:
然后采取和前面一样的策略,从队列中弹出队头元素即节点2 ,查表其后继为节点4,将节点4的入度减去1,判断是否为0,不为0不做操作,继续从队列中取出
节点3,查表其后继为节点4,将节点4的入度减去1,判断是否为0,这是4的入度为0了,于是将节点4加入队列。
然后从队列中取队头元素即节点4,查表没有后继了,然后发现队列是为空了,于是算法结束。
下面我们来编程实现:思考一下,入度我们可以使用一个数组来记录,后继节点我们可以使用链表,为了方便我们使用vector数组来替代链表。队列直接使用
STL中的queue就好了。
#include<iostream> #include<vector> #include<queue> #include<string> using namespace std; vector<int> next[101];//保存某节点的后继 queue<int> Q;//队列 int inNode[101];//记录节点的入度 int main() { int n,m;//n个顶点,m条边 while(cin>>n>>m && n && m) { int i; for(i=1;i<=n;i++)//下标从1开始 { inNode[i] = 0;//初始化 next[i].clear();//清空 } while(!Q.empty())//清空 Q.pop(); for(i=1;i<=m;i++)//输入边信息 { int a,b; cin>>a>>b; inNode[b]++; next[a].push_back(b); } int cnt = 0;//用于累加已经确定拓扑序列的结点个数 for(i=1;i<=n;i++)//将入度为0的节点加入队列 { if(inNode[i] == 0) Q.push(i); } while(!Q.empty())//队列非空则重复执行 { //取队头节点并弹出 int now = Q.front(); Q.pop(); cnt++;//确定一个节点总数加1 for(i=0;i<next[now].size();i++)//遍历处理后继 { inNode[next[now][i]]--;//入度减1 if(inNode[next[now][i]] == 0)//若节点入度变为0 { Q.push(next[now][i]);//将其加入队列 } } } if(cnt == n) cout<<"这是一个有向无环图!"<<endl; else cout<<"这不是一个有向无环图!"<<endl; } return 0; }