考研复试系列——第八节 拓扑排序
前言
拓扑排序最适于解决判断一个有向图是不是有向无环图的问题。在考研机试中也是经常出现的,但是题目又各不相同,只要抓住问题的根本,
即拓扑排序判断有向无环图的本质就可以以不变应万变。本节依然和前面依然,侧重于说明算法的编程实现。
拓扑排序——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;
}