最小生成树:
给你一个图,里面的结点之间是连通的(同属于一个连通分量),每一条边都有一个权重,问题:如何选择边,让这些结点之间保持连通,并使得边的权重最小。
这就是一个最小生成树问题,这里介绍prim算法用来求解
prim算法概述:
- 任意选择一个结点,找出与这个结点直接相连的最小的边,并将两个结点合并。
- 将合并好的结点看成一个结点,图上其他点到这这两个结点的任意一个的距离看作到合并好的结点的距离(也即是把两个结点看作一个结点)
- 重复上面的过程,直到所有的结点都合并完成。
具体过程如图所示:
- 大致就是这样的思路,一直计算下去就是结果
- 至于正确性,这里简单说一下:
- 如果只有两个结点,显然两个直接相连就是结果
- 如果有很多个结点,因为每个结点都需要包含在结果内,所以起初任意选一个没有问题
- 接下来,任意选的这个结点,需要和其他的结点有所连接,那么与他直接相连,并且距离最短的那条路径,一定是一条最小生成树需要包含在内的路径(这里简单想一下就知道)
- 那么在一条路径把两个结点连接起来之后,就可以把这连接起来的结点看成一个结点,这样递归下去,最后的结果就是正确结果。
c++实现:
这个是实现起来还是有一点点难度的,这里为了简单,使用邻接矩阵的方式来实现。
- 定义一个con数组,用来表示各个点到合并后的点的距离。
- 定义一个vis数组,表示是否访问过某个点
具体实现:
- 首先对con数组初始化,初始即为各个点到任意选择的点的距离,因为是无向图,所以就是选择的点到其他各个点的距离。并标记为访问过最开始选择的点。
- 遍历所有结点,找到某个结点没有被访问过,并且到合并点的距离最小(其实就是con数组中没有访问过,并且最小的值),将这个点标记为访问过。
- 维护con数组,因为新增加了一个点,只需要维护别的点到这个新的点的距离即可。也就是新增的这个点到其他别的点的距离,取最小值即可。
- 每次新增一个元素,最终即可求出结果。
c++代码:
int dir[1002][1002];//dir表示图的邻接矩阵
int n//n表示结点个数,结点是[1, n]
int ans = 0;//ans表示这个最小生成树的权值和。
int con[1002];//con表示其他各个点到合并的点的距离
bool flag = false;//flag表示这个图是否是不连通的。
void prim(int start) {
int vis[1001] = {0};
//初始化con
for (int i = 1; i <= n; i++)
con[i] = dir[start][i];
//x表示当前合并的结点是由几个结点合并来的
int x = 1;
vis[start] = 1;
while (x < n) {
//这里在找最小值,mi表示当前的最小值,t表示当前最小值的下标
int mi = INF - 1, t = -1;
for (int i = 1; i <= n; i++) {
if (!vis[i] && con[i] < mi) {
mi = con[i];
t = i;
}
}
//如果t == -1 说明这个图不连通
if (t == - 1) {
flag = true;
return;
}
ans += mi;
x++;
vis[t] = 1;
//将t纳入麾下。维护con
for (int i = 1; i <= n; i++)
if (!vis[i] && dir[t][i] < con[i])
con[i] = dir[t][i];
}
}
时间复杂度为O(n平方)