最小生成树(MST):是在一个给定的无向图G(V,E)中求一棵树T,使得这棵树拥有图G 中的所有顶点,且所有边是来自图G中的边,并且满足整棵树的边权之和最小。
性质:
①最小生成树是树,因此其边数等于顶点数减1,且树内一定不会有环
②对给定的图G(V,E),其最小生成树可以不唯一,但其边权之和一定是唯一的
③由于最小生成树是在无向图上生成的,因此其根节点可以是这棵树上的任意一个结点
Prime算法基本思想:
对图G(V,E)设置集合S,存放已被访问的顶点,然后每次从集合V-S选择与集合S的最短距离最小的一个顶点(记为u),访问并加入集合S。
Code:
#include<iostream>
#include<algorithm>
#include<vector>
#include<numeric>
using namespace std;
int N,M;
struct T{
int to,w;//下一个节点 权值
};
vector<vector<T>>Edge;//存储边的信息及权值
vector<int>d;//存储每个点的
vector<bool>used;//是否被使用过
int ans=0;//记录权值之和
void Prime(){
for(int i=0;i<=N;i++)//把所有节点到已有生成树的距离为无穷大
d[i]=0x3f3f3f3f;
d[1]=0;//开始点为0
while(true){
int x=-1;
for(int i=1;i<=N;i++)
if(!used[i]&&(x==-1||d[i]<d[x]))//找出未被访问 且是生成树外的最小节点 加入生成树中
x=i;
if(x==-1)//图里没有小的了 如果有 则是不连通图 再对其它连通片执行Prim()算法 得出来就是此图的最小生成森林
break;
cout<<x<<" "<<d[x]<<endl;
used[x]=true;//标记为已访问
ans+=d[x];//记录权值
for(auto &e:Edge[x])//e是一个结构体 e.to是与x相邻接的边 e.w为边上的权值
d[e.to]=min(d[e.to],e.w); //更新x所有连接的边 找出自身已更新的值d[e.to]和边权值[e.w]中小的
}
}
int main(){
cin>>N>>M;//顶点数 边数
Edge.resize(N+1);//节点1~N
d.resize(N+1);
used.resize(N+1);
int x,y,w;
for(int i=1;i<=M;i++){//无向图
cin>>x>>y>>w;
Edge[x].push_back({y,w});
Edge[y].push_back({x,w});
}
cout<<"最小生成树:"<<endl;
Prime();
cout<<"权值之和: ";
cout<<ans;//输出最小生成树的权值之和
return 0;
}
//6 10
//1 2 4
//1 5 1
//1 6 2
//2 3 6
//2 6 3
//3 6 5
//3 4 6
//4 6 5
//4 5 4
//5 6 3
注意:使用这种写法复杂度是O(N^2),可以利用堆优化为O(NlogN)
利用STL中的优先队列
头文件:< queue >
基本操作:
empty() 如果队列为空返回真
pop() 删除对顶元素
push() 加入一个元素
size() 返回优先队列中拥有的元素个数
top() 返回优先队列对顶元素
声明方式:
①priority_queue< int >q;
//通过操作,按照元素从大到小的顺序出队
②priority_queue<int, vector, greater< int > q;
//此时整数中元素小的优先级高。
Code:
#include<iostream>
#include<algorithm>
#include<vector>
#include<queue>
#include<numeric>
using namespace std;
using P=pair<int,int>;
int N,M;
struct T{
int to,w;//下一个节点 权值
};
vector<vector<T>>Edge;//存储边的信息及权值
priority_queue<P,vector<P>,greater<P> >Q;
vector<int>d;
vector<bool>used;//是否被使用过
int ans=0;//记录权值之和
void Prime(){
for(int i=0;i<=N;i++)//把所有节点到已有生成树的距离为无穷大
d[i]=0x3f3f3f3f;
d[1]=0;
Q.push({0,1});//把1结点加入 并且d[1]为0
while(!Q.empty()){
auto e=Q.top();//取出队头元素 根据小顶堆 第一个为最小的
Q.pop();//弹出队列
int x=e.second,w=e.first;
if(used[x])//若x被访问过 则继续弹出下一个进行访问
continue;
ans+=d[x];
used[x]=true;//标记为已访问
cout<<x<<" : "<<d[x]<<endl;
for(auto h:Edge[x]){
if(d[h.to]>h.w){//d值 比边权值大 则更新
d[h.to]=h.w;
Q.push({h.w,h.to});//放入优先队列中
}
}
}
}
int main(){
cin>>N>>M;//顶点数 边数
Edge.resize(N+1);//节点1~N
d.resize(N+1);
used.resize(N+1);
int x,y,w;
for(int i=1;i<=M;i++){//无向图
cin>>x>>y>>w;
Edge[x].push_back({y,w});
Edge[y].push_back({x,w});
}
cout<<"最小生成树:"<<endl;
Prime();
cout<<"权值之和: ";
cout<<ans;//输出最小生成树的权值之和
return 0;
}
//6 10
//1 2 4
//1 5 1
//1 6 2
//2 3 6
//2 6 3
//3 6 5
//3 4 6
//4 6 5
//4 5 4
//5 6 3