图:最小生成树

图:最小生成树

简述

按照不同的遍历算法,将得到不同的生成树;从不同的顶点出发,得到的生成树夜有所不同。对于一个带权图而言,不同的生成树对应的总权值夜不尽相同。所以,如何找到权值最小的生成树呢?
典型的方法有两种:Kruskal算法和Prim算法

Kruskal 算法

基本过程

任意给定一个有n个节点的连通网络 N={ V , E },首先构造一个由这n个顶点组成的、不含任何边的图 T = { V, ∅ } ,其中每个顶点自成一个连通分量。不断从E中取出权值最细小的一条边(若有多条,任取其一),若改变的两个端点来自不同的连通分量,则将刺鳊加入到T中。如此重复,知道所有顶点在同一连通分量上为止。
利用最小堆和并查集来实现Kruskal算法。首先利用最小堆来存放E中所有的边。堆中每个节点的格式为:
tail(边的顶点位置);head(边的顶点位置);cost(边的权值)
在构造最小生成树的过程中,尚未处理的边存放在最小堆中。通过并查集的Find运算,可以很快地判断热议一条边的两个端点是否来自同一个连通分量。若是,则舍弃这条边;否则,将此边加入到T中,并通过并查集的Union运算,将两个端点各自所处的两个连通分量合二为一。随着T中边的不断增多,连通分量的逐步合并明知道最后主题构成一个连通分量为止。

伪码描述

T =;
while(T包含的变少于n-1&&E不空)
{E中选一条具有最小代价(cost)的边(v ,w);E中能够删去(v,w);
	如果(v,w)加到T中不会产生回路,则将(v,w)加入T;
	否则放弃(v,w);
}
if(T中包含的边数少于n-1)则不是最小生成树;

代码实现

先给出最小生成树的类定义

