最小生成树(Kruskal算法和Prim算法)

本文介绍了求解最小生成树的两种经典算法:Kruskal算法和Prim算法。详细讲解了这两种算法的具体实现过程,并提供了完整的代码示例。

最小生成树:

一个有n个结点的连通图的生成树,是原图的极小连通子图,包含原图放入所有结点,并且保持图连通的最小的边。求最小生成树可以用Kruskal(克鲁斯卡尔)算法和Prim(普里姆)算法求出。

Kruskal算法:

具体思路:是将每条边上的权进行排序,之后在从中选取最小的边,加入最小树的集合中。

注意:在以上的过程中得防止出现环路。可以用并查集维护。
以下图为例:
这里写图片描述
首先,建图,用一个结构体数组:

struct node{
	int x;  //起点
	int y;  //终点
	int w;  //权重
}graph[100];

之后,用并查集判断是否会产生环路:

int point[100];  //用来标记结点
for(int i=1;i<=n;i++){
	point[i]=i;  //先将每个结点的根节点都设为自己
}
/*查找*/
int find(int n){
	if(n==point[n]) return n;//返回结点的根节点
	find(point[n]);
}
/*合并*/
for(int i=1;i<=m;i++){
	int fx = find(graph[i].x); //查找根节点
	int fy = find(graph[i].y); //查找根节点
	//若这条边的起点和终点相同,说明形成环路
	if(fy!=fx){
		point[fy] = fx;  //不相等,则合并(纳入最小树的集合)
	}
}

完整代码:

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std; 
const int M =100;

int n; //结点数
int m; //边数
int point[M];  //并查集 

//图的结构体数组 
struct node{
	int x;    //起点 
	int y;	  //终点 
	int w;    //权重 
}graph[M];

//并查集:查找 
int find(int x){
	if(point[x]==x) return x;
	return find(point[x]);
}

int cmp(node a,node b){
	return a.w<b.w;
}

int main(){
	int sum;
	while(cin>>n>>m){
		for(int i=1;i<=n;i++){
			point[i] = i;
		}
		// 建图 
		for(int i=1;i<=m;i++){
			cin>>graph[i].x>>graph[i].y>>graph[i].w; 
		}
		// 按照边上的权重大小排序 
		sort(graph+1,graph+m+1,cmp);
		sum = 0;
		for(int i=1;i<=m;i++){
			int fx = find(graph[i].x); //集合的顶点 
			int fy = find(graph[i].y); //集合的顶点 
			//判断是否形成环路 
			if(fy!=fx){
				sum+=graph[i].w;
				point[fy] = fx;  //纳入集合 
			}
		}
		cout<<sum<<endl;  //输出最小连通图的权值的和
	}
	return 0;
}

Prim算法

对于Prim算法,相对于Kruskal算法而言,Prim算法,适用于稠密图,即:边比较少的图。也就是说Kruskal算法比较适用于稀疏图。

具体思路:以图中的任意一个结点作为起点,逐个找到各个结点的最小权值的边(当前局部最小,达到整体最小)。来构建最小生成树。
首先,建图:

/*用一个二维数组cost[][],建图*/
for(int i=0;i<n;i++){
	for(int j=0;j<n;j++){
		int x,y,len;
		cin>>x>>y>>len;
		cost[x][y] = len;
		cost[y][x] = len;
	}
}	

核心代码:

int Prim(int m){
	int sum=0,k,min; 
	for(int i=0;i<n;i++){
		dist[i] = cost[m][i];
	}
	dist[m]=0;
	for(int i=1;i<n;i++){
		sum = 0,min = MAX;
		for(int j=0;j<n;j++){
			 if(dist[j]&&dist[j]<min){
				 min = dist[j];
				 k = j;
			}
		}
		sum += min;
		dist[k] = 0;
		for(int j=0;j<n;j++){
			if(dist[j]&&dist[j]>cost[k][j]){
				dist[j] = cost[k][j];
			}
		}
	}
	return sum;
}	

完整代码:

#include<iostream>
#include<cstring>
using namespace std;

const int M=100;
const int MAX = 1<<30;
int cost[M][M];
int dist[M]; //用来记录当前所在结点,到其他结点的边上的权值
int n; //结点数
int m; //边数
int  Prim(int m){
	int i,j,k,min,sum = 0;
	//初始化dist[]中的值,即:与m结点相连接的边上的权值
	for(i=0;i<n;i++){
		dist[i] = cost[m][i];
	}
	dist[m] = 0; //标记
	//第一个结点,不做处理
	for(i=1;i<n;i++){
		min = MAX;
		//找到dist[]中最小的值
		for(j=0;j<n;j++){
			if(dist[j]!=0&&dist[j]<min){
				min = dist[j];
				k = j;//记录下当前结点的位置
			}
		}
		sum +=min;
		dist[k] = 0;  //第k个结点取过了,标记为0
		//更新dist[],中的值,即:与k结点相连接的边的权值
		for(j=0;j<n;j++){
			if(dist[j]!=0&&dist[j]>cost[k][j]){
				dist[j] = cost[k][j];
			}
		}
	
	}
	return sum; //返回最小连通图的边上的权值总和
} 

