53. 寻宝(第七期模拟笔试)
Prim算法
使用Prim算法求最小生成树,并在过程中记录最小生成树的代价。
时间复杂度:
O
(
n
2
)
O(n^2)
O(n2)
空间复杂度:
O
(
n
)
O(n)
O(n)
版本一
// c++
#include<bits/stdc++.h>
using namespace std;
#define infinity 10001
int main(){
int n,m;
cin>>n>>m;
vector<vector<int>> edges(n+1, vector<int>(n+1, infinity));
vector<int> cost(n+1, infinity);
vector<bool> inTree(n+1, false);
int v1,v2,val;
for(int i=0; i<m; i++){
cin>>v1>>v2>>val;
// 无向图存储两次
edges[v1][v2] = val;
edges[v2][v1] = val;
}
// 记录生成树的总代价
int result = 0;
// 以1为出发节点
// cost[0] = 0;
cost[1] = 0;
// 共n个结点
for(int i=1; i<=n; i++){
int cur = -1;
int minVal = infinity;
for(int j=1; j<=n; j++){
// 找当前距离生成树的最近节点
if(!inTree[j] && cost[j] < minVal){
cur = j;
minVal = cost[j];
}
}
// 当前节点加入树
inTree[cur] = true;
// cout<<cur<<" "<<minVal<<endl;
// 记录生成树的总代价
result += minVal;
// 更新结点代价
for(int j=1; j<=n; j++){
if(!inTree[j] && edges[cur][j]<cost[j]){
cost[j] = edges[cur][j];
}
}
}
cout<<result<<endl;
return 0;
}
版本二
// c++
#include<bits/stdc++.h>
using namespace std;
#define infinity 10001
int main(){
int n,m;
cin>>n>>m;
vector<vector<int>> edges(n+1, vector<int>(n+1, infinity));
vector<int> cost(n+1, infinity);
vector<bool> inTree(n+1, false);
int v1,v2,val;
for(int i=0; i<m; i++){
cin>>v1>>v2>>val;
// 无向图存储两次
edges[v1][v2] = val;
edges[v2][v1] = val;
}
// 记录生成树的总代价
int result = 0;
// 以1为出发节点
// cost[0] = 0;
// cost[1] = 0;
// 共n个结点
for(int i=1; i<=n; i++){
int cur = -1;
int minVal = INT_MAX;
for(int j=1; j<=n; j++){
// 找当前距离生成树的最近节点
if(!inTree[j] && cost[j] < minVal){
cur = j;
minVal = cost[j];
}
}
// 当前节点加入树
inTree[cur] = true;
// cout<<i<<" "<<cur<<" "<<minVal<<endl;
// 记录生成树的总代价
// result += minVal;
// 更新结点代价
for(int j=1; j<=n; j++){
if(!inTree[j] && edges[cur][j]<cost[j]){
cost[j] = edges[cur][j];
}
}
}
for(int i=2; i<=n; i++) result += cost[i];
cout<<result<<endl;
return 0;
}
Kruskal算法
先对所有边按权值升序排序,每次将不在树种的结点的权值最小边相连,直至最终成为一棵树。
时间复杂度:
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)
空间复杂度:
O
(
n
)
O(n)
O(n)
// c++
#include<bits/stdc++.h>
using namespace std;
#define infinity 10001
struct Edge{
int left;
int right;
int val;
};
void init(vector<int> &father){
for(int i=0; i<father.size(); i++) father[i] = i;
}
int find(vector<int> &father, int u){
return u == father[u] ? u : father[u] = find(father, father[u]);
}
void join(vector<int> &father, int u, int v){
u = find(father, u);
v = find(father, v);
if(u == v) return ;
father[v] = u;
}
int main(){
int n,m;
cin>>n>>m;
vector<Edge> edges;
vector<int> father(n+1, 0);
int v1,v2,val;
for(int i=0; i<m; i++){
cin>>v1>>v2>>val;
edges.push_back({v1, v2, val});
}
// 初始化并查集father
init(father);
sort(edges.begin(), edges.end(), [](const Edge &a, const Edge &b){
return a.val<b.val;
});
// 记录生成树的总代价
int result = 0;
for(Edge edge : edges){
int u = find(father, edge.left);
int v = find(father, edge.right);
if(u == v) continue;
join(father, u, v);
result += edge.val;
}
cout<<result<<endl;
return 0;
}
总结
Prim维护的是结点集合,Kruskal维护的是边集合。在具体问题上,若结点少边多,则选择Prim;反之,节点多边少,选择Kruskal。
即:稠密图中,Prim更佳,在稀疏图中,Kruskal更佳。
Prim时间复杂度 O(V2),仅与结点数有关。
Kruskal时间复杂度 O(ElogE),仅与边数有关。(快排O(ElogE),并查集O(logE)。)