一、概念
1、
最小生成树是一副连通加权无向图中一棵权值最小的生成树
如下图黑线表示的是一个最小生成树
2、
一个连通图可能有多个生成树。当图中的边具有权值时,总会有一个生成树的边的权值之和小于或者等于其它生成树的边的权值之和。广义上而言,对于非连通无向图来说,它的每一连通分量同样有最小生成树,它们的并被称为最小生成森林。
二、相关性质
1、个数多个
最小生成树在一些情况下可能会有多个
例如,当图的每一条边的权值都相同时,该图的所有生成树都是最小生成树。
例如下图
如果图的每一条边的权值都互不相同,那么最小生成树将只有一个,这一定理同样适用于最小生成森林。
2、边的权值之和最低的子图
如果图的边的权值都为正数,那么最小生成树就是该图的所有包含所有顶点的子图中权值最低的子图。
3、环定理
对于连通图中的任意一个环:如果中有边 的权值大于该环中任意一个其它的边的权值,那么这个边不会是最小生成树中的边
证明:
假设 属于最小生成树 ,那么将 删去将会使得 变为两个树。因为环 必然还存在另一横切边f可以连接两个子树形成生成树 ,且由于 < ,生成树 权值更小,与 是最小生成树矛盾。
4、最小权值边
如果图的具有最小权值的边只有一条,那么这条边包含在任意一个最小生成树中。
三、Prim(普利姆算法)
1、思想
1.从图中选取一个节点作为起始节点(也是树的根节点),标记为已达;初始化所有未达节点到树的距离为到根节点的距离;
2. 从剩余未达节点中选取到树距离最短的节点i,标记为已达;更新未达节点到树的距离(如果节点到节点i的距离小于现距离,则更新);
3. 重复步骤2直到所有n个节点均为已达。
图例 | 说明 | 不可选 | 可选 | 已选 |
---|---|---|---|---|
此为原始的加权连通图。每条边一侧的数字代表其权值。 | - | - | - | |
顶点D被任意选为起始点。顶点A、B、E和F通过单条边与D相连。A是距离D最近的顶点,因此将A及对应边AD以高亮表示。 | C, G | A, B, E, F | D | |
下一个顶点为距离D或A最近的顶点。B距D为9,距A为7,E距D为15,F距D为6。因此,F距D或A最近,因此将顶点F与相应边DF以高亮表示。 | C, G | B, E, F | A, D | |
算法继续重复上面的步骤。距离A为7的顶点B被高亮表示。 | C | B, E, G | A, D, F | |
在当前情况下,可以在C、E与G间进行选择。C距B为8,E距B为7,G距F为11。E最近,因此将顶点E与相应边BE高亮表示。 | 无 | C, E, G | A, D, F, B | |
这里,可供选择的顶点只有C和G。C距E为5,G距E为9,故选取C,并与边EC一同高亮表示。 | 无 | C, G | A, D, F, B, E | |
顶点G是唯一剩下的顶点,它距F为11,距E为9,E最近,故高亮表示G及相应边EG。 | 无 | G | A, D, F, B, E, C | |
现在,所有顶点均已被选取,图中绿色部分即为连通图的最小生成树。在此例中,最小生成树的权值之和为39。 | 无 | 无 | A, D, F, B, E, C, G
|
2、CODE:
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <set>
#include <map>
typedef long long LL;
using namespace std;
#define memset(a,n) memset(a,n,sizeof(a))
#define INF 0x3f3f3f3f
int dis[105];
int vis[105];
int mapp[105][105];
int n,m;
void Prim()
{
int sum=0;
for(int i=1;i<=m;i++)
dis[i]=mapp[1][i];
vis[1]=1;
dis[1]=0;
int minn=INF,pos=INF;
for(int i=1;i<m;i++){ // 只进行顶点-1 次
minn=INF;
pos=INF;
for(int j=1;j<=m;j++){
if(vis[j]==0&&dis[j]<minn){ // 找一个最小的边
minn=dis[j];
pos=j;
}
}
vis[pos]=1;
sum+=minn;
// 更新两点间的距离
for(int j=1;j<=m;j++)
if(vis[j]==0&&dis[j]>mapp[pos][j])
dis[j]=mapp[pos][j];
}
printf("%d\n",sum);
}
int main()
{
int be,en,v;
while(scanf("%d %d",&n,&m)&&n!=0) // m 为顶点,n 为边
{
memset(vis,0);
memset(dis,INF);
memset(mapp,INF);
for(int i=0;i<n;i++){
scanf("%d %d %d",&be,&en,&v);
if(mapp[be][en]>v) // 防止重边,只保留这两个顶点间更小的一个边
mapp[be][en]=mapp[en][be]=v;
}
Prim();
}
}
四、Kurskal(克鲁斯卡尔算法)
1、思想
- 新建图G,G中拥有原图中相同的节点,但没有边
- 将原图中所有的边按权值从小到大排序
- 从权值最小的边开始,这条边与图中已有的边不形成环,则加入该条边
- 重复3,直至图G中所有的节点都在同一个连通分量中
2、判断成不成环,需要用到 并查集
3、CODE:
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <set>
#include <map>
typedef long long LL;
using namespace std;
#define memset(a,n) memset(a,n,sizeof(a))
#define INF 0x3f3f3f3f
int pre[110];
int find(int x)
{
if(x!=pre[x]){
pre[x]=find(pre[x]);
}
return pre[x];
}
void Merge(int a,int b)
{
int root1=find(a);
int root2=find(b);
if(root1!=root2){
pre[root1]=root2;
}
}
int main()
{
int n,m,a,b;
while(scanf("%d",&n)&&n!=0){
scanf("%d",&m);
for(int i=1;i<=n;i++)
pre[i]=i;
for(int i=0;i<m;i++){
scanf("%d %d",&a,&b);
Merge(a,b);
}
int sum=0;
for(int i=1;i<=n;i++){
if(pre[i]==i)
sum++;
}
sum--;
printf("%d\n",sum);
}
}
二、与最短路区别:
最小生成树能够保证整个拓扑图的所有路径之和最小,但不能保证任意两点之间是最短路径。
最短路径是从一点出发,到达目的地的路径最小。
三、例题详解