用邻接矩阵+普里姆(Prim)算法构造最小生成树(最小支撑树)

首先什么是最小生成树(我的教材称为最小支撑树)?它是一个图的极小连通子图。那什么是图?什么是极小连通子图

图是一种数据结构,由顶点集合V(非空)和边集合E构成,记为G=(V,E)。G即Graph图,V即Vertex顶点,E即Edge边。连通图可以理解为能够一笔画出来的图。而极小连通子图则是包含一个连通图的所有顶点(假设有n个),却只有(n-1)条边(这些边仍然属于E)的子图,并且这个子图仍然是连通的(如下图的G1和G2)。

而最小生成树不仅要是极小连通子图,并且它边上的权值之和要最小。显然,由一个连通图构造的最小生成树可能不唯一,但权值之和是唯一的

这个问题的一个解法是基于贪心思想的Prim算法。设这个图是G=(V,E),最小生成树T=(Vt,Et),它的思想是从一个顶点u出发,即初始时T中的Vt={ u }, Et={ },寻找Vt中的顶点与其它顶点(即V-Vt,集合的差运算)构成的边的最小权值

-------------------------------以下的讨论均以上图为例(请忽略我的鬼画符......)-------------------------------

在这个图中,假设我们从V1点出发(哪个点一样),可以看到,当前Vt中只有一个顶点{ V1 },从Vt各点到V1,V2,...,V6的最小权值用一个数组low_cost[]表示,约定-1为结点已经加入Vt,∞表示Vt各点不能到达结点。则数组的值是{-1,6,1,5,∞,∞}。由此可以,下一步我们应当把V3加入Vt,因为此时Vt的顶点到其余各点权值最小的便是V3,权值为1。如此Vt={ V1,V3 },Et={ (V1->V3) }

要注意的是,此时的low_cost[]数组应当进行更新,因为V3加入Vt,故而而V3到V2的距离是5,比原来的6要小;V3到V4、V5的距离是6、4,比原来的无穷大∞要小,因此low_cost[] = { -1,5,-1,5,6,4 }。

显然,此时low_cost[]中权值最小(不计-1)的是4,即V6,因此将V6加入Vt,此时Vt={ V1,V3,V6 }, Et={ (V1->V3), (V3->V6) }。同上可知,V6到V4的距离为2,比原来的6要小,因此low_cost[] = { -1,5,-1,5,2,-1 }。

同理再选取V5加入Vt......

............

如此一直进行到V == Vt为止,时间复杂度为O( |V|² ) ,|V|是图的顶点个数。

以下是源码和基于以上例子调试结果:

其中代码部分参考 https://blog.csdn.net/qq_35644234/article/details/59106779

#include <iostream>
#include <string>
#include <vector>
#include <climits>
using namespace std;

typedef struct{
	int vexnum;	//顶点数 
	int arcnum;	//边数 
	int **arc;	//邻接矩阵
	string *name; 
}Matrix_Graph;	

bool newGraph(Matrix_Graph& g)
{
	cout << "-------准备使用邻接矩阵法创建无向带权图-------" << endl;
	cout << "请输入顶点数和边数(空格隔开): ";
	cin  >> g.vexnum >> g.arcnum;
	//	图可以没有边,但不能没有顶点
	//	若无向图有n个顶点,则最多有n*(n-1)/2条边(无向完全图) 
	if( g.vexnum<0 || g.arcnum<=0 || g.arcnum>(g.vexnum*(g.vexnum-1)/2) ){
		cerr << "数据输入有误,请检查数据!" << endl; 
		g.vexnum = g.arcnum = 0;
		return false;
	}
	//	邻接矩阵初始化 
	g.arc  = new int*[g.vexnum+1];
	g.name = new string[g.vexnum+1]; 
	for(int i = 1; i <= g.vexnum; ++i){
		g.name[i] = "V" + to_string(i);	//从1开始计数,边为V1, V2, V3... 
		g.arc[i]  = new int[g.vexnum+1];
		for(int j = 1; j <= g.vexnum; ++j)
			g.arc[i][j] = INT_MAX;		//各边距离初始化为无穷大	
	}
	//	输入各边的权值
	int vstart, vend, weight;
	int n = g.arcnum;
	cout << "请输入" << n << "条边的起始顶点、终止顶点和权值(空格隔开)" << endl; 
	while( n-- ){
		//	懒,这里就不做输入检查了 
		cin >> vstart >> vend >> weight;
		g.arc[vstart][vend] = g.arc[vend][vstart] = weight;		
	}	 
	
	return true;		
}


