一、基本概念
1、生成树定义:在一个|V|个点的无向连通图中,取|V|-1条边,并连接所有的顶点,所得到的子图为原图的一颗生成树。
2、数的属性:树是图的一种特殊形态。图G是树当且仅当以下任意一个条件成立:
① G有 |V| - 1 条边,无环;
② G有 |V| - 1 条边,连通;
③ 任意两点之间只有唯一的简单路径;
④ G连通,但任意删除一条边后就不连通。
3、最小生成树:在一个带权的无向连通图中,各边权和最小一颗生成树即为原图的最小生成树。
4、最小边原则:图中权值最小的边(如果唯一的话)一定在最小生成树上。
5、唯一性定理:对于一个图 G ,如果图中的边权值都不相同,则图的最小生成树唯一。
二、计算无向图的最小生成树的两种算法
【Prim算法】
※算法思想※
Prime算法是一种贪心算法,它最初将无向图G中所有顶点 V 分成两个顶点集合
V
A
V_A
VA 和
V
B
V_B
VB。在计算过程中
V
A
V_A
VA 中的点为已选好链接入生成树的点,否则属于
V
B
V_B
VB。最开始时
V
A
V_A
VA 值包含任意选取的图G中的一个点 u,其余点属于
V
B
V_B
VB ,每次添加一个
V
B
V_B
VB 中的点到
V
A
V_A
VA ,该点是集合
V
B
V_B
VB 到
V
A
V_A
VA 中距离最小的一个点。直到 V 个顶点全部属于
V
A
V_A
VA,算法借书。显然出发点不同,最小生成树的形态就不同,但边权和的最小值是唯一的。
※算法步骤※
选定图中的任意一个顶点
V
0
V_0
V0 ,从
V
0
V_0
V0 开始生成最小生成树;
(1)初始化 dist[v0] = 0 ,其他点的距离 dist[i] = ∞。其中dist[i]表示集合
V
B
V_B
VB 中的点到
V
A
V_A
VA中的点的距离。
(2)经过 N 次如下步骤操作,最后得到一颗含 N 个顶点,N-1条边的最小生成树:
①选择一个未标记的点 k ,并且 dist[k]的值是最小的;
②标记点 k 进入集合
V
A
V_A
VA;
③以 k 为中间点,修改未标记点 j ,即
V
B
V_B
VB中点到
V
A
V_A
VA的距离值;
(3)得到最小生成树 T。
任意时刻的连边状态都是一棵树;从某一个点开始,每次都花费最小的代价,用一条边加进一个新的点。
※算法实现※
//时间复杂度为O(n),优化需要使用堆来优化求最小生成树的n-1条边
#include<iostream>
#include<algorithm>
#include<cstring>
#include<string>
using namespace std;
#define ll long long
#define inf 0x3f3f3f3f
const int N = 1e4+10;
//vis表示是否加入树,d表示点与当前生成树中的点有连边的边长最小值
int vis[N],d[N],g[N][N];
int n,m,ans=0;
void read(){
int x,y,w;
cin >> n >> m;
//memset(g,inf,sizeof g);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
g[i][j] = inf;
for(int i=1;i<=m;i++){
cin >> x >> y >> w;
g[x][y] = g[y][x] = w;
}
}
void Prim(int v){
int minn,k;
memset(vis,0,sizeof(vis));
for(int i=1;i<=n;i++)
d[i] = inf;
d[v] = 0;
ans = 0;
for(int i=1;i<=n;i++){
minn = inf;
for(int j=1;j<=n;j++){
if(vis[j]==0&&minn>d[j]){
minn = d[j];
k = j;
}
}
vis[k] = 1;
ans += d[k];
for(int j=1;j<=n;j++)
if(vis[j]==0&&d[j]>g[k][j])
d[j] = g[k][j];
}
}
int main()
{
read();
Prim(1);
cout << ans <<endl;
return 0;
}
【Kruskal算法】
※算法思想※
Kruskal算法也是一种贪心算法,它是将边按照权值排序,每次从剩余的边集中选择权值最小且两个端点不在同一集合的边加入生成树中,反复操作,直到加入了n-1条边。
※算法步骤※
- 将图G中的边按照权值从小到大进行快排。
- 按照权值从小到大以此选边。
- 重复2的操作直到生成树中包含 n-1 条边。否则遍历完所有边后,选取不到 n-1 条边,则最小生成树不存在。
※算法实现※
#include<iostream>
#include<algorithm>
#include<cstring>
#include<string>
using namespace std;
#define ll long long
#define inf 0x3f3f3f3f
const int N = 1e5+10;
struct edge{
int x,y,z;
}a[N];
int n,m,prt[N],ans=0,bj;
bool cmp(edge &x,edge &y){
return x.z<y.z;
}
int Getfather(int x){
if(prt[x] == x) return x;
prt[x] = Getfather(prt[x]);
return prt[x];
}
void kruskal(){
int f1,f2,k;
k = 0;
for(int i=1;i<=n;i++)
prt[i] = i;
for(int i=1;i<=m;i++){
f1 = Getfather(a[i].x);
f2 = Getfather(a[i].y);
if(f1!=f2){
ans = ans+a[i].z;
prt[f1] = f2;
k++;
if(k == n-1) break;
}
}
if(k<n-1){
cout << "Impossible" << endl;
bj = 0;
return;
}
}
int main()
{
cin >> n >> m;
ans = 0;
bj = 1;
for(int i=1;i<=m;i++)
cin >> a[i].x >> a[i].y >> a[i].z;
sort(a+1,a+1+m,cmp);
kruskal();
if(bj) cout << ans <<endl;
return 0;
}