初学拓扑排序笔记

拓扑排序

1、定义

  • 对一个有向无环图(Directed Acyclic Graph简称DAG) G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若边(u,v)∈E(G),则u在线性序列中出现在v之前。通常,这样的线性序列称为满足拓扑次序的序列,简称拓扑序列。简单的说,由某个集合上的一个偏序得到该集合上的一个全序,这个操作称之为拓扑排序。
  • 在AOV网中,若不存在回路,则所有活动可排列成一个线性序列,使得每个活动的所有前驱活动都排在该活动的前面,我们把此序列叫做拓扑序列,由AOV网构造拓扑序列的过程叫做拓扑排序。AOV网的拓扑序列不是唯一的,满足上述定义的任一线性序列都称作它的拓扑序列。

2、拓扑排序的实现步骤

  1. 将有向图中所有没有前驱(入度为0)的顶点丢入队列。
  2. 队首出队列,从图中删除该顶点和所有它能到达的边这些边的终点的入度减1,将入度变为0的点入队列。
  3. 重复第二步,直至队列为空为止,如果当前图中还存在入度不为0的顶点,则说明我们的有向图是有环的,因此,也可以通过拓扑排序来判断一个图是否有环。

3、拓扑排序示例手动实现

如果我们有如下的一个有向无环图,我们需要对这个图的顶点进行拓扑排序,过程如下:
初学拓扑排序笔记_第1张图片

首先,我们发现1和6是入度为0的,所以我们就把他们丢入队列,假设1先入队列,我们先输出1,删除顶点1和1能到达的边,得到如下图结果:

初学拓扑排序笔记_第2张图片
此时3入度为0,3入队列。队列为 1(已出),6,3。

然后,6出队列,删除顶点6和6能到达的边,得到如下图结果:

初学拓扑排序笔记_第3张图片
此时4入度为0,4入队列。队列为 1(已出),6(已出),3,4。

接着,3出队列,删除顶点3和3能到达的边,得到如下图结果:

初学拓扑排序笔记_第4张图片
此时2入度为0,2入队列。队列为 1(已出),6(已出),3(已出),4,2。

然后,4出队列,删除顶点4和4能到达的边,得到如下图结果:

初学拓扑排序笔记_第5张图片
此时5入度为0,5入队列。队列为 1(已出),6(已出),3(已出),4(已出),2,5。

接着,2出队列,删除顶点2和2能到达的边,得到如下图结果:

初学拓扑排序笔记_第6张图片
此时队列为 1(已出),6(已出),3(已出),4(已出),2(已出),5。

然后,5出队列,删除顶点5和5能到达的边,得到如下图结果(其实已经什么都没有了):

此时队列为空,排序完成,该图的一个拓扑序列为:

1→ 6→ 3→ 4→ 2→ 5

过程简述:
  1. 将有向图中所有没有前驱(入度为0)的顶点丢入队列。
  2. 队首出队列,从图中删除该顶点和所有它能到达的边这些边的终点的入度减1,将入度变为0的点入队列。
  3. 重复第二步,直至队列为空为止。

4.模板题

洛谷P1807 最长路(我是以这个为模板题的)

代码如下:
#include
#include
#include
#define maxn 1505
#define maxm 50005
#define ri register int
using namespace std;
int to[maxm];
int weight[maxm];
int next[maxm];
int head[maxn];
int dis[maxn];
int in[maxn];//每个点的入度
int flag[maxn];//判断1是否能到达
int n,m,cnt;
inline int read()//快读
{
	ri x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9')
	{
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		x=x*10+ch-'0';
		ch=getchar();
	}
	return x*f;
}
inline void add_edge(int u,int v,int w)//建边
{
	to[cnt]=v;
	weight[cnt]=w;
	next[cnt]=head[u];
	head[u]=cnt;
	cnt++;//一个好习惯
}
inline void spfa()
{
	queue<int>q;//定义一个普通队列
	memset(dis,-1,sizeof(dis));//dis清为-1
	dis[1]=0;
	flag[1]=1;//1肯定能到达1的啦
	for(ri i=1;i<=n;++i) if(!in[i]) q.push(i);//入度为0的点入队列
	while(q.size())//如果队列不为空
	{
		ri u=q.front();
		q.pop();
		for(ri i=head[u];~i;i=next[i])//~i即i!=-1
		{
			ri v=to[i],w=weight[i];
			in[v]--;//这条边的终点入度减1
			if(flag[u])//如果1能到达
			{
				if(dis[u]+w>dis[v]) dis[v]=dis[u]+w;//更新最长路
				flag[v]=1;//1能到达这条边的终点
			}
			if(!in[v]) q.push(v);//入度为0进队列
		}
	}
}
int main()
{
	n=read(),m=read();
	memset(head,-1,sizeof(head));//又一个好习惯
	for(ri i=1,u,v,w;i<=m;++i)
	{
		u=read(),v=read(),w=read();
		add_edge(u,v,w);
		in[v]++;//通向的点入度加1
	}
	spfa();
	printf("%d",dis[n]);
	return 0;
}
  • 看不懂我的代码的请看这里。

你可能感兴趣的:(图论)