const float maxValue=FLOAT_MAX;		//机器可表示的、问题中不可能出现的大数
template<class T,class E>
struct MSTEdgeNode					//最小生成树边节点的类声明
{
	int tail,head;E key;			//端点的位置和边的权值
	MSTEdgeNode() :tail(-1),head(-1),key(0){}//构造函数
	//操作符,自行补充
	bool operator <= (MSTEdgeNode<T,E>&R){return key<=R.key;
};
template<class T,class E>
class MinSpanTree					//最小生成树的类定义
{
protected:
	MSTEdgeNode<T,E> *edgevalue;	//用边值数组表示树
	int maxSize, n					//数组的最大元素个数和当前元素个数
public:
	MibnSpanTree(int sz=DefaultSize-1):MaxSize(Sz),n(0)
	{edgevalue=new MSTEdgeNode<T,E>[sz];}
	int Insert(MSTEdgeNode& item);
};

在求解最小生成树的时候,可以使用邻接矩阵存储图,也可以用邻接表存储。算法中使用图的抽象基类操作,无需考录图及其操作的具体实现。

#include"heap.h"
#include"UFSets.h"
template<class T,class E>
void Kruskal(Graph<T,E>& G,MinSpanTree<T,E> & MST)
{
	MSTEdgeNode<T,E> ed;int u,v,count;			//边节点辅助单元
	int n=G.NumberOfVertices()//顶点数
	int m=G.NumberOfEdges();					//边数
	MinHeap<MSTEdgeNode<T,E>> H(m);				//最小堆,关键码类型为E
	UFSets F(n);								//并查集
	for(u=0;u<n;u++)
	{
		for(v=u+1;v<n;v++)
		{
			ed.tail=u;ed.head=v;				//插入堆
			ed.key=G.getWeight(u,v);
			H.Insert(ed);
		}
	}
	count=1;									//最小生成树加入边数计数
	while(count<n)								//反复执行,去n-1条边
	{
		//从最小堆中退出具有最小权值的边ed
		H.RemoveMib(ed);					
		u=F.Find(ed.tail);						//取两顶点所在集合的根u和v
		v=F.Find(ed.head);		
		if(u!=v)								//不是同一个集合,说明不连通
		{
			F.Union(u,v);						//合并,连通
			MST.Insert(ed);						//该边存入最小生成树
			count++;
		}				
	}
}

复杂度分析

如果基于邻接矩阵实现图的存储,则建立在最小堆是需要检测图的邻接矩阵,这需要O(n2)的时间,此外,需要将e条边组成初始的最小堆。如果从空堆开始,依次插入各边,需要O(e l o g 2 e log_2{e} log2e)时间。在构造最小生成树的过程中,需要进行O(e)次出堆的操作RemoveMin( )、2次并查集的Find( )操作以及n-1次Union( )操作。所以计算的总时间为O(e l o g 2 e log_2{e} log2e+e l o g 2 n log_2{n} log2n+n2+n)。对连通图而言就是O(n2+e l o g 2 e log_2{e} log2e)
如果采用邻接表实现图的存储,则在建立最小堆的时候需要检测图的邻接表,需要O(n+e)的时间。为建成初始的最小堆。需要O(e l o g 2 e log_2{e} log2e)时间。在构造对象生成树的过程中,需要进行O(e)次出堆操作RemoveMin( )、2e次并查集的Find( )操作和n-1次Union( )操作,计算总时间为O(e l o g 2 e log_2{e} log2e+ l o g 2 e log_2{e} log2e+n2+n)。对连通图而言就是O(n+e l o g 2 e log_2{e} log2e)。

Prim 算法

基本过程

与Kruskal算法类似,需要一个最小堆存储图的边,每次选出的一个端点在生成树中,另外一个端点不在生成树的权值最小的边,它正好在最小堆的顶端,将其从堆中退出,加入到生成树中。然后将新出现的所有端点在生成树中,一个端点不在生成树的边都插入最小堆中。下一次更迭中,下一条满足要求的权值最小的又上升到最小堆的顶端。如此重复n-1次。最有建立起最小生成树。

伪码描述

//选定构造最小生成树的顶点u
V={u},E=//E是最小生成树的边集合,V是其顶点集合
while(V包含的顶点少于n&&E不空){E中选择一条边(u,v),u∈V ∩ u∈!V,且具有最小代价;V=V(v),E=E{(u,v)};
	将新选出的边从E中剔除E=E-{(u,v)};
}
if(V包含的顶点少于n)cout<<"不是最小生成树"<<endl;

代码实现

#include"heap.h"
template<class T,class E>
void Prim(Graph<T,E>& G,const T u0,MinSpan<T,E> &MST)
{
	MSTEdgeNode<T,E>ed;int i,u,v,count;//边节点辅助单元
	int n=G.NumberOfVertices();//顶点数
	int m=G.NumberOfEdges();//边曙
	int u=G.getVertexPos(u0);//求起始顶点号
	MinHeap<MSTEdgeNode<T,E>>H(m);//最小堆,关键码为E
	bool Vmst=new bool[n];//最小生成树顶点集合
	for(i=0;i<n;i++)Vmst[i]=false;
	Vmst[u]=true;//u加入生成树
	count=1;
	do{
		v=G.getFirstNeighbor(u);//迭代
		while(v!=-1){//重复检测u的所有邻接点
			if(Vmst[v]==false){//若v不在生成树,(u,v)加入堆
				ed.tail=u;ed.head=v;
				ed.key=G.getWeight(u,v);//tail在树内,head不在树内
				H.insert(ed);
			}
			v=G.getNextNeightbor(u,v);
		}
		while(!H.isEmpty()&&count<n)
		{
			H.RemoveMin(ed);
			if(Vmst[ed.head]==false){
				MST.insert(ed);
				u=ed.head;Vmstu[u]=true;
				count++;break;
		}
	}while(count<n);
}

复杂度分析

此算法的迭代次数为O(n),每次迭代将平均2e/n条边插入最小堆中,e条边从典狱长删除,堆得插入和删除时间复杂性均为O( l o g 2 e log_2{e} log2e),则总的计算时间为O(e l o g 2 e log_2{e} log2e)。

引用

[1]殷人昆.《数据结构(用面向对象方法与c++语言描述)》.北京:清华大学,2007.6:371~375页.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值