- 一个连通图的
生成树
是图的一个极小连通子图,它包含所有顶点,但只有足以构成树的n-1
条边 - 这意味着对生成树来说,砍去它的任何一条边,就会使生成树变成非连通图,若给他增加一条边就会形成一条回路
最小生成树
:权值最小的那颗生成树叫~最小生成树的性质:
- 最小生成树并不唯一,准确的来说是
最小生成树的树形并不唯一
- 最小生成树的权值之和唯一,并且是最小的
- 最小生成树的边数=顶点数-1
- 最小生成树并不唯一,准确的来说是
求最小生成树有两种经典算法:
普里姆算法(prim)
和克鲁斯卡尔(kruskal)算法
普里姆算法求最小生成树代码(粘贴即能跑)
#include <iostream>
#include<stdlib.h>
#define maxSize 100
#define infinity 65535
using namespace std;
typedef struct{
char vnode[maxSize];
int edge[maxSize][maxSize];
int n,e;
}MGraph;
void createMGraph(MGraph &G){//邻接矩阵构造图
int i,j,n,e,k,w;
cout<< "请输入图的顶点数和边数"<<endl;
cin>> G.n >> G.e;
n=G.n;
e=G.e;
cout<< "请输入顶点"<<endl;
for(i=0;i<n;i++){
cin>> G.vnode[i];
}
for(i=0;i<n;i++){
for(j=0;j<n;j++){
G.edge[i][j]=infinity;
}
}
cout<< "请输入边的下标i,j"<<endl;
for(k=0;k<e;k++){
scanf("%d %d %d",&i,&j,&w);
G.edge[i][j]=w;
G.edge[j][i]=G.edge[i][j];
}
}
void minSpanTree_prim(MGraph G){//prim算法求最小生成树
int n,e,i,j,min,k,t;
n=G.n;
int lowcost[maxSize];//为0表示顶点加入最小生成树,其他存放边的权值
int adjvex[maxSize];//这个存放顶点下标,标明顶点是新增顶点,还是之前遍历过的顶点
lowcost[0]=0;//把首个顶点(下标为0的顶点)加入到最小生成树
adjvex[0]=0;//下标为0
for(i=1;i<n;i++){//循环0下标顶点与其他顶点的连接情况 (不从0开始是因为0 0表示自己和自己的环)
lowcost[i]=G.edge[0][i];//把0下标顶点和其他顶点组成的边的权值存放到lowcost数组中
adjvex[i]=0;//当前lowcost数组中的边的权值的起始顶点全部都是下标为0的顶点,而结束顶点则是序号为lowcost数组下标的顶点
}
cout<<"最小生成树为:"<<endl;
for(t=1;t<n;t++){//循环所有顶点,构造最小生成树
min=infinity;//初始化min,刚开始为一个极大值
j=1;k=0;
while(j<n){//遍历lowcost数组
if(lowcost[j]!=0&&lowcost[j]<min){//除去已加入最小生成树的顶点
min=lowcost[j];//找出lowcost数组中最小的权值,并赋值给min
k=j;//记录最小权值的下标 (这个k其实就是权值最小的那条边的结束顶点)
}
j++;
}
printf("(%d,%d)\n",adjvex[k],k);//打印权值最小的那条边的起始顶点和结束顶点
lowcost[k]=0;//把k下标的顶点加入到最小生成树
for(i=1;i<n;i++){//遍历顶点
if(lowcost[i]!=0&&G.edge[k][i]<lowcost[i]){//要除去已加入最小生成树的顶点
lowcost[i]=G.edge[k][i];//在k结点与其他顶点邻接的权值和lowcost数组中取较小的一方更新lowcost
adjvex[i]=k;//记录较小权值的边的起始顶点下标
}
}
}
}
int main(int argc, char** argv) {
MGraph G;
createMGraph(G);
minSpanTree_prim(G);
return 0;
}
/*
示例输入:
顶点数和边数: 9 15
输入顶点: 0 1 2 3 4 5 6 7 8
输入顶点下标和权值:
4 7 7
2 8 8
0 1 10
0 5 11
1 8 12
3 7 16
1 6 16
5 6 17
1 2 18
6 7 19
3 4 20
3 8 21
2 3 22
3 6 24
4 5 26
*/
克鲁斯卡尔算法构造最小生成树代码:
#include <iostream>
#include<algorithm>
#include<stdlib.h>
#define maxSize 100
#define infinity 65535
using namespace std;
typedef struct{//邻接矩阵构造的图结点
char vnode[maxSize];
int edge[maxSize][maxSize];
int n,e;
}MGraph;
typedef struct{//边集结点(存放边的顶点下标和边的权重)
int start;//边起点
int end;//边终点
int w;//边权值
}Road;
Road road[maxSize];//边集数组
int parent[maxSize];
int getRoot(int i){//此函数用于找到下标为i的顶点在生成树中的父节点 (并查集)
while(parent[i]!=i){
i=parent[i];
}
return i;
}
bool compare(Road x,Road y){//自定义结构体比较方式,按结构体中的权值升序排
return x.w<y.w;
}
void createMGraph(MGraph &G){//创建图
int i,j,n,e,k,w;
cout<< "请输入图的顶点数和边数"<<endl;
cin>> G.n >> G.e;
n=G.n;
e=G.e;
cout<< "请输入顶点"<<endl;
for(i=0;i<n;i++){
cin>> G.vnode[i];
}
for(i=0;i<n;i++){
for(j=0;j<n;j++){
G.edge[i][j]=infinity;
}
}
cout<< "请输入边的下标i,j"<<endl;
for(k=0;k<e;k++){
scanf("%d %d %d",&i,&j,&w);
G.edge[i][j]=w;
road[k].start=i;//创建的时候就给边集数组赋值
road[k].end=j;
road[k].w=w;
G.edge[j][i]=G.edge[i][j];//根据无向图邻接矩阵的对称性赋值
}
}
int kruskal(MGraph G){//克鲁斯卡尔算法
int a,b,sum=0;
int e=G.e;
for(int i=0;i<G.e;i++){//初始化根结点下标数组
parent[i]=i;//为存放根节点下标数组赋初值,自身作为自己的根节点
}
sort(road,road+e,compare);//按边集数组中的权值由小到大排序
for(int i=0;i<e;i++){//遍历已经排好序的边
a=getRoot(road[i].start);//获取开始顶点在生成树中的父节点
b=getRoot(road[i].end);//获取终结顶点在生成树中的父节点
if(a!=b){//当ab不等说明两者不是同一个父节点,不会构成环
parent[a]=b;//把b点作为孩子加在a的后面
printf("(%d,%d)\n",road[i].start,road[i].end);//打印构成最小生成树的边
sum+=road[i].w;//最小生成树的权值总和
}
}
printf("sum=%d\n",sum);
return sum;
}
int main(int argc, char** argv) {
MGraph G;
createMGraph(G);
kruskal(G);
return 0;
}
/*
示例输入:
顶点数和边数: 9 15
输入顶点: 0 1 2 3 4 5 6 7 8
输入顶点下标和权值:
4 7 7
2 8 8
0 1 10
0 5 11
1 8 12
3 7 16
1 6 16
5 6 17
1 2 18
6 7 19
3 4 20
3 8 21
2 3 22
3 6 24
4 5 26
*/