最小树型图的求解与实现

转自:http://www.zlinkin.com/?p=63

  图论是ACM竞赛中比较重要的组成部分,其模型广泛存在于现实生活之中。因其表述形象生动,思维方式抽象而不离具体,因此深受各类喜欢使劲YY的Acmer的喜爱。这篇文章引述图论中有关有向图最小生成树的部分,具体介绍朱刘算法的求解思路,并结合一系列coding技巧,实现最小树型图O(VE)的算法,并在最后提供了该算法的模版,以供参考。

  关于最小生成树的概念,想必已然家喻户晓。给定一个连通图,要求得到一个包含所有顶点的树(原图的子图),使之所构成的边权值之和最小。在无向图中,由于边的无向性质,所以求解变得十分容易,使用经典的kruskal与prim算法已经足够解决这类问题。而在有向图中,则定义要稍微加上一点约束,即以根为起点,沿给定有向边,可以访问到所有的点,并使所构成的边权值之和最小,于是问题开始变得不一样了,我们称之为最小树型图问题。

  该问题是由朱永津与刘振宏在上个世纪60年代解决的,值得一提的是,这2个人现在仍然健在,更另人敬佩的是,朱永津几乎可以说是一位盲人。解决最小树型图的算法后来被称作朱刘算法,也是当之无愧的。

  首先我们来阐述下算法的流程:算法一开始先判断从固定根开始是否可达所有原图中的点,若不可,则一定不存在最小树形图。这一步是一个很随便的搜索,写多搓都行,不加废话。第二步,遍历所有的边,从中找出除根结点外各点的最小入边,累加权值,构成新图。接着判断该图是否存在环。若不存在,则该图便是所求最小树型图,当前权为最小权。否则对环缩点,然后回到第二步继续判断。

  这里存在一系列细节上的实现问题,以确保能够达到VE的复杂度。首先是查环,对于新图来说只有n-1条入边,对于各条入边,其指向的顶点是唯一的,于是我们可以在边表中添加from,表示该边的出发点,并考虑到如果存在环,则对环上所有边反向,环是显然同构的,于是最多作V次dfs就能在新图中找到所有的环,并能在递归返回时对顶点重标号进行缩点,此步的重标号可以用hash数组映射。然后就是重要的改边法,对于所有不在环上的边,修改其权为w-min(v),w为当前边权,min(v)为当前连接v点的最小边权。其数学意义在于选择当前边的同时便放弃了原来的最小入边。我们可以知道,每次迭代选边操作O(E),缩点操作O(V),更新权操作O(E),至少使一个顶点归入生成树,于是能在V-1步内完成计算,其复杂度为O(VE)。

  以上为定根最小树型图,对于无固定根最小树型图,只要虚拟一个根连所有的点的权为边权总和+1,最后的结果减去(边权+1)即可。另外对于求所定的根标号,只要搜索被选中的虚边就可以判断了。

  以下为代码上的一些实现:

#include <iostream>
using namespace std;
 
const int MAXN=1010;
const int MAXM=10010;
typedef struct{int v,next,rt,cost,bt,bv;}edge;
 
int N,M,eid;
int rt,w;//不定根
int p[MAXN],In[MAXN],ind[MAXN],hash[MAXN];;
edge e[MAXM];
bool vist[MAXN];
int clen;
 
inline void init(){eid=0;memset(p,-1,sizeof(p));}
 
inline void insert(int from , int to , int cost)
{
	if (from==to) return;
	e[eid].next=p[from];
	e[eid].v=to;
	e[eid].rt=from;
	e[eid].bv=to;
	e[eid].bt=from;
	e[eid].cost=cost;
	p[from]=eid++;
}
 
// 定根
// inline void dfs(int pos)
// {
//	 int j;
//	 for (j=p[pos];j!=-1;j=e[j].next)
//	 {
//		 if(!vist[e[j].v])
//		 {
//			 vist[e[j].v]=true;
//			 dfs(e[j].v);
//		 }
//	 }
// }
int Cnt;
int st;
inline bool c_dfs(int pos)
{
	int x=ind[pos];
 
	if (x==-1)
	{
		return false;
	}
 
	int v=e[x].rt;
 
	if (!vist[v])
	{
		vist[v]=true;
		if(c_dfs(v))
		{
			hash[v]=Cnt;
			return true;
		}
		vist[v]=false;
	}
	else
	{
		return st==v;
	}
 
	return false;
}
 
int fit;
 
inline int Rtree()
{
	int i;
	//以下为不定根
	++w;
	rt=N;
	for (i=0;i<N;++i) insert(rt,i,w);
	++N;
	///
	//定根专用
	//rt=0
	//memset(vist,0,sizeof(vist));
	//vist[rt]=true;
	//dfs(rt);
	//for (i=0;i<N;++i) if(!vist[i]) {return -1;}
	///
 
	int res=0;
	for (i=0;i<N;++i) hash[i]=i;
	bool flag=true;
 
	int bbt=rt;
	int ct;
 
	fit=INT_MAX;
 
	while(1)
	{
		ct=clen=Cnt=0;
		for (i=0;i<N;++i) In[i]=INT_MAX,ind[i]=-1;
 
		//找最小边
		for (i=0;i<eid;++i)
		{
			int ff=e[i].rt;
			int tt=e[i].v;
 
			if (ff==tt)
			{
				continue;
			}
 
			if (e[i].cost<In[tt])
			{
				In[tt]=e[i].cost;
				ind[tt]=i;
			}
		}
 
		In[rt]=0;
		ind[rt]=-1;
 
		for (i=0;i<N;++i)
		{
			int x=ind[i];
			if(x==-1) continue;
			if(e[x].bt==bbt)
			{
				++ct;
				if(e[x].bv<fit) fit=e[x].bv;
			}
		}
 
		memset(vist,0,sizeof(vist));
 
		//找环
 
		for (i=0;i<N;++i)
		{
			if(!vist[i])
			{
				vist[i]=true;
				hash[i]=Cnt;
				st=i;
				if(c_dfs(i))
				{
					++clen;
					++Cnt;
					continue;
				}
				vist[i]=false;
				++Cnt;
			}
		}
 
		for (i=0;i<N;++i)
		{
			res+=In[i];
		}
 
		if (clen==0)
		{
			break;
		}
 
		//缩点并改边
		for (i=0;i<eid;++i)
		{
			int ff,tt;
			ff=e[i].rt;
			tt=e[i].v;
			e[i].rt=hash[e[i].rt];
			e[i].v=hash[e[i].v];
 
			if (e[i].rt!=e[i].v)
			{
				e[i].cost-=In[tt];
			}
		}
 
		N=Cnt;
		rt=hash[rt];
	}
 
	if(ct!=1) return -1;
	return res-w;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值