//	该结构体数组存储当前最小生成树(Vt)中的顶点到 
//	其它顶点(V-Vt)的边的最小距离weight,-1代表该顶点已经在V中了
//	vstart是该边起点,vend是该边终点
typedef struct{
	int vstart;
	int vend;
	int weight;
}Closest;

void printRes(vector<string>& Vt, vector<Closest>& Et, Closest *low_cost)
{
	cout << "当前最小生成树的顶点有:";
	for(auto v : Vt)
		cout << v << ", ";
	cout << endl;
	
	cout << "当前最小生成树的边:";
	for(auto e : Et)
		cout << "V"+to_string(e.vstart) << "->" << "V"+to_string(e.vend) << ", ";
	cout << endl;
	
	cout << "当前到各结点最小权值:" << endl;
	for(int i = 1; i <= low_cost[0].weight; ++i){
		cout << "到V" << i << ": ";
		if(  low_cost[i].weight == INT_MAX )
			cout << "∞" << endl;
		else
			cout << low_cost[i].weight << endl;
	}  
	cout << endl;	
}

bool Prim(const Matrix_Graph& g, int start = 1)
{
	//	以下2个vector仅为了输出过程及结果,对算法无影响 
	vector<string>  Vt;	//	存储当前最小生成树的结点 
	vector<Closest> Et;//	存储当前最小生成树的边 
	
	int i, j, k; 
	Closest *low_cost  = new Closest[g.vexnum+1];
	low_cost[0].weight = g.vexnum;	//	记录顶点数,方便输出 
	for(i = 1; i <= g.vexnum; ++i){		
		low_cost[i].weight = g.arc[start][i];
		low_cost[i].vstart = start;
		low_cost[i].vend   = i;
	}
	low_cost[start].weight = -1;	
	Vt.push_back(g.name[start]);	//	起始顶点存储进Vt
	
	printRes(Vt, Et, low_cost);
	//	将剩下的 g.vexnum-1 个结点加入Vt中 
	for(i = 1; i < g.vexnum; ++i)
	{
		//	查找当前可连接的最小权值边
		int index = 0, min = INT_MAX;
		for(j = 1; j <= g.vexnum; ++j){
			if( low_cost[j].weight!=-1 && low_cost[j].weight<min ){
				min = low_cost[j].weight;
				index = j;
			}	
		}
		if( index == 0 ){
			cerr << "此图不能形成最小生成树!" << endl;
			return false; 
		}
		//	该边的weight置-1,表明存储进V
		low_cost[index].weight = -1;
		Vt.push_back(g.name[index]);
		Et.push_back(low_cost[index]);
		//	更新low_cost数组,因为此时新加入的结点可能
		//	比原来的结点到其它结点的权重要小
		int newPos = low_cost[index].vend;	//新加入结点的编号 
		for(k = 1; k <= g.vexnum; ++k){
			if( g.arc[newPos][k] < low_cost[k].weight ){
				low_cost[k].weight = g.arc[newPos][k];
				low_cost[k].vstart = newPos;
				low_cost[k].vend   = k;
			}	
		} 
		//	打印信息
		printRes(Vt, Et, low_cost);			
	}
	cout << "------最小生成树构造完毕!------" << endl;
	return true;
}
 
int main()
{    
    Matrix_Graph g;
    if( newGraph(g) ){
    	Prim(g);
	} 
	
    return 0;
}

 

  • 7
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值