最小生成树问题

[问题描述]ios

若要在n个城市之间建设通讯网络,只须要假设n-1条线路便可。如何以最低的经济代价建设这个通讯网,是一个网的最小生成树问题。算法

[系统要求]数组

  1. 利用克鲁斯卡尔算法求网的最小生成树。
  2. 利用普里姆算法求网的最小生成树。
  3. 要求输出各条边及它们的权值。

[测试数据]网络

由学生任意指定,但报告上要求写出多批数据测试结果。函数

[实现提示]测试

通讯线路一旦建成,必然是双向的。所以,构造最小生成树的网必定是无向网。设图的顶点数不超过30个,并为简单起见,网中边的权值设成小于100的整数,可利用C语言提供的随机函数产生。spa

图的存储结构的选取应和所做操做相适应。为了便于选择权值最小的边,此题的存储结构既不选用邻接矩阵的数组表示法,也不选用邻接表,而是以存储边(带权)的数组表示图。code

[选做内容]排序

利用堆排序实现选择权值最小的边。ci

#include<cstdio>
#include<iostream>
#define inf 0x3f3f3f3f 
using namespace std;

const int Max_e=1000;
const int Max_v=30;
int f[Max_v+1],v,e,count=0;
int edge[Max_v][Max_v],book[Max_v];//
//book:标记点是否加入到生成树中,全局变量初始为0(未加入) edge:邻接矩阵 

struct A {
	int distance;//distance:各个顶点到"生成树"的距离
	int index;//记录生成树中的点(起点) 
}dis[Max_v];

struct B {
	int mmin1;
	int index;
}mmin;

struct E {
	int st;
	int et;
	int w;
}Edge[Max_e]; 

/**打印信息*/ 
void PrintEdgeMes(){
	cout<<"---------------------------"<<endl; 
	cout<<"权值\t起点\t终点\t"<<endl;
	for(int i=1;i<=e;i++){
		cout<<Edge[i].w<<"\t"<<Edge[i].st<<"\t"<<Edge[i].et<<"\t"<<endl;
	}
	cout<<"---------------------------"<<endl;
	cout<<endl; 
}

/**交换两条边的信息*/
void Swap(int i,int j){
	int st,et,w;
	st=Edge[i].st;Edge[i].st=Edge[j].st;Edge[j].st=st;
	et=Edge[i].et;Edge[i].et=Edge[j].et;Edge[j].et=et;
	w=Edge[i].w;Edge[i].w=Edge[j].w;Edge[j].w=w;
} 
/**筛选法调整堆*/
void HeapAdjust(int s,int m){
	//s根结点,m序列长度 
	Edge[0]=Edge[s];//保存根结点信息 
	for(int i=2*s;i<=m;i*=2){//沿着权值较大的孩子结点向下筛选 
		if(i<m&&Edge[i].w<Edge[i+1].w) i++;
		if(Edge[0].w>=Edge[i].w) break;//根结点的权值大于两个孩子,0号位置的信息应该在s位置上 
		Edge[s]=Edge[i];s=i;//不然0号位的信息应该在i,更新根结点,继续找他的左右堆,直到找到叶子结点,一次调整完毕 
	}
	Edge[s]=Edge[0]; 
} 

/**创建大根堆*/
void CreatHeap(){
	int n=e;
	//全部大于n/2的位置上都是叶子结点,咱们从叶子最后一个非叶子结点筛选 
	for(int i=n/2;i>0;i--){
		HeapAdjust(i,e);//不断的调整堆 
	}
		
} 
 
/**对边进行排序(堆排序)*/ 
void HeapSort(){
    CreatHeap();//把无序序列建成大根堆 
    for(int i=e;i>1;i--){
    	//1:堆顶元素的位置,i:未排序的最后一个 
    	Swap(1,i);//将堆顶元素与没有排序(1-i)的的最后一个记录交换 
    	HeapAdjust(1,i-1);//除了堆顶信息,剩下的继续调整为大根堆 
	} 
}

/**找到第一个标记*/
int getf(int n){
    if(f[n]==n)
    	return n;
    else{
    	f[n]=getf(f[n]);//在找到最终的那个点以前遇到的点都"属于最终的那个点"
		return f[n]; 
	}
} 

