图的最小生成树Prim堆优化适用于稀疏图
复杂度为O(MlogN)
例:
输入
6 9
2 4 11
3 5 13
4 6 3
5 6 4
2 3 6
4 5 7
1 2 1
3 4 9
1 3 2
选出最少的边让图连通,使得边的总长度之和最短。
算法的流程
1.以邻接表的方式存储无向图,从任意一个顶点开始构造生成树,假设从1号顶点开始,用book记录哪些顶点已被选中。
2.用h数组表示堆,pos数组存编号在堆中的位置,dis数组存储编号在对应的值(生成树到各个顶点的最小距离),没直连边时设为dis无穷大,dis初始化编号1号开始dis[1]=0,然后搜索与1点直接相连的顶点对应的边更新dis值,然后把所有编号在堆中随便放,再建立最小堆,最后将编号1弹出(dis[1]为0)。
3.弹出堆的顶点(即数组dis中离生成树最近的点)假设这个点为x,加入到生成树中,更新x到每个直连边的距离(松弛)即:
dis[v[i]] =min(dis[v[i]],w[i])
最后再维护堆。
4.重复第3步直到生成树中有n个顶点为止
代码实现如下:
#include<stdio.h>
#define INF 99999999
int n,m,i,j,sum=0,count=0,size;//size表示堆的大小
//h[i]表示堆的i位置对应的编号,pos[i]表示i编号在堆中的位置
int first[50],next[100],u[100],v[100],w[100],book[50]={0},dis[50],h[50],pos[50];
//交换
void swap(int a,int b){
//交换堆的位置
int t=h[a];
h[a]=h[b];
h[b]=t;
//更新记录
t=pos[h[a]];
pos[h[a]]=pos[h[b]];
pos[h[b]]=t;
return;
}
//向下调整
void siftdown(int o){
int t,flag=0;
while(o*2 <= size && flag==0){
if(dis[h[o]] > dis[h[o*2]]){
t=o*2;
} else t=o;
if(o*2+1 <= size){
if(dis[h[t]] > dis[h[o*2+1]])
t=o*2+1;
}
if(t!=o){
swap(t,o);
o=t;
} else flag=1;
}
return;
}
//向上调整
void siftup(int k){
int flag=0;
while(flag==0 && k!=1){
if(dis[h[k]] < dis[h[k/2]])
swap(k,k/2);
else flag=1;
k=k/2;
}
return;
}
int pop(){
int t=h[1];
pos[t]=0; //t点的位置放一边,可写可不写
h[1]=h[size]; //堆的第一个位置的编号变为最后一个位置的编号
pos[h[1]]=1; //h[1]点的位置记录为1
size--;
siftdown(1);
return t;
}
int main(){
scanf("%d %d",&n,&m);
for(i=1; i <=n; i++){
first[i]=-1;
dis[i]=INF;
}
//邻接表存储双向图
for(i=1; i <= m; i++){
scanf("%d%d%d",&u[i],&v[i],&w[i]);
u[m+i]=v[i];
v[m+i]=u[i];
w[m+i]=w[i];
}
for(i=1; i <= 2*m; i++){
next[i]=first[u[i]];
first[u[i]]=i;
}
//Prim核心部分
//假设以1为出发点
dis[1]=0;
book[1]=1;
count++;
for(i=first[1]; i!=-1; i=next[i]){
dis[v[i]]=w[i];
}
size=n;
for(i=1; i<=size; i++){
h[i]=i;
pos[i]=i;
}
for(i=size/2; i>= 1; i--){//建最小堆
siftdown(i);
}
pop();//先把编号1弹出
while(count<n){
j=pop();//返回编号值
book[j]=1;
sum+=dis[j]; //写成dis[h[j]]debug好久QAQ
count++;
//松弛
for(i=first[j]; i != -1; i=next[i]){
if(book[v[i]]==0 && dis[v[i]] > w[i]){
dis[v[i]] = w[i];
siftup(pos[v[i]]);
}
}
}
printf("%d",sum);
return 0;
}