## [$AcWing$ $1140$. 最短网络](https://www.acwing.com/problem/content/1142/) ### 一、题目描述 农夫约翰被选为他们镇的镇长! 他其中一个竞选承诺就是在镇上建立起互联网,并连接到所有的农场。 约翰已经给他的农场安排了一条高速的网络线路,他想把这条线路共享给其他农场。 约翰的农场的编号是$1$,其他农场的编号是 $2$∼$n$。 为了使花费最少,他希望用于连接所有的农场的 **光纤总长度尽可能短**。 你将得到一份各农场之间连接距离的列表,你必须找出能连接所有农场并使所用光纤最短的方案。 **输入格式** 第一行包含一个整数 $n$,表示农场个数。 接下来 $n$ 行,每行包含 $n$ 个整数,输入一个对角线上全是$0$的对称矩阵。 其中第 $x+1$ 行 $y$ 列的整数表示连接农场 $x$ 和农场 $y$ 所需要的光纤长度。 **输出格式** 输出一个整数,表示所需的最小光纤长度。 **数据范围** $3≤n≤100$ 每两个农场间的距离均是非负整数且不超过$100000$。 **输入样例** ```c++ 4 0 4 9 21 4 0 8 17 9 8 0 16 21 17 16 0 ``` **输出样例** ```c++ 28 ``` ### 二、解题思路 $Prim$算法和$Kruskal$算法都是用于 **求解最小生成树的算法**,但它们的使用场景和应用领域存在一些差异。 #### $Prim$算法 ① $Prim$算法是一种贪心算法,基于顶点的方式构建最小生成树。 ② $Prim$算法 **适用于稠密图**,即边的数量接近于完全图$(n*(n-1)/2)$的图。 ③ $Prim$算法从一个起始顶点开始逐步扩展,直到生成一个包含所有顶点的最小生成树。 ④ $Prim$算法的时间复杂度为$O(ElogV)$,对于稠密图有较好的性能。 #### $Kruskal$算法 ① $Kruskal$算法是一种基于边的方式构建最小生成树的算法。 ② $Kruskal$算法 **适用于稀疏图**,即边的数量远小于完全图$(n*(n-1)/2)$的图。 ③ $Kruskal$算法按权值递增的顺序选择边,并通过判断是否构成环来决定是否将边加入最小生成树。 ④ $Kruskal$算法的时间复杂度为$O(ElogE)$,对于稀疏图有较好的性能。 #### 总结 总体而言,$Prim$算法适用于稠密图,具有更好的时间复杂度,而$Kruskal$算法适用于稀疏图,具有相对较好的性能。在选择使用哪种算法时,可以根据图的特性和规模来进行选择。 ### 三、$Prim$ 算法 ```cpp {.line-numbers} #include using namespace std; const int N = 110; int n; int w[N][N]; // 邻接矩阵,记录每两个点之间的距离 int dist[N]; // 每个点距离集合的最小长度 bool st[N]; // 是不是已经加入到集合中 int prim() { // 初始化所有节点到集合的距离为正无穷 memset(dist, 0x3f, sizeof dist); dist[1] = 0; // 1号节点到集合的距离为0 int res = 0; for (int i = 1; i <= n; i++) { // 迭代n次 int t = -1; //(1)是不是第一次 //(2)如果不是第1次那么找出距离最近的那个点j for (int j = 1; j <= n; j++) if (!st[j] && (t == -1 || dist[t] > dist[j])) t = j; // 最小生成树的距离和增加dist[t] res += dist[t]; // t节点入集合 st[t] = true; // 利用t,拉近其它节点长度 for (int j = 1; j <= n; j++) dist[j] = min(dist[j], w[t][j]); } return res; } int main() { scanf("%d", &n); // 完全图,每两个点之间都有距离,不用考虑无解情况 for (int i = 1; i <= n; i++) for (int j = 1; j <= n; j++) scanf("%d", &w[i][j]); // 利用prim算法计算最小生成树 printf("%d\n", prim()); return 0; } ``` ### 四、$kruscal$ 算法 ```cpp {.line-numbers} #include using namespace std; const int N = 110; const int M = 10010; struct Node { //用结构体存储每条边 int f, t, w; bool operator<(const Node &e) const { return w < e.w; } } edges[M]; int p[N]; int find(int x) { //并查集找根节点 if (p[x] != x) p[x] = find(p[x]); return p[x]; } int n, idx, ans; int main() { scanf("%d", &n); //邻接矩阵 for (int i = 1; i <= n; i++) for (int j = 1; j <= n; j++) { int w; scanf("%d", &w); edges[idx++] = {i, j, w}; //加入当前的边 } sort(edges, edges + idx); //对边权进行排序,注意这里不是优先队列,是谁小谁在前 for (int i = 1; i <= n; i++) p[i] = i; //并查集初始化 for (int i = 1; i <= idx; i++) { //枚举每条边 int f = find(edges[i].f), t = find(edges[i].t); if (f != t) { //当前两点不连通 ans += edges[i].w; //更新答案 p[f] = t; //让两点变连通 } } printf("%d", ans); return 0; } ```