点击查看代码
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
#pragma warning(disable:4996)
/* prim算法与Dijkstra算法思想相同,只有d[]的定义不同:
(1)Dijkstra算法:d[i]是指起点与某个顶点i的最短距离
(2)prim算法:d[i]是指已知集合S与顶点i的最短距离,已知集合S由已选出的顶点组成,初始时只有起点一个,
然后每选出一个顶点i就将它加入到集合S中(算法结束时,已知集合中的顶点就组成最小生成树)*/
prim(G, d[]) { //图G,d[]是已知集合S与各顶点的最短距离
初始化:将d[]初始为一个很大的值,表示已知集合S与各点都不相连。然后初始起点s与已知集合最短距离为0,即d[s]=0;
for (循环n次) { //n个顶点循环n次
u = 能使d[u]最小,且还未被访问的顶点编号;
设置u已被访问;
for (枚举u的所有邻接点v) {
if (如果v未被访问,且以u为中转点能使已知集合S与v的最短距离d[v]更小) {
更新最小的d[v];
}
}
}
}
const int maxn = 1010; //不超过1000个结点
const int INF = 0x3fffffff; //无穷大INF,表示两个顶点之间不相连
//1、邻接矩阵版prim算法,时间复杂度:O(n^2)
int n, G[maxn][maxn]; //顶点个数,邻接矩阵
int d[maxn]; //已知集合S与顶点i的最短距离
bool vis[maxn] = { false }; //记录顶点是否已被访问,初始化全部为false未被访问
int prim() { //求解最小生成树的边权之和。没有传入初始参数,默认起点从0号顶点开始
fill(d, d + maxn, INF); //将d[]全部初始为INF,表示已知集合S与各点都不相连
d[0] = 0; //集合S与起点0号顶点最短距离为0
int ans = 0; //存储最小生成树的边权之和
for (int i = 0; i < n; i++) { //n个顶点循环n轮,每轮找出一个顶点
int u = -1, MIN = INF; //u存储d[u]最小的顶点,MIN存储最短距离d[u]
for (int j = 0; j < n; j++) { //枚举所有顶点
if (vis[j] == false && d[j] < MIN) { //查找在未访问的顶点中,最小的d[j]顶点j
u = j; //用u记录j
MIN = d[j]; //MIN记录最短距离d[j]
}
}
if (u == -1) return -1; //如果u等于-1,则表示剩下的顶点与集合S都不相连,可以结束算法,返回-1表示无解
vis[u] = true; //如果有解,则设置u已被访问
ans += d[u]; //将与集合S距离最小的边加进最小生成树中,累加边权之和
for (int v = 0; v < n; v++) { //枚举所有顶点,查找u的邻接点v,判断能否以u为集合边界,使集合S与v距离更短
if (vis[v] == false && G[u][v] != INF) { //如果v未被访问,且u和v之间相连
if (G[u][v] < d[v]) { //如果以u为集合边界,能使集合S与v的距离更短(注意prim的判断距离与Dijkstra不同)
d[v] = G[u][v]; //更新d[v]
}
}
}
}
return ans; //返回最小生成树的边权之和
}
//2、邻接表版prim算法,时间复杂度:O(n^2)
//如果使用STL的优先队列存储d[u](内部会自动排序,可以设队首元素最小),在查找最小的d[u]过程中可以降低时间复杂度,变为O(nlogn+E)
struct Node {
int v, dis; //v为边的目标顶点,dis是边权
};
vector<Node> adj[maxn]; //邻接表
int n, d[maxn]; //顶点数,已知集合S与顶点i的最短距离
bool vis[maxn] = { false }; //记录顶点是否已被访问,初始化全部为false未被访问
void prim(int s) { //求解最小生成树的边权之和。没有传入初始参数,默认起点从0号顶点开始
fill(d, d + maxn, INF); //将d[]全部初始为INF,表示已知集合S与各点都不相连
d[0] = 0; //集合S与起点0号顶点最短距离为0
int ans = 0; //存储最小生成树的边权之和
for (int i = 0; i < n; i++) { //n个顶点循环n轮,每轮找出一个顶点
int u = -1, MIN = INF; //u存储d[u]最小的顶点,MIN存放最短距离d[u]
for (int j = 0; j < n; j++) { //枚举所有顶点
if (vis[j] == false && d[j] < MIN) { //查找在未访问的顶点中,最小的d[j]顶点j
u = j; //用u记录j
MIN = d[j]; //MIN记录最短距离d[j]
}
}
if (u == -1) return -1; //如果u等于-1,则表示剩下的顶点与集合S都不相连,可以结束算法,返回-1表示无解
vis[u] = true; //否则设置u已被访问
ans += d[u]; //将与集合S距离最小的边加进最小生成树中,累加边权之和
/* 邻接表版本只有以下部分与邻接矩阵不同,因为邻接表可以直接获得u的邻接点,不需要枚举所有顶点 */
for (int j= 0; j < adj[u].size(); j++) { //枚举u的所有邻接点v,判断能否以u为集合边界,使集合S与v距离更短
int v = adj[u][j].v; //为了方便书写,将邻接点adj[u][j]的编号存入v中
if (vis[v] == false && adj[u][j].dis < d[v]) { //如果v未被访问,且以u为集合边界,能使集合S与v的距离更短(注意prim的判断距离与Dijkstra不同)
d[v] = adj[u][j].dis; //更新d[v]
}
}
}
return ans; //返回最小生成树的边权之和
}