最小生成树的Kruskal算法-详解

最小生成树的Kruskal算法


一、 什么是最小生成树


1.1 最小生成树定义:

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


1.2 例子:
在这里插入图片描述
在这里插入图片描述

通俗易懂的讲就是最小生成树包含原图的所有节点而只用最少的边和最小的权值距离


1.3 理解 最小生成树:

将最小生成树拆分成: 最小 生成 树

树:
• 树中没有环
• 所有的顶点都要在树中
• 对于N条顶点,有N-1条边

最小:
一个图,可以有很多生成树,我们把一棵树的权值相加,得到权值和。因此不同的生成树就会有不同的权值和,而最小生成树就是权值和最小生成树。


1.4 如何求最小生成树?

• Kruskal算法(克鲁斯卡尔),直接选择权值最小的边

• Prim算法(普里姆算法) 从顶点出发间接选择权值最小的边

二、Kruskal算法(克鲁斯卡尔)求解最小生成树

第一步:

将图中所有的边,都取出放入一个列表中。并按照边的权值由小到大的顺序排序
在这里插入图片描述

第二步:

初始化,查源数组(并查集),判断当前图是否存在环
在这里插入图片描述

第三步:

回贴过程,按次序每次取出一条边,回帖到图中,每回帖一条新边都要进行判断,当前图的状态中是否存在环。
利用查源数组(并查集)来判断当前图是否存在环

回帖A–B

(1)查源:
        1.A : 1–1 源头:A
        2.B : 2–2 源头:B
        3.A,B不同源,即无环
(2)权值和 =4
(3)把 源头 A 对应查源数组的下标的值,改为 源头 B
在这里插入图片描述

回帖A–C
  1. 查源:
    (1)A : 1–2 源头:B
    (2) C : 3–3 源头:C
    (3)A,C不同源,即无环
  2. 权值和 = 4+5
  3. 把 源头 B 对应查源数组的下标的值,改为 源头 C
    在这里插入图片描述
回帖B–C
  1. 查源:
    (1) B : 2–3 源头:C
    (2)C : 3–3 源头:C
    (3)A,C同源,有环。跳过

    最小生成树为:
    在这里插入图片描述
    最小权值和=9
    注:其实直接可以设置,回帖图的边的数目到达N-1时即可退出程序。

三、 最小生成树的Kruskal算法代码

/**
...project: Kruskal算法的设计
...time:    09/05/2020
...author:  @DUDU
**/

#include<iostream>
using namespace std;
char node[1000];

/**  (1)图的存储结构  **/
//节点集 
typedef struct Node{
	char a,b;  //起点和终点 
	int w;     //权值 
}Node; 
//边集
typedef struct{
	int len,n;  //长度和顶点的个数 
	Node *list; //指针 
}Graph;


/**  (2)线性表的初始化  **/
int InitList(Graph &G){
	G.len=0;
	G.list=new Node[1000];
	return 0;
} 

/**  (3)线性表赋初值 **/
int GetList(Graph &G){
	cout<<"输入节点的个数和边的条数"<<endl;
	cin>>G.len>>G.n;
	cout<<"输入节点的名称"<<endl;
	for(int i=1;i<=G.len;i++)
		cin>>node[i];
	cout<<"输入边起点,终点,权值"<<endl;
	G.list[0].w=0;
	for(int i=1;i<=G.n;i++)
		cin>>G.list[i].a>>G.list[i].b>>G.list[i].w;
	return 0;
} 

/**  线性表的按权值插入排序 **/
int InsertList(Graph G){
	int j=0;
	for(int i=2;i<=G.n;i++){
		if(G.list[i].w<G.list[i-1].w){
			G.list[0]=G.list[i];
			G.list[i]=G.list[i-1];
			for(j=i-2;G.list[0].w<G.list[j].w;j--)
			    G.list[j+1]=G.list[j];
			G.list[j+1]=G.list[0];
		}		   
	}
	return 0;
}

/**  (4)查找源头  **/
int GetRoot(Graph G,int V[],char x){
	int y;
	//将要查询的数转化为并查集数
	for(int i=1;i<=G.len;i++)
		if(node[i]==x)y=i;
	//查源 
	while(V[y]!=y)
	    y=V[y];
	cout<<x<<"的源头为:"<<y<<endl; 
	return y;
	
}

