什么是生成树?
一个连通图的生成树是指一个连通子图,它含有图中全部n个顶点,但只有足以构成一棵树的n-1条边。一颗有n个顶点的生成树有且仅有n-1条边,如果生成树中再添加一条边,则必定成环。
什么是最小生成树?
在连通网的所有生成树中,所有边的代价和最小的生成树,称为最小生成树。
注意是在无向图中。
求解最小生成树的两种经典方法!
-
Kruskal 克鲁斯卡尔算法 :(加边法)
-
Prim 普里姆算法算法 (加点法)
算法实现:
Prim 普里姆算法算法 (加点法)
/*
2020-5-28
无向图
最小代价生成树
Prim算法: 普里姆算法
思想: 从图中任意取出一个顶点,把这个顶点当做一颗树,然后选取这颗树相邻接的边中,最短的边(权重最小)
,然后将选中这条最短的边的顶点也并入这颗树。此时树中有两个顶点。然后又从这颗新的树,选取相邻接最短的边,并将
这条选中边的顶点并入树中。一直重复上诉步骤,直到图中所有定点都并入树中。
算法过程: lowCost[i] 生成树中到顶点i最短的一条边的权重
从图中任取一个顶点 v0开始,构造生成树
1:将v0到其他所有顶点的所有边都当做候选边
2:重复以下步骤n-1次,使得n-1个顶点都被并入到生成树中
1: 从候选边中选取权重最小的边,并将该边的另一端相接的顶点v并入生成树中
2: 对剩余顶点vi ,if (v,vi)的权值比lowCost[i] 小,则更新lowCost[i]=(v,vi)权值
*/
#include<iostream>
using namespace std ;
#define MaxSize 50 //图最大顶点个数
#define inf 999999999 //无穷大值,在图中表示不连通
typedef int datatype; //可以根据需要改变数据类型,也可以自己封装数据类型
//邻接矩阵
typedef struct{
int num; //顶点编号
char info; //顶点信息
}Node ; // 顶点类型
typedef struct{
datatype Weight[MaxSize][MaxSize] ; //保存图的,边集权重信息
int n,e ; // n 顶点的总数, e 边数
Node nodes[MaxSize] ; //保存顶点信息
}MGraph;
int visited[MaxSize] ; //访问记录数组
int pre[MaxSize] ; //保存当前访问结点的前一个已经访问节点
/* 构造图的初始函数 */
MGraph InitiaL_Graph(){
MGraph graph ;
int n ,e ; // n: 结点数 e: 边数
int i, j ;
cout<<"初始化图操作:请输入你要构造的图的 顶点数(n) 和边数(e)"<<endl ;
cout<<"请输入结点数n: \t";
cin>>n;
cout<<"请输入变数e: \t";
cin>>e;
graph.n = n ;
graph.e = e ;
for(i=1;i<=n;i++ ){
for(j=1;j<=n;j++){
graph.Weight[i][j] =inf; 把每条边赋值-inf,表示不连通
}
}
cout<<"请输入边值权重"<<endl ;
cout<<" edgs[i][j] = weight,如果没有图中没有weight,请全输入1占位: 输入格式 i ,j ,weight"<<endl ;
while(e--){
datatype weight ;
cin>>i>>j>>weight ;
graph.Weight[i][j] = weight ; //注意是无向边, 所有 (i,j) =(j,i)
graph.Weight[j][i] = weight ;
}
return graph ;
}
/*
Prim:
graph: 图
v : 图中任意一个顶点
return : sumCost 图->生成树的最小代价
注意: 如果图数据比较多,用引用或者指针传递参数
*/
int Prim(MGraph graph , int v )
{
int n = graph.n ; //图中顶点 个数
int sumCost = 0 ; //生成树最小代价
int lowCost[n] ; //记录生成树,到图中还未被并入的顶点边的最小权值
int vset[n] ; // vset[i] 标记顶点i 是否并入生成树中
int i,j ,k,index,minWeight ;
for(i = 1 ;i<=n;i++){ //初始化生成树只有一个顶点V
lowCost[i] =graph.Weight[v][i] ; //到图中所有顶点最小权值, 不通的顶点为inf
vset[i] = 0 ; // 0: 表示未被并入生成树中 1: 并入
}
k=v ; //打印并入生成树的结点
vset[v] =1 ; //第一个顶点并入,还剩下n-1 个
cout<<"图中第"<<k++<<"个并入生成树结点"<<v<<endl ;
for(i =1; i<n;i++){
minWeight = inf ;
for(j=1;j<=n;j++){ //找出候选边中最小者
if(vset[j]==0&&lowCost[j]<minWeight){ //注意是图中剩余的顶点vset[j]=0
minWeight = lowCost[j];
index = j ;
}
}
vset[index] =1 ; //选中最小的顶点 index ,并入生成树中
sumCost+=minWeight ; //代价求和
v=index ; //更新生成树中新来的顶点到到图中还未被并入的顶点边的最小权值
cout<<"图中第"<<k++<<"个并入生成树结点是: "<<v<<" weight = "<<minWeight<<endl ;
for(j=1 ;j<=n;j++){
if(vset[j]==0 && graph.Weight[v][j]<lowCost[j]){
lowCost[j] = graph.Weight[v][j] ;
}
}
}
return sumCost ;
}
int main()
{
MGraph graph ;
int cost ;
graph = InitiaL_Graph() ; //初始化图
cost =Prim(graph,1) ;//从图中顶点1开始生成最小生成树
cout<<"这个图生成最小生成树代价"<<endl ;
cout<<cost <<endl ;
return (1) ;
}
/*
input: n=5 e=8
1 2 5
1 3 1
1 4 2
2 3 3
2 4 5
3 4 6
3 5 2
4 5 3
*/
Kruskal 克鲁斯卡尔算法 :(加边法)
/*
@date: 2020-5-28
@author:wby
克鲁斯卡尔 Kruskal算法:
算法步骤:
1:将图中边按照权值从小到大排序
2:然后从最小边开始,检测当前边是否为候选边(该边的并入不会构成回路)
if yes ,将此边并入生成树中,下一条边继续检测,
else: 下一条边继续检测
直到到所有边都被检测完为止
*/
#include<iostream>
#include <algorithm>
using namespace std ;
#define MaxSize 50 //图最大顶点个数
#define inf 999999999 //无穷大值,在图中表示不连通
typedef int datatype; //可以根据需要改变数据类型,也可以自己封装数据类型
typedef struct{ //存储图中所有的边 (u,v) 和这条边上的权值
int u ;
int v ;
datatype weight ;
}edge;
//邻接矩阵
typedef struct{
datatype Weight[MaxSize][MaxSize] ; //保存图的,边集权重信息
int n,e ; // n 顶点的总数, e 边数
}MGraph;
int unioFindSet[MaxSize] ; //并查集
edge edges[MaxSize] ;//保存图中所有的边,全局变量,便于传参
/* 构造图的初始函数 */
MGraph InitiaL_Graph(){
MGraph graph ;
int n ,e ; // n: 结点数 e: 边数
int i, j ;
cout<<"初始化图操作:请输入你要构造的图的 顶点数(n) 和边数(e)"<<endl ;
cout<<"请输入结点数n: \t";
cin>>n;
cout<<"请输入变数e: \t";
cin>>e;
graph.n = n ;
graph.e = e ;
for(i=1;i<=n;i++ ){
for(j=1;j<=n;j++){
graph.Weight[i][j] =inf; 把每条边赋值-inf,表示不连通
}
}
int k=1 ;
cout<<"请输入边值权重"<<endl ;
cout<<" edgs[i][j] = weight,如果没有图中没有weight,请全输入1占位: 输入格式 i ,j ,weight"<<endl ;
while(e--){
datatype weight ;
cin>>i>>j>>weight ;
graph.Weight[i][j] = weight ; //注意是无向边, 所有 (i,j) =(j,i)
edges[k].u = i; //保存边
edges[k].v = j ;
edges[k].weight =weight ;
k++ ;
}
return graph ;
}
/*
getRoot:
v: 任意一个结点
return : v结点的根结点
*/
int getRoot(int v) //在并查集中查找根结点
{
while(v!=unioFindSet[v]){
v= unioFindSet[v] ;
}
return v ;
}
/* 结构体排序规则 权值按照从小到大,将所有边排序 */
int compare(const edge &edge1 ,const edge &edge2)
{
return edge1.weight < edge2.weight ;
}
/*
Kruskal:
graph: 给定一个图 邻接矩阵存储
return : 最小生成树代价
*/
int Kruskal(MGraph graph )
{
datatype minCost = 0 ;
int u,v ; //(u,v) 边
int i;
int n ,e ;
n = graph.n ; //顶点个数
e = graph.e ; //边个数
for(i = 1;i<=n;i++){ //初始化
unioFindSet[i]= i ; //开始所有结点的根结点都是自己, n 个结点n 颗树
}
//结构体排序:权值按照从小到大,将所有边排序
sort(edges+1,edges+e+1,compare) ;//图中顶点从1开始,没用0这个位置。
cout<<"边集排序"<<endl ;//输出排序后的情况
for(i=1 ;i<=e;i++)
cout<<"( "<<edges[i].u<<" ,"<<edges[i].v<<") weight= "<<edges[i].weight<<endl ;
for(i=1;i<=e;i++){
int a=edges[i].u ; //当前边(a,b)
int b =edges[i].v ;
u = getRoot(a); //查找当前结点的根结点
v = getRoot(b) ;
if(u!=v){ //如果a,b 的根结点不同,将它们合并到生成树中
unioFindSet[u] =v ;
minCost+=edges[i].weight ;
cout<<"将边("<<a<<" ,"<<b<<" )并入生成树中,"<<" weight: "<<edges[i].weight <<endl ;
}
}
return minCost ;
}
int main()
{
MGraph graph;
int cost ;
graph =InitiaL_Graph() ;
cost = Kruskal(graph) ;
cout<<"Kruskal求最小生成树花费代价:" ;
cout<<cost<<endl ;
return 1 ;
}
/*
input:n =5 e=8
1 2 5
1 3 1
1 4 2
2 3 3
2 4 5
3 4 6
3 5 2
4 5 3
*/
Reference:
[1]: 算法导论–第三版