图的最小生成树

一 、最小生成树的定义和性质

  • 最小生成树说是树,其实上只是图的衍生物(裁掉某些边),并不能用树形结构来写代码
  • 最小生成树指的是总权值最小的那颗生成树
  • 最小生成树是针对带权无向联通图
  • 最小生成树可能有多个树形,但是一定只有一个序列
  • 设顶点数为n 则边数必为n-1

二、Prim算法

定义:图G=(V,E)   生成树T=(U,E1)
步骤:	①判断图G是否为联通图        通过计算对图进行广度搜索得到图的联通分支数,进而判断
        ②初始化生成树T         	   从图G中任取一点加入到生成树T的顶点集合U中
        ③对顶点集合V和顶点集合U,任取x∈V,y∈U。当d(x,y)最小时,将x从集合V中取出放入U中
        ④不断重复上述过程指导顶点集合V为空

请添加图片描述

代码(实际上就是不断得从另一集合取合适的点加入U中) 时间复杂度为O(V^2)
	//最小生成树的Prim算法   分成两个集合U、V,求U、V中路径最小的点x,y   x在U内、y在V内。不断的把点从V移动到U
	MGraph_ND* Prim(MGraph_ND* Basic_Graph) {
		if (Basic_Graph->Branch>1)return nullptr;//不连通,无最小生成树
		MGraph_ND* MST = new MGraph_ND(Basic_Graph->Vertex_num);
		int Current_i = 0;
		int Current_j = 0;
		int Max =CountMax(MST);
		int min = 0;
		Prim_InitMST(MST);
		MST->Vertex[0] = Basic_Graph->Vertex[0];//放入第0号元素
		Basic_Graph->Vertex[0] = -1;//取出0号元素
		MST->Vertex_num++;
		//n个节点,n-1次循环
		for (int k = 0; k < Basic_Graph->Vertex_num-1; k++) {
			min =Max;
			for (int i = 0; i < Basic_Graph->Vertex_num; i++) {
				//i在集合V内
				if (Basic_Graph->Vertex[i] != -1) {
					for (int j = 0; j < Basic_Graph->Vertex_num; j++) {
						//j在集合U内
						if (MST->Vertex[j] != -1) {
							if (Basic_Graph->Edge[j][i] <= min && Basic_Graph->Edge[j][i]>0) {
								min = Basic_Graph->Edge[j][i];
								Current_i = i;
								Current_j = j;
							}
						}
					}

				}
					
			}
			Basic_Graph->Vertex[Current_i] = -1;//从集合V中取出
			MST->Vertex[Current_i] = Current_i;//加入集合U
			MST->Edge[Current_j][Current_i] = min;//加边
			MST->Edge[Current_i][Current_j] = min;//加边
			MST->Vertex_num++;
			MST->Edge_num++;
		}
		MST->Branch = Count_Linked();
		return MST;
	}
	
	-----Init
	//Prim算法要求刚开始时 U集合内 点和边均为空
	void Prim_InitMST(MGraph_ND* MST) {
		for (int i = 0; i < MST->Vertex_num; i++) {
			MST->Vertex[i] = -1;//无结点
			for (int j = 0; j < MST->Vertex_num; j++) {
				MST->Edge[i][j] = -1;//无边
			}
		}
		MST->Vertex_num = 0;
		MST->Edge_num = 0;
		MST->Branch = 0;
	}

三、Kruskal算法

定义:图G=(V,E)   生成树T=(U,E1)
步骤:	①判断当前图是否为联通图
        ②初始化生成树T      ⭐这里域Prim算法不同的是,Kruskal算法需要刚开始就确定好所有顶点位置,后续再更新边
        ③计算当前的联通分支数      因为生成树内没有边,所有联通分支数就是顶点数
        ④当生成树的联通分支数大于1时:
        			找出生成树内最短的边,记录顶点坐标(u,v)和长度length
        			如果u和v属于不同分支则将u、v划入同一集合中,并更新生成树
        			否则将这条边抛弃⭐(这步很重要,否则会成环)

在这里插入图片描述
可以使用划分等价类的形式,不断得将不同联通分支的点聚合。最终变成只有一个联通分支
请添加图片描述

代码

//最小生成树的Kruskal算法   已初始化点  只需要找到边
	MGraph_ND* Kruskal(MGraph_ND* Basic_Graph) {
		if (Basic_Graph->Branch > 1)return nullptr;//不连通,无最小生成树
		MGraph_ND* MST = new MGraph_ND(Basic_Graph->Vertex_num);
		int branch = MST->Vertex_num;
		Set *sets[MaxSize];
		Kruskal_InitMST(MST);
		for (int i = 0; i < MST->Vertex_num; i++) {
			sets[i] = new Set(i);
		}
		//连通分支数大于1
		while(branch>1){
			int i;
			int j;
			int Min = 1000;
			for (int  m = 0; m < Basic_Graph->Vertex_num; m++) {
				for (int n = 0; n < Basic_Graph->Vertex_num; n++) {
					//求最小的边
					if (Basic_Graph->Edge[m][n]<=Min && Basic_Graph->Edge[m][n]>0)
					{
						Min = Basic_Graph->Edge[m][n];
						i = m;
						j = n;
					}
				}
			}
			Set* pi=sets[i];
			Set* pj=sets[j];
			while (pi->parent != nullptr) { pi = pi->parent; }
			while (pj->parent != nullptr) { pj = pj->parent; }
			//i和j属于不同联通分支
			if (pi->tag!= pj->tag) {
				Basic_Graph->Edge[i][j] = -1;//从原集合中取出边
				Basic_Graph->Edge[j][i] = -1;
				MST->Edge[i][j] = Min;//向U中放入边
				MST->Edge[j][i] = Min;
				MST->Edge_num++;
				Set* parent = new Set(pi->tag>pj->tag ?pj->tag:pj->tag);//取较小的那个为tag
				pi->parent = parent;
				pj->parent = parent;
				cout << sets[0];
				branch--;
			}
			else {
				Basic_Graph->Edge[i][j] = -1;//i和j属于相同联通分支,应该丢弃,否则构成回路,最终导致死循环
				Basic_Graph->Edge[j][i] = -1;

			}

		}
		return MST;
	}

	----Init
		//Prim算法要求刚开始时 U集合内 边均为空,只有空点
	void Kruskal_InitMST(MGraph_ND* MST) {
		for (int i = 0; i < MST->Vertex_num; i++) {
			MST->Vertex[i] = i;//无结点
			for (int j = 0; j < MST->Vertex_num; j++) {
				MST->Edge[i][j] = -1;//无边
			}
		}
		MST->Edge_num = 0;
		MST->Branch = 0;
	}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值