拓扑排序简单来说就是把一个图的所有节点排序,使得每一条有向边(u,v)对应的u都排在v的前面。
拓扑排序最大的用途就是判断一个有向图是否有环。
如果用邻接表的话拓扑排序的时间复杂度是O(N + E),邻接矩阵是O(N^2),N表示顶点数,E表示边数。
性质
1、 拓扑排序在有向无环图中才能排出有效的序列,否则能判断该有向图有环。
2、如果输入的有向图中的点,不存在入度为0的点,则该有向图存在回路
3、如果存在的入度为0的点大于一个,则该有向图肯定不存在一个可以确定的拓扑序列但并不妨碍拓扑排序 。
实现很简单:就是在网络中找到入度为0的点,删去这个点和其关联的边,这样会影响一些点的入度减1,这样就会出现新的入度为0的点(如果不出现说明有环),重复直到网络上所有点全部删除。
先贴个邻接矩阵的代码,很好理解,不过复杂度太高。
//如果无法完成排序,返回0,否则返回1,ret返回有序点列
//传入图的大小n和邻接阵mat,不相邻点边权0,相邻点边权1
const int MAXN = 100;
int toposort(int n, int mat[][MAXN], int *ret)
{
int d[MAXN], i, j, k;
for (i = 0; i < n; ++i)
for (d[i] = j = 0; j < n; d[i] += mat[j++][i]); //计算每个点的入度,d数组储存
for (k = 0; k < n; ret[k++] = i)
{
for (i = 0; d[i] && i < n; ++i); //寻找入度为0的点
if (i == n) //入度为0的点不存在,说明有环
return 0;
for (d[i] = -1, j = 0; j < n; ++j) //删去该点相连的边,其他相关点度-1
d[j] -= mat[i][j];
}
return 1;
}
邻接表的复杂是O(N+E),适合很多点的稀疏图,但是代码量上比较大了.....
d数组依旧记录每个点入度数,但是对于当前入度为0的点,我们可以用d数组它前一个入度为0的点(第一个入度为0记录值是-1),用top记录当前最新出现的入度0的点,这样就像当于手动模拟栈了。
举例一下吧:
假如我的1,2,4,8这四个点当前入度都是0,top初始-1,手动模拟一下:
遇见点1,让d[1] = top,同时让top = 1;
遇见点2,让d[2] = top,同时让top = 2;
剩下两个点同理,最后就是d[1] = -1, d[2] = 1, d[4] = 2, d[8] = 4, top = 8,这样,我们输出0点的时候就可以由当前top指向的点推回去了。
之后对于每个点记录的信息,用链表记录每个点所指向的边,每次删点的时候,所关联的边也能删去了。
这样就是:每个点压栈一次,出栈一次,每条边遍历了一次,复杂度是O(N + E)。
代码是以前写模板,直接粘了.......
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int maxn = 50;
struct Node
{
int to; //保存终点
struct Node *next;
};
int n, m;
Node *List[maxn];
int indeg[maxn];
int ans[100];
void toposort()
{
int i, top = -1, t = 0;
Node *tmp;
bool flag = false; //初始化为无环
for (i = 0; i < n; ++i)
if (!indeg[i]) //如果i点入度为0,压入栈
indeg[i] = top, top = i;
//手动模拟栈操作,top指向栈顶元素,indeg来存栈中元素和空指针(栈底)。
for (i = 0; i < n; ++i)
{
if (top == -1) //如果top指向-1,则网络中出现环
{
flag = true;
break;
}
//否则,继续将元素加入ans数组
else
{
int j = top;
top = indeg[top]; //出栈操作
ans[t++] = j + 1;
tmp = List[j];
while (tmp != NULL)
{
int k = tmp -> to;
if (--indeg[k] == 0)
indeg[k] = top, top = k;
tmp = tmp -> next;
}
}
}
if (flag)
puts("Network has a cycle!");
else
{
for (i = 0; i < t; ++i)
printf("%d%c", ans[i], i !=t - 1 ? ' ' : '\n');
}
}
int main()
{
int i, u, v;
while (scanf("%d%d", &n, &m), n && m)
{
memset(List, 0, sizeof(List));
memset(indeg, 0, sizeof(indeg));
Node *tmp;
for (i = 0; i < m; ++i)
{
scanf("%d%d", &u, &v);
u--, v--; //在操作中,1点相当于0点(方便代码书写)
indeg[v]++;
tmp = new Node;
tmp -> to = v;
tmp -> next = NULL;
if (List[u] == NULL)
List[u] = tmp;
else
tmp -> next = List[u], List[u] = tmp;
}
toposort();
for (i = 0; i < n; ++i) //删除链表
{
tmp = List[i];
while (tmp != NULL)
{
List[i] = tmp -> next;
delete tmp ;
tmp = List[i];
}
}
}
return 0;
}
推荐一道不错的toposort的题,主要是一种证明的思路很不错:http://blog.csdn.net/lvlawliet/article/details/6923082