问题描述
假设现有7个村庄,需要在村庄之间架设电缆。在保证每一个村庄都有电缆链接的前提下,总的电缆长度最小。
该问题用无向连通图 G = ( V , E ) G=(V,E) G=(V,E)来表示电缆链接网络, V V V表示顶点集, E E E表示边集。把各个村庄抽象为图中的顶点,顶点与顶点之间的边表示村庄之间电缆网络,边的权值表示两个村庄之间的电缆费用。如果两个顶点之间没有连线,代表两个村庄之间架设电缆,费用为无穷大。对应 n n n个顶点只需要 n − 1 n-1 n−1条边就可以使这个图连通。只要必须无回路才能保证 n − 1 n-1 n−1条边连通。
所以,我们只需找到 n − 1 n-1 n−1条权值和最小并且无回路的边即可。
强烈推荐小白下载这个APP,快速搞懂算法运行流程。
算法动态图解:链接:https://pan.baidu.com/s/1mX3s7VjLTKLr7MZhAQO-6Q
提取码:cv5y
相关概念:
- 子图:从子图中选出一些顶点和边组成的图,称为原图的子图。
- 生成子图:选出一些边和所有顶点组成的图,称为原图的生成子图。
- 生成树:如果生成子图恰好是一棵树(无回路),称为生成树。
- 最小生成树:权值之和最小的生成树,称为最小生成树。
这几个概念的范围是逐渐递减的。
算法流程
算法关键的两点是无回路连通 和权值最小,权值最小想到的是我们的贪心算法,在选择下一个村庄的时候,我们都会基于当前选出架设电缆长度最短的村庄。无回路连通这里采用的是集合的概念, A A A集合和 B B B集合之间点连接的边,取出集合之间连接的边就能够防止回路连通。因为只能集合内的点连接会导致回路。所有的顶点所在的集合称为 V V V集合,权值最小的边关联的节点所在的集合称为 U U U集合,剩下的顶点为 V − U V-U V−U集合。
- 1.初始化
- 2.找到两个集合之间的最短边
- 3.加入到 U U U集合
- 4.更新集合之间的边连接
- 5.重复2-4之间的步骤直到 n − 1 n-1 n−1条边连接。
源代码
#include <iostream>
using namespace std;
const int INF = 0x3fffffff;
const int N = 100;
bool s[N];
int closest[N];
int lowcost[N];
void Prim(int n, int u0, int c[N][N])
{
//如果s[i]=true,说明顶点i已加入最小生成树
//的顶点集合U;否则顶点i属于集合V-U
//将最后的相关的最小权值传递到数组lowcost
s[u0] = true;
int i;
int j;
for (i = 1; i <= n; i++)
{
if (i != u0)
{
lowcost[i] = c[u0][i];
closest[i] = u0;
s[i] = false;
}
else
lowcost[i] = 0;
}
//2.找到两个集合之间的最短边
for (i = 1; i <= n; i++)
{
int temp = INF;
int t = u0;
for (j = 1; j <= n; j++)
{
if ((!s[j]) && (lowcost[j] < temp))
{
t = j;
temp = lowcost[j];
}
}
//3.加入到$U$集合
if (t == u0)
break; //找不到t,跳出循环
s[t] = true;
// 4.更新集合之间的边连接
for (j = 1; j <= n; j++) //更新lowcost和closest
{
if ((!s[j]) && (c[t][j] < lowcost[j]))
{
lowcost[j] = c[t][j];
closest[j] = t;
}
}
}
}
int main()
{
//1.初始化邻接矩阵,村庄之间的距离值
int n, c[N][N], m, u, v, w;
int u0;
cout << "输入结点数n和边数m:" << endl;
cin >> n >> m;
int sumcost = 0;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
c[i][j] = INF;
cout << "输入结点数u,v和边值w:" << endl;
for (int i = 1; i <= m; i++)
{
cin >> u >> v >> w;
c[u][v] = c[v][u] = w;
}
cout << "输入任一结点u0:" << endl;
cin >> u0;
//计算最后的lowcos的总和,即为最后要求的最小的费用之和
Prim(n, u0, c);
cout << "数组lowcost的内容为" << endl;
for (int i = 1; i <= n; i++)
cout << lowcost[i] << " ";
cout << endl;
for (int i = 1; i <= n; i++)
sumcost += lowcost[i];
cout << "最小的花费是:" << sumcost << endl;
return 0;
}
复杂度分析
在 P r i m Prim Prim算法中一共有4个for循环,其中第二个for循环为双重循环嵌套。故时间复杂度为 O ( n 2 ) O(n^2) O(n2)。
算法所需要的辅助空间包括 i , j , l o w c o s t 和 c l o s e s t i,j,lowcost和closest i,j,lowcost和closest,故空间复杂度为 O ( n ) O(n) O(n)。
老汤建议:测试代码前先用笔画出算法图和计算流程能够更容易理解代码哦!