/** 输(5)出选择路径  **/
int Pop(Graph G,int data[],int t){
	int m;
	//输出 
	cout<<"选择的边和权值为:"<<endl; 
	for(int i=0;i<t;i++){
		m=data[i];
		cout<<G.list[m].a<<"-->"<<G.list[m].b<<" :"<<G.list[m].w<<endl; 
	}
	return 0;
}

/**  Kruskal算法  **/
int Kruskal(Graph G){
    //记录最短路径 
	int sum=0,t=0;  
	int data[1000];
	//并查集
	int V[1000]; 
	//并查集初始化 
	for(int i=1;i<=G.len;i++)
		V[i]=i;
	//算法核心 
	for(int i=1;i<=G.n;i++){
		int v1,v2;
		//查找源头
		v1=GetRoot(G,V,G.list[i].a); //起点源头 
		v2=GetRoot(G,V,G.list[i].b); //终点源头 
		if(v1!=v2){
		    sum+=G.list[i].w;
			V[v1]=v2; //A--C: V[A的源头B] = C的源头C
			for(int k=1;k<=3;k++)
			cout<<"V["<<k<<"]="<<V[k]<<endl;
			data[t]=i; //记录回帖的边 
			t++;
		}
	} 
	
	Pop(G,data,t);
	return sum;
}


int main(){
	Graph G;
	InitList(G);
	GetList(G);
	InsertList(G);
	cout<<"最短路径为: "<<Kruskal(G);
	return 0;
} 
Kruskal算法是一种用于查找图的最小生成树的方法。它的基本思想是将图中的所有边按照权值从小到大进行排序,然后依次选择权值最小的边,如果这条边的两个顶点不在同一个连通分量中,就将这条边加入最小生成树的边集合中,并将这两个顶点合并到同一个连通分量中。重复这个过程,直到最小生成树的边集合中包含了图中的所有顶点,或者边集合中的边数达到了N-1(N为图中顶点的个数)。 以下是使用Kruskal算法求图的最小生成树的步骤: 1. 将图中的所有边按照权值从小到大进行排序。 2. 创建一个空的边集合,用于存储最小生成树的边。 3. 创建一个空的连通分量集合,用于存储图中的顶点的连通分量。 4. 遍历排序后的边集合,依次选择权值最小的边。 5. 如果这条边的两个顶点不在同一个连通分量中,将这条边加入最小生成树的边集合中,并将这两个顶点合并到同一个连通分量中。 6. 重复步骤4和步骤5,直到最小生成树的边集合中包含了图中的所有顶点,或者边集合中的边数达到了N-1。 以下是一个使用Kruskal算法求图的最小生成树的示例代码: ```python class UnionFind: def __init__(self, n): self.parent = list(range(n)) self.rank = [0] * n def find(self, x): if self.parent[x] != x: self.parent[x] = self.find(self.parent[x]) return self.parent[x] def union(self, x, y): root_x = self.find(x) root_y = self.find(y) if root_x != root_y: if self.rank[root_x] < self.rank[root_y]: self.parent[root_x] = root_y elif self.rank[root_x] > self.rank[root_y]: self.parent[root_y] = root_x else: self.parent[root_y] = root_x self.rank[root_x] += 1 def kruskal(graph): edges = [] for u in range(len(graph)): for v in range(u + 1, len(graph)): if graph[u][v] != 0: edges.append((u, v, graph[u][v])) edges.sort(key=lambda x: x[2]) uf = UnionFind(len(graph)) mst = [] for edge in edges: u, v, weight = edge if uf.find(u) != uf.find(v): uf.union(u, v) mst.append(edge) return mst # 示例图的邻接矩阵表示 graph = [ [0, 2, 0, 6, 0], [2, 0, 3, 8, 5], [0, 3, 0, 0, 7], [6, 8, 0, 0, 9], [0, 5, 7, 9, 0] ] mst = kruskal(graph) vertex_set = set() edge_set = set() for edge in mst: u, v, weight = edge vertex_set.add(u) vertex_set.add(v) edge_set.add((u, v)) print("顶点集合:", vertex_set) print("边的集合:", edge_set) ``` 运行以上代码,可以得到图的最小生成树的顶点集合和边的集合。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值