1. 最小生成树
是一个在给定的无向图G(V,E)中求出一棵树T,使得这棵树拥有图G中的所有有顶点,且所有边都是来自于图G中的边,并且满足这棵树的边权之和是最小的
对于最小生成树需要掌握以下三个性质:
① 最小生成树是树,因此其边数是等于顶点数减1,而且树内一定不会有环
② 对于给定的图G(V,E)其最小生成树可以是不唯一的,但是边权这个是唯一的
③ 由于最小生成树是在无向图中产生的,所以其根节点可以是这棵树上的任意一个节点,于是题目中涉及了最小生成树的输出为了让最小生成树是唯一的,一般都是直接给出根节点
求解最小生成树的算法有两种,即Kruskal算法与prim算法,这两个算法都是采用了贪心的策略,只是贪心的策略是不一样
2. 下面使用的是prim算法求解最小生成树的过程,prim算法与Dijkstra算法是很类似的,只是有一个地方存在差别,就是d[]数组记录的含义是不一样的,Dijkstra算法中的是源点到其余顶点的最短距离,而这里的是顶点vi到集合S之间的距离
下面是算法的执行过程:
① 每次都是从集合V-S中选择与集合S最近的一个顶点,集合S中的顶点存放的是已经被访问过的顶点,将这个顶点记录为u,访问u并且将其加入到集合S中,同时将这条离集合S的边加入到生成树中
② 令顶点u作为集合S与集合V-S连接的接口,优化从u能够到未访问顶点与集合S的最短距离
其实对于上面的算法,我自己的理解是是否需要更新到当前顶点的距离,每一次都是选择d[]中最小的而且未被访问过的顶点,然后决定是否更新到其余顶点的距离,所以d数组的含义也可以这样理解,其他顶点到当前我这个顶点的最短距离是多少
3. 下面是C++代码写的代码:
#include<cstdio>
#include<iostream>
using namespace std;
const int maxSize = 1000;
const int INF = 2147483647;
int n, m, G[maxSize][maxSize];
int d[maxSize];
bool vis[maxSize] = {false};
int prim(){
fill(d, d + maxSize, INF);
d[0] = 0;
int ans = 0;
for(int i = 0; i < n; i++){
int u = -1, min = INF;
for(int j = 0; j < n; j++){
if(vis[j] == false && d[j] < min){
u = j;
min = d[j];
}
}
//找不到小于INF的d[u]则剩下的顶点和集合s不连通
if(u == -1) return -1;
vis[u] = true;
ans += d[u];
for(int v = 0; v < n; v++){
if(vis[v] == false && G[u][v] != INF && G[u][v] < d[v]){
d[v] = G[u][v];
}
}
}
return ans;
}
int main(void){
int u, v, w;
//n为顶点数, m为边数
scanf("%d%d", &n, &m);
fill(G[0], G[0] + maxSize * maxSize, INF);
for(int i = 0; i < m; i++){
scanf("%d%d%d", &u, &v, &w);
G[u][v] = G[v][u] = w;
}
int ans = prim();
printf("%d\n", ans);
for(int i = 0; i < n; ++i){
cout << d[i] << " ";
}
cout << endl;
}