int main()
{
	while(cin>>n>>m){
		for(int i=0;i<n;i++){
			for(int j=0;j<n;j++){
				cost[i][j] = MAX;
			}
		}
		for(int i=0;i<m;i++){
			int x,y,len;
			cin>>x>>y>>len;
			cost[x][y]=len;
			cost[y][x]=len;
		} 
		//这里以v0为起点
		cout<<Prim(0)<<endl;
	}
}
### 最小生成树 Kruskal 算法 vs Prim 算法 #### 1. 算法思想对比 Kruskal算法是一种贪心算法,其核心在于按照边的权重从小到大依次选取不构成回路的边加入最小生成树中[^2]。而Prim算法则是从任意一个顶点开始构建最小生成树,在每次迭代过程中选择连接已有的部分剩余未访问节点之间的最短路径作为新的边加入当前的部分最小生成树内[^3]。 #### 2. 初始化方式不同 对于Kruskal算法而言,初始化时只需要将所有的边按权重升序排列即可;而对于Prim算法来说,则是从某个特定起点出发,初始状态下只有该起始结点被纳入考虑范围之内[^4]。 #### 3. 数据结构的选择差异 为了高效地执行这两种算法,所采用数据结构也有所不同: - **Kruskal**: 使用并查集(Union-Find Set)来检测新增加的一条边是否会形成环路; - **Prim**: 则更倾向于利用优先队列(Priority Queue),比如二叉堆斐波那契堆等高级数据结构来进行优化处理,从而快速找到下一个最优候选边。 #### 4. 时间复杂度分析 当涉及到时间效率方面: - 对于稀疏图 (edge数远小于vertex平方),由于Kruskal主要依赖于对所有edges的操作,因此它的时间性能较好; - 而针对稠密图(edge数目接近vertex数量级), Prim因为可以更好地控制局部搜索空间,所以往往表现得更为出色一些。 #### 5. Python代码实现示例 以下是两种算法简单的Python实现版本: ##### Kruskal Algorithm Implementation ```python from collections import defaultdict class Graph: def __init__(self, vertices): self.V = vertices self.graph = [] # 添加新边的方法 def addEdge(self,u,v,w): self.graph.append([u,v,w]) # 查找函数用于查找子集合 def find(self,parent,i): if parent[i] == i: return i return graph.find(parent ,parent[i]) # 合并两个子集合 def union(self,parent,rank,x,y): rootX = self.find(parent,x) rootY = self.find(parent,y) if rank[rootX] < rank[rootY]: parent[rootX]=rootY elif rank[rootX]>rank[rootY]: parent[rootY]=rootX else : parent[rootY]=rootX rank[rootX]+=1 def kruskalMST(self): result=[] e=0 self.graph=sorted(self.graph,key=lambda item:item[2]) parent=[] rank=[] for node in range(self.V): parent.append(node) rank.append(0) while(e<self.V-1 and len(self.graph)>e): u,v,w=self.graph[e] e+=1 x=self.find(parent,u) y=self.find(parent,v) if(x!=y): result.append((u,v,w)) self.union(parent,rank,x,y) minimumCost=0 print ("Edges in the constructed MST") for u,v,weight in result: minimumCost += weight print("%d -- %d == %d"%(u,v,weight)) print("Minimum Spanning Tree",minimumCost) ``` ##### Prim's Algorithm Implementation ```python import sys class Graph(): def __init__(self,vertices): self.V=vertices self.graph=[[0 for column in range(vertices)]for row in range(vertices)] def printSolution(self,dist): print("Vertex tDistance from Source") for node in range(self.V): print(node,"t",dist[node]) def minKey(self,dist,mstSet): min=sys.maxsize for v in range(self.V): if dist[v]<min and mstSet[v]==False: min=dist[v] min_index=v return min_index def primMST(self): key=[sys.maxsize]*self.V parent=[None]*self.V key[0]=0 mstSet=[False]*self.V parent[0]=-1 for cout in range(self.V): u=self.minKey(key,mstSet) mstSet[u]=True for v in range(self.V): if self.graph[u][v]>0 and mstSet[v]==False and key[v]>self.graph[u][v]: key[v]=self.graph[u][v] parent[v]=u self.printSolution(key) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值