【算法总结】图论-拓扑排序
一、概念
设有一个有向无环图(DAG 图),对其进行拓扑排序即求其中结点的一个拓扑序列,对于所有的有向边(U,V)(由U指向V),在该序列中结点U都排列在结点 V 之前。满足该要求的结点序列,被称为满足拓扑次序的序列。求这个序列的过程,被称为拓扑排序。
由满足拓扑次序序列的特征我们也能得出其如下特点:若结点U经过若干条有向边后能够到达结点V,则在求得的序列中U必排在V之前。 在了解了拓扑次序的定义以后,我们就知道了前文中为什么将拓扑排序限定在一个有向无环图上。若图无向,则边的两个顶点等价,不存在先后关系;若图为有向图,但存在一个环路,则该环中所有结点无法判定先后关系(任意结点间都能通过若干条有向边到达)。
二、拓扑排序的方法
首先,所有有入度(即以该结点为弧头的弧的个数)的结点均不可能排在第一个。那么,我们选择一个入度为0的结点,作为序列的第一个结点。当该结点被选为序列的第一个顶点后,我们将该点从图中删去,同时删去以该结点为弧尾的所有边,得到一个新图。那么这个新图的拓扑序列即为原图的拓扑序列中除去第一个结点后剩余的序列。
同样的,我们在新图上选择一个入度为0的结点,将其作为原图的第二个结点,并在新图中删去该点以及以该点为弧尾的边。这样我们又得到了一张新图,重复同样的方法,直到所有的结点和边都从原图中删去。 若在所有结点尚未被删去时即出现了找不到入度为0的结点的情况,则说明剩余的结点形成一个环路,拓扑排序失败,原图不存在拓扑序列。
例 5.7 Legal or Not
AC代码
#include<cstdio> #include<vector> #include<queue> using namespace std; vector<int>edge[501];//邻接链表,因为边不存在权值,只需保存与其邻接的结点编号即可 queue<int> Q;//保存入度为0的结点的队列 int main() { int inDegree[501];//统计每个结点的入度 int n, m; while (scanf("%d%d", &n, &m) != EOF) { if (n == 0 && m == 0)break; for (int i = 0; i < n; i++)//初始化所有结点,此题结点编号由0到n-1 { inDegree[i] = 0;//入度信息初始化为0 edge[i].clear();//清空邻接链表 } while (m--) { int a, b; scanf("%d%d", &a, &b);//读入一条由a指向b的有向边 inDegree[b]++;//又出现了一条弧头指向b的边,累加结点b的入度 edge[a].push_back(b);//将b加入a的邻接链表 } while (Q.empty() == false)Q.pop();//初始化,清空队列元素 for (int i = 0; i < n; i++)//统计所有结点的入度 { if (inDegree[i] == 0)Q.push(i);//将入度为0的结点入队 } int cnt = 0;//计数器,初始值为0,用来记录已经确定拓扑序列的节点个数 while (Q.empty() == false)//队列中还有入度为0的结点就继续 { int nowP = Q.front();//读出队头结点编号 Q.pop(); cnt++;//被确定的结点个数加一 for (int i = 0; i < edge[nowP].size(); i++)//将该结点以及以其为弧尾的所有边去除 { inDegree[edge[nowP][i]]--;//去除某边后,该边所指后继结点的入度减一 if (inDegree[edge[nowP][i]] == 0)Q.push(edge[nowP][i]); } } if (cnt == n)puts("YES"); else puts("NO"); } return 0; }