这道题网上大多是用kruskal做的,因为准备ccf的关系,准备学习c++,于是希望实现prim练练手,写博客来积累一下学到的数据结构。
这道题题意就是求最小生成树中权值最大的边。因为它给的数据量问题,是个边稀疏图,用kruskal做效率更高,但是优化的prim也可以做到满分。
prim+邻接链表+堆实现。
堆中存放的是边,用pair表示,first表示边权值,second表示到达点的序号。count表示已访问过的顶点个数,n表示顶点总个数
核心逻辑是:
1.入堆一个虚拟边,权值为0,到达点为起始点。
2.弹出堆顶元素,若second访问过了,则continue,若没有访问过,则count++,res = first
3.if count == n then return res
4.将所有到达点未访问过的邻接边入堆 ,goto 2
误区(就是我开始写错了):在4中判断过到达点未访问过,所以不用在2中判断second是否已经访问过,这是不对的。原因是循环的判断顺序是按照权值的,而不是入堆的顺序,所以可能在之前判断未访问过的到达点边,在出堆的时候已经访问过了。
#include<bits/stdc++.h>
using namespace std;
/*
prim算法
*/
typedef pair<int,int> wl;
//默认第一关键字排序,这里的空格是必要的,否则会成移位符 greater表示升序队列,即小顶堆
priority_queue <wl,vector<wl>,greater<wl> >q;
struct Edge{
int nextNode;
int cost;
};
const int N = 100001;
vector<Edge> edge[N];
const int MAXV = 500001;
bool visited[MAXV] = {false};//表示是否已经拜访
int prim(int root,int n){
q.push(make_pair(0,1));
int res = 0,count = 0;
while(!q.empty()){
int u = q.top().second;
int v = q.top().first;
q.pop();
if(visited[u]){
continue;
}
count++;
res = res>v?res:v;//这里多此一举了,应该只需要res = v;
if(count==n){
return res;
}
visited[u] = true;
for(int i = 0;i<edge[u].size();i++){
if(!visited[edge[u][i].nextNode]){
q.push(make_pair(edge[u][i].cost,edge[u][i].nextNode));
}
}
}
return 0;
}
int main(){
int n,m,root;
int u,v,w;
scanf("%d",&n);
scanf("%d",&m);
scanf("%d",&root);
for(int i = 0;i<m;i++){
scanf("%d %d %d",&u,&v,&w);
Edge temp1,temp2;
temp1.nextNode = v;
temp1.cost = w;
temp2.nextNode = u;
temp2.cost = w;
edge[u].push_back(temp1);
edge[v].push_back(temp2);
}
printf("%d\n",prim(root,n));
return 0;
}