/**判断边的顶点是否属于一个连通份量*/ 
int  merge(int st,int et){
	int t1,t2;
	t1=getf(st);
	t2=getf(et);
	if(t1!=t2){//t1是不是一个连通份量 
		f[t1]=t2;//标记值都靠近左边的值 
		return 1;
	} 
	return 0;
} 

/* -------Kruskal算法简介(适用于稀疏图)-------
   按照边的权值大小排序,每次从剩余的边中选择权
   值较小且边的两个顶点不在同一个集合内的边加
   入到生成树中,直到加入n-1条边为止 */
void MiniSpanTree_Kruskal(){
	//对边进行排序(堆排序)
	HeapSort();
	//打印对边排序的结果 
	cout<<endl<<"对全部边排序(堆排序)的结果为:"<<endl;
	PrintEdgeMes();
	for(int i=1;i<=v;i++)
		f[i]=i;//各顶点自成一个连通份量
	//枚举边
	cout<<"Kruskal算法选用的边极其权值为:"<<endl;
	cout<<"-----------------------------------"<<endl;
	cout<<"起点\t终点\t权值"<<endl; 
	for(int i=1;i<=e;i++){
		if(merge(Edge[i].st,Edge[i].et)){
			count++; 
		//检查这两个点是否在同一个集合中,若是不在返回1,就选用这条边 
			cout<<Edge[i].st<<"\t"<<Edge[i].et<<"\t"<<Edge[i].w<<"\t"<<endl;
			if(count==v-1)//选了n-1条边时退出循环
				break; 
		} 
	} 
	cout<<"-----------------------------------"<<endl;
}

/*-------Prim算法简介(适用于稠密图)-------
  咱们从1号顶点开始,找到与选过的点相链接的
  最短边,而且这条边能够链接到没有被选中的
  点.这样一直重复n-1次便可找到最小生成树 
*/ 
int main(){
	cout<<"--------------Kruskal算法--------------"<<endl<<endl; 
	cout<<"请输入顶点数和边数:"	;
	cin>>v>>e;
	cout<<"请依次输入e条边及其权值:"<<endl; 
	for(int i=1;i<=e;i++)
		cin>>Edge[i].st>>Edge[i].et>>Edge[i].w;
	MiniSpanTree_Kruskal();
	cout<<endl<<"---------------------------------------"<<endl; 
	cout<<">>Enter键进入Prim算法"<<endl; 
	system("pause");
	system("cls"); 
	
	 
	cout<<"-----------------Prim算法-----------------"<<endl<<endl; 
	count=0;//加入到生成树中的顶点数置为0 
	cout<<"请输入顶点数和边数:"	;
	cin>>v>>e;
	for(int i=1;i<=v;i++){
		for(int j=1;j<=v;j++){
			if(i==j) edge[i][j]=0;
			else edge[i][j]=inf;
		}
	} 
	
	int st,et,w,j;
	cout<<"请依次输入e条边及其权值:"<<endl;
	for(int i=1;i<=e;i++){
		cin>>st>>et>>w;
		edge[st][et]=w;//无向图 
		edge[et][st]=w;
	}
	//先把一号顶点加入到生成树中
	for(int i=1;i<=v;i++){
		dis[i].distance=edge[1][i];
		dis[i].index=1; //目前记录的最短路径都是从1出发的 
	}
		
	book[1]=1;
	count++;
	cout<<endl<<"Prim算法选用的边及其权值为:"<<endl;
	cout<<"-----------------------------------"<<endl;
	cout<<"起点\t终点\t权值"<<endl;
	while(count<v){
		mmin.mmin1=inf;//最短的边
		for(int i=1;i<=v;i++){
			if(book[i]==0&&dis[i].distance<mmin.mmin1){
				mmin.mmin1=dis[i].distance;j=i;//新加入的顶点
				mmin.index=dis[i].index; 
			}
		}
			cout<<mmin.index<<"\t"<<j<<"\t"<<mmin.mmin1<<endl;
			book[j]=1;count++;
			for(int k=1;k<=v;k++){//枚举j链接的顶点,更新各顶点到生成树之间的距离 
				if(book[k]==0&&dis[k].distance>edge[j][k]){
				 dis[k].distance=edge[j][k];
				 dis[k].index=j; 
				//若是这个点不在生成树中,并且有更短的距离 
				} 
			}
	}
	cout<<"-----------------------------------"<<endl<<endl;
	cout<<"-------------------------------------------"<<endl; 
	return 0;
}