图的拓扑排序——卡恩算法
拓扑排序
有向图的拓扑排序是其顶点的线性排序,使得对于从顶点u 到顶点v 的每个有向边uv ,u 在排序中都在v 之前。
在图论中,由一个有向无环图的顶点组成的序列,当且仅当满足下列条件时,称为该图的一个拓扑排序(Topological sorting)。
- 每个顶点出现且只出现一次;
- 若A在序列中排在B的前面,则在图中不存在从B到A的路径。
//#include<Windows.h> #include<iostream> #include<cstring> #include<cstdio> using namespace std; const int maxn = 105; const int MAX_E = 10005; const int MAX_V = 105; struct ENode { int to; int Next; }; ENode Edegs[MAX_E]; int Head[MAX_V]; int tnt; void Add_ENode(int w, int v) { ++tnt; Edegs[tnt].to = v; Edegs[tnt].Next = Head[w]; Head[w] = tnt; /*可以拓扑排序则保证这是一个有向图*/ } int IN_degree[maxn];//记录每个点的入度; int Queue[maxn]; int main() { int n, m; int u, v; scanf_s("%d %d", &n, &m); memset(IN_degree, 0, sizeof(IN_degree)); memset(Head, -1, sizeof(Head)); tnt = -1; for (int i = 0; i < m; i++) { scanf_s("%d %d", &u, &v); Add_ENode(u, v); IN_degree[v] ++; } int iq = 0; for (int i = 1; i <= n; i++) { if (IN_degree[i] == 0) { Queue[iq++] = i; } } for (int i = 0; i < iq; i++) { for (int k = Head[Queue[i]]; k != -1; k = Edegs[k].Next) { IN_degree[Edegs[k].to] --; if (IN_degree[Edegs[k].to] == 0) { Queue[iq++] = Edegs[k].to; } } } for (int i = 0; i <= iq; i++) { printf("%d%c", Queue[i], i != iq - 1 ? ' ' : '\n'); } // system("pause"); return 0; }
卡恩算法
假设L 是存放结果的列表,先找到那些入度为零的节点,把这些节点放到L 中,因为这些节点没有任何的父节点。然后把与这些节点相连的边从图中去掉,再寻找图中的入度为零的节点。对于新找到的这些入度为零的节点来说,他们的父节点已经都在L中了,所以也可以放入L。重复上述操作,直到找不到入度为零的节点。如果此时L中的元素个数和节点总数相同,说明排序完成;如果L中的元素个数和节点总数不同,说明原图中存在环,无法进行拓扑排序。
概括起来,即以下三步:
①从有向图中选择一个没有前驱(入度为0)的点,输出它。
②从网(图)中删除它,并且删除从它出发的所有有向边。
③重复上面步骤,直到图(网)中不再存在没有前驱(入度为0)的点为止。
对于上面的算法,如果最终存在不能删除的点,则剩余的点之间一定构成环路。
实现方式:用链式前向星存储整张图,在读入数据时统计每个点的入度。每删除一个点,就删除所有从它出发的边,并且把对应的终点入度-1,再去删除下一个点。
实现代码
//#include<Windows.h> #include<iostream> #include<cstring> #include<cstdio> using namespace std; const int maxn = 105; const int MAX_E = 10005; const int MAX_V = 105; struct ENode { int to; int Next; }; ENode Edegs[MAX_E]; int Head[MAX_V]; int tnt; void Add_ENode(int w, int v) { ++tnt; Edegs[tnt].to = v; Edegs[tnt].Next = Head[w]; Head[w] = tnt; /*可以拓扑排序则保证这是一个有向图*/ } int IN_degree[maxn];//记录每个点的入度; int Queue[maxn]; int main() { int n, m; int u, v; scanf_s("%d %d", &n, &m); memset(IN_degree, 0, sizeof(IN_degree)); memset(Head, -1, sizeof(Head)); tnt = -1; for (int i = 0; i < m; i++) { scanf_s("%d %d", &u, &v); Add_ENode(u, v); IN_degree[v] ++; } int iq = 0; for (int i = 1; i <= n; i++) { if (IN_degree[i] == 0) { Queue[iq++] = i; } } for (int i = 0; i < iq; i++) { for (int k = Head[Queue[i]]; k != -1; k = Edegs[k].Next) { IN_degree[Edegs[k].to] --; if (IN_degree[Edegs[k].to] == 0) { Queue[iq++] = Edegs[k].to; } } } for (int i = 0; i <= iq; i++) { printf("%d%c", Queue[i], i != iq - 1 ? ' ' : '\n'); } // system("pause"); return 0; }