最小生成树的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)A : 1–2 源头:B
(2) C : 3–3 源头:C
(3)A,C不同源,即无环- 权值和 = 4+5
- 把 源头 B 对应查源数组的下标的值,改为 源头 C
回帖B–C
- 查源:
(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;
}