与Dijkstra算法不同,Prim算法的dist数组是表示dist【t】点t到集合最短距离,而Dijkstra算法是点t到点1的最短距离!
这段代码实现了普里姆(Prim)算法,用于计算加权无向图的最小生成树(MST)。下面是代码的详细解释:
-
常量和全局变量:
const int N = 510
:定义最大顶点数为510。const int INF = 0x3f3f3f3f
:定义无穷大的值,用于初始化距离数组和图的邻接矩阵。
-
变量声明:
int n, m
:分别表示图中顶点数和边数。int g[N][N]
:邻接矩阵,用于存储图的边的信息。int dist[N]
:用于存储当前顶点到生成树的距离。bool st[N]
:标记数组,用于标记顶点是否已经被加入生成树。
-
prim
函数:memset(dist, 0x3f, sizeof dist)
:初始化距离数组,将所有距离设置为无穷大。int res = 0
:用于存储最小生成树的总权重。- 外层循环:从0到n-1,每次循环选择一个顶点加入生成树。
int t = -1
:用于存储当前未加入生成树的最近顶点。- 内层循环:遍历所有顶点,找到未加入生成树且距离最小的顶点。
if (i && dist[t] == INF) return INF
:如果已经选择了一个顶点,且当前未加入生成树的最近顶点的距离仍然是无穷大,说明无法连接所有顶点,返回无穷大。if (i) res += dist[t]
:如果已经选择了至少一个顶点,将当前顶点到生成树的距离累加到结果中。st[t] = true
:标记当前顶点已加入生成树。- 内层循环:更新与当前顶点相邻的顶点到生成树的距离。
- 返回最小生成树的总权重。
-
main
函数:scanf("%d%d", &n, &m)
:读取顶点数和边数。memset(g, 0x3f, sizeof g)
:初始化邻接矩阵,将所有边的权重设置为无穷大。- 循环读取每条边的信息,并更新邻接矩阵。
- 调用
prim
函数计算最小生成树的权重。 - 判断结果是否为无穷大,如果是,则输出 “impossible”,否则输出最小生成树的权重。
这段代码实现了普里姆算法的核心逻辑,通过贪心策略逐步构建最小生成树,并计算其总权重。
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 510, INF = 0x3f3f3f3f;
int n, m;
int g[N][N];
int dist[N];
bool st[N];
int prim()
{
memset(dist, 0x3f, sizeof dist);
int res = 0;
for (int i = 0; i < n; i ++ )
{
int t = -1;
for (int j = 1; j <= n; j ++ )
if (!st[j] && (t == -1 || dist[t] > dist[j]))
t = j;
if (i && dist[t] == INF) return INF;
if (i) res += dist[t];
st[t] = true;
for (int j = 1; j <= n; j ++ ) dist[j] = min(dist[j], g[t][j]);
}
return res;
}
int main()
{
scanf("%d%d", &n, &m);
memset(g, 0x3f, sizeof g);
while (m -- )
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
g[a][b] = g[b][a] = min(g[a][b], c);
}
int t = prim();
if (t == INF) puts("impossible");
else printf("%d\n", t);
return 0;
}
Prim算法的应用
比如铺设公路,不同地点之间铺设公路使得总共铺设的公路总长最短!用料最少!Prim算法保证的是最小生成树各点之间边权和最小,但是无法保证最小生成树唯一,因为最小生成树可能不唯一!
Prim算法与Kruskal算法比较
克鲁斯卡尔(Kruskal)算法和普里姆(Prim)算法都是图论中用于寻找最小生成树(Minimum Spanning Tree, MST)的算法。最小生成树是指在一个加权无向图中,包含图中所有顶点的树,且边的总权重最小。这两种算法在设计和实现上有所不同:
-
算法思想:
- 克鲁斯卡尔算法:是一种贪心算法,它按照边的权重从小到大的顺序选择边,每次选择不会与已选择的边形成环的最小权重边,直到选出 V − 1 V-1 V−1 条边(其中 V V V 是顶点数)。
- 普里姆算法:也是一种贪心算法,它从一个顶点开始,逐渐增加新的顶点和边到当前生成树中,每次增加的边是连接当前生成树和不在树中的顶点且权重最小的边。
-
数据结构:
- 克鲁斯卡尔算法:通常使用优先队列(最小堆)来存储边,并按照权重从小到大的顺序选择边。
- 普里姆算法:通常使用优先队列来存储与生成树相邻的顶点,按照与生成树连接的边的权重从小到大的顺序选择顶点。
-
适用场景:
- 克鲁斯卡尔算法:适合边的数量远多于顶点数量的稀疏图,因为算法主要操作是对边进行排序。
- 普里姆算法:适合顶点数量远多于边数量的密集图,因为算法主要操作是对顶点进行处理。
-
时间复杂度:
- 克鲁斯卡尔算法:时间复杂度为 O ( E log E ) O(E \log E) O(ElogE) 或 O ( E log V ) O(E\log V) O(ElogV),其中 $ E $ 是边的数量,排序边需要 O ( E log E ) O(E\log E) O(ElogE) 的时间,使用并查集检测环需要 O ( E log V ) O(E\log V) O(ElogV) 的时间。
- 普里姆算法:时间复杂度为 O ( E log V ) O(E\log V) O(ElogV),因为每次从优先队列中取出最小元素需要 O ( log V ) O(\log V) O(logV) 的时间。
-
边和顶点的选择:
- 克鲁斯卡尔算法:每次选择一条边,直到选出 V − 1 V-1 V−1 条边。
- 普里姆算法:每次选择一个顶点,直到所有顶点都被选中。
-
初始化:
- 克鲁斯卡尔算法:不需要初始化,直接对所有边进行排序。
- 普里姆算法:需要从某个顶点开始,初始化生成树,通常选择顶点集合中的第一个顶点。
-
环的检测:
- 克鲁斯卡尔算法:使用并查集数据结构来检测加入新边是否会形成环。
- 普里姆算法:不需要检测环,因为每次都是向生成树中添加新的顶点。
总结来说,克鲁斯卡尔算法更适用于边多于顶点的稀疏图,而普里姆算法更适用于顶点多于边的密集图。选择哪种算法取决于具体问题的需求和图的特性。