拓扑排序与欧拉遍历

拓扑排序与欧拉遍历

拓扑排序

概念:
将一个有向图转化为一个线性序列的问题,且要求满足图中的顶点先后关系.
(即不与图相互冲突)
这里写图片描述

对于这个图来说,它的拓扑序可以为ABCD或ABDC,可见一个图的拓扑序并不是唯一的.但是无论怎样,如果说一个有向图存在一个拓扑序,前提是它不包含环.(A<B,B<C,C<A, 那是不可能转换为一个线性序列又与原图重合的)
我们也可以将一些数学关系理解为拓扑序.如A<B,B<C,那么就有了A<B<C.而如果是A<B,A<C,那么既有可能是A<B<C又有可能是A<C<B.

那么如何来求一个拓扑序呢?我们既可以采用DFS,又可以采用BFS.

BFS
我们可以每次取出入度为0的点从它开始找邻接点,并令所有邻接点的入度减一,从而转化为若干个子问题,放在队列中等待解决.

bool BFS()
{
	int d[MAXN+5];//记录入度
	queue<int> q;
	vector<int> ans;
	for(int i=1;i<=n;i++)
		if(d[i]==0)
			q.push(i);
	while(q.empty()==false)
	{
		int u=q.front();q.pop();
		bool FInd=false;
		for(node *p=Adj[u];p!=NULL;p=p->next)
		{
			int v=p->v;
			d[v]--;
			if(d[v]<=0)
			{
				ans.push(v);//最后的ans即为所求
				q.push_back(v);
			}
		}
	}
	if(ans.size()==n)
		return true;
	else
		return false;//存在环
}

时间复杂度:O(|N|+|E|)
DFS

对于这样一个图(E->C),我们尝试着进行一次DFS发现它的DFS的返回序列为HFCBGEDA,而它的一个拓扑序为ADEGBCFH,正好是前者的逆序.由此便可进行算法的实现了.

vector<int> ans;
int vis[MAXN+5];
bool DFS(int u)
{
	visu[u]=-1;//正在进行递归调用
	for(node *p=Adj[u];p!=NULL;p=p->next)
	{
		int v=p->v;
		if(vis[v]==-1)//找到了一个环
			return false;
		if(vis[v]==0&&dfs(v)==false)
			return false;
	}
	ans.push_back(u);
	vis[u]=1;
	return true;
}
bool TopSort()
{
	memset(vis,0,sizeof(vis));
	for(int i=1;i<=n;i++)
		if(vis[i]==0&&dfs(i)==false)
			return false;
	reverse(ans.begin(),ans.end());
	return true;
}

例题:士兵排队, sorting it all out, instruction arrangement.

求字典序最小的拓扑序:

先说做法:
直接用BFS做。用优先队列来维护。每次取最小的入度为0的点即可。

再说正确性:
区别编号小的尽量靠前考前的位置标号尽量小的区别。
用例子感受一下:
在这里插入图片描述
这两种说法其实对应两种不同的题型。这里我们说的是第二种。答案应该是2341;而第一种的话,答案应该是4123。

第一种描述对应的问题描述和解法如下:
给每个点标号,满足若存在边(u,v),那么id[u]<id[v]。求字典序最小的答案序列。
显然可以把id[u]看作是u的位置。那么也就是要小的u的位置尽量靠前。
模仿DFS求拓扑序的过程,不过用优先队列来实现。每次取出出度为0的、编号最大的节点,放到答案序列的末尾。这样做的原因是尽可能在后面放大数,把小数尽量保留下来,放在尽量靠前的位置。
(我知道这样的描述很模糊,想起来也很抽象,我自己也没想太懂,目前只能理解到这个程度了)

欧拉遍历

欧拉遍历问题就是一笔画问题.如果在进行一次欧拉遍历后还能回到起点,那么就存在一条欧拉回路.
对于无向图,若所有的点的度数均为偶数,那么存在欧拉回路.若有且只有两个点的度数为奇数,那么存在一条欧拉路径,且入口和出口正好是这两个点.
对于有向图,若所有的点的出度均等于入度,则存在欧拉回路.若有且仅有两个点的入度不等于出度,且点A的出度减入度=1,点B的入度减出度=1,那么存在一条欧拉路径,且起点为点A,终点为点B.
那么如何解决欧拉路径问题呢?
我们采用的算法叫做消圈算法.不断遍历每一个以当前点为顶点的环,在找的过程中删去已经走过的边,最终在回溯的时候输出.
实现:

void AddEdge(int u,int v)
{
	node *p=++ncnt;
	p->to=v;
	p->vis=false;//用来删边
	p->next=Adj[u];
	Adj[u]=p;
	
	node *q=++ncnt;
	q->to=u;
	q->vis=false;
	q->next=Adj[v];
	Adj[v]=q;
	
	p->back=q,q->back=p;
}
void dfs(int u)
{
	for(node *p=Adj[u];p!=NULL;p=p->next)
		if(p->vis==false)
			p->back->vis=true,p->vis=true,dfs(p->to);
			//对于无向图来说,需要同时删去反向边
	printf("%d ",u);
}

我们知道,欧拉路径有可能不止一条,而有些题会要求我们输出字典序最小的一种,那么这种问题怎么解决呢?
我们可以这样来想:既然是在回溯的过程中输出,那么最先遍历的环必定最后输出,那么我们应该最先遍历顶点编号最小的点.
如果是用邻接矩阵来做的话,很好实现;但是如果使用邻接表来实现的话,我们需要考虑排序的问题.这里我们可以采用插入排序的思想,在每插入一条边的时候寻找到它适宜的位置,然后在邻接表中间进行插入操作.此时可以用vector或set简单实现,或者用手写邻接表的方式实现.

//手写邻接表的实现:
void AddEdge(int u,int v)
{
	node *p=++ncnt;
	p->v=v;
	node *l=Adj[u];
	node *r=r;
	while(r!=NULL&&r->v<=p->v)//或根据权值等需求自行调整
		l=r,r=r->next;
	if(l==r)
		p->next=Adj[u],Adj[u]=p;
	else
		l->next=p,p->next=r;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值