最小生成树
首先明白两个概念,什么是生成树以及最小生成树?
相关概念引入:
- 连通:如果顶点v和v’之间由路径,则称v和v’是连通的
- 连通图:如果图中任意两个顶点都是连通的,则称其为连通图
- 连通子图:子图中的顶点为原图的子集,且子图是连通的
生成树: 一个极小连通子图,它包含图中所有顶点,但是仅包含n-1条边(n为顶点个数)
最小生成树: 在生成树的基础上,其各边权值之和最小
如何构建生成树?
MST性质: 假设N是一个连通网,U是顶点集V的一个非空子集。若(u, v)是一条具有最小权值的边,其中u属于U,v属于V-U,则必存在一棵包含边(u,v)的最小生成树。
最小生成树要求:
- 包含图中所有顶点
- n个顶点仅包含n-1条边
- 各边权值之和最小
先看要求三:权值之和最小,即每次选择权值最小的边即可。要求二:即保证图中不存在环。要求一:每个顶点都要被选中。下面基于prim算法介绍如何构建最小生成树。
prim算法
构造过程:
- 首先选择一个开始顶点(因为最小生成树要求是包含所有顶点,因此选择哪个开始顶点并不会影响最终结果)
- 将选中顶点放入集合U中,选择集合U中的顶点到V-U中的最短路径。
- 将选中的顶点(V-U内的顶点)放入集合U
- 重复步骤2、3,直至U = V
如上图所示:首选顶点V1,将V1加入集合U。选择V1到V-U内顶点距离最短的顶点V3,将V3加入集合U。选择U内顶点到V-U内顶点距离最近的顶点,这里需要注意的是:是从全局的角度选择,不是从单一顶点选择最近的顶点。例如上图,选择V4结束,没有可选的后,并没有回溯到V6,选择V6的邻接点,而是选择了V3->V2,因为V3->V2的距离为5,而V6->V5的距离为6。重复上述步骤,直至图f所示(U = V)。
算法步骤:
首先我们选择邻接矩阵存储图中各顶点间的关系。使用一个标记数组flag,标记加入U内的顶点。为了避免重复扫描,可以借助两个数组一个记录U内顶点到V-U内顶点的最短距离,另一个记录V-U内顶点到U内的最邻接点。
- 初始化上述各数组,存储图的二维矩阵每个元素初始化为无穷大。标记数组全为false(表示U内无顶点)。记录距离和邻接点的数组全为0。
- 给出图中各顶点之间的关系,且给出开始顶点
- 根据开始顶点更新U到V-U内各顶点的距离(邻接矩阵对应行与各邻接点之间的关系),以及记录邻接点的数组
- 找出记录距离数组中到U到V-U内顶点的最小值,对应顶点加入U,更新U到V-U内顶点的距离关系和邻接点关系
- 重复步骤4,直至U = V
代码示例:
#include<iostream>
using namespace std;
#define MAX_SIZE 100
#define INT 1e7
int graph[MAX_SIZE][MAX_SIZE];
bool flag[MAX_SIZE];
int close[MAX_SIZE];
int low[MAX_SIZE];
void init_array(){
for(int i = 0; i < MAX_SIZE; i++){
for(int j = 0; j < MAX_SIZE; j++)
graph[i][j] = INT;
}
}
void init_graph(int m){
for(int i = 0; i < m; i++){
int x, y, z;
cin >> x >> y >> z;
graph[x][y] = z;
graph[y][x] = z;
}
}
void prim(int n, int s){
flag[s] = true;
for(int i = 1; i <= n; i++){
if(i != s){
close[i] = 1;
low[i] = graph[s][i];
}
}
for(int i = 1; i <= n; i++){
int min = INT, t = -1;
for(int j = 1; j <= n; j++){
if(!flag[j] && low[j] < min){
min = low[j];
t = j;
}
}
if(t == s) return;
flag[t] = true;
for(int j = 1; j <= n; j++){
if(!flag[j] && graph[t][j] < low[j]){
low[j] = graph[t][j];
close[j] = t;
}
}
}
}
int calculate_weight(int n){
int sum = 0;
for(int i = 1; i <= n; i++)
sum += low[i];
return sum;
}
int main(){
int n, m, s;
cin >> n >> m >> s;
init_array();
init_graph(m);
prim(n, s);
calculate_weight(n);
return 0;
}
观察代码,每次查找最小值要O(n)的时间,此外共需要执行n次,因此时间复杂度为O(n^2)。