什么是最小生成树
- 是一棵树
无回路
|V|个顶点一定有 |V|-1 条边 - 是生成树
不唯一
包含全部顶点
|V|-1 条边都在图里 - 边的权值和最小
举个列子,有6个村落,如何修路能使6个村落都相通,同时花费最少呢?
一第一种算法:Prim算法
即从一个根节点开始,慢慢的收录别的节点,dist[i]表示该节点离生成树的最近距离
从v1->v4
v1->v2>v4
v1->v2->v4->v3
v1->v2->v4->v3->v7->v6->v5
void Prim(){
MST = {s}; // parent[s] = -1
while(1){
V = 未收录顶点中dist最小者; // dist[V] = E<V,W> 或 正无穷
if ( 这样的V不存在 )
break;
dist[V] = 0; // 将V收录进MST
for ( V 的每个邻接点 W )
if ( dist[W]!= 0)
if ( E<V,W> < dist[w] ){
dist[W] = E<V,W>;
parent[W] = V;
}
}
if ( MST 中收的顶点不到|V|个)
Error ( "图不连通" );
}
其中dist[i]表示该点到数的最短距离,如果能被数收录,就让dist[i]=0,目的是防止形成回路
代码如下:
#include<bits/stdc++.h>
using namespace std;
int dist[1000]={0};
int G[1000][1000]={0};
int parent[1000]={0};//存放并查集的根节点
int n,m;
int sum=0;
vector<int>ve;//存放收录的顺序
int cnt=0;
void create(){
cin>>n>>m;
for(int i=1;i<=n;i++){
dist[i]=100000;
parent[i]=i;
}
for(int i=1;i<=m;i++){
int l1,l2,v;
cin>>l1>>l2>>v;
G[l1][l2]=v;
G[l2][l1]=v;
}
}
void init(int s){
dist[s]=0;
ve.push_back(s);
for(int i=1;i<=n;i++){
if(i!=s&&G[s][i]){
dist[i]=G[s][i];
parent[i]=s;
}
}
}
int findmin(int s){
int mini=-1;
int m=100000;
for(int i=1;i<=n;i++){
if(i!=s&&dist[i]&&dist[i]<m){
m=dist[i];
mini=i;
}
}
return mini;
}
void prim(int s){
init(s);
while(true){
int i=findmin(s);
if(i==-1)break;
sum+=dist[i];
cnt++;
dist[i]=0;
ve.push_back(i);
for(int j=1;j<=n;j++){
if(dist[j]&&G[i][j]){
if(G[i][j]<dist[j]){
dist[j]=G[i][j];
parent[j]=i;
}
}
}
}
}
void output(){
cout<<"收录的顺序为:"<<endl;
for(int i=0;i<=cnt;i++){
cout<<ve[i]<<" ";
}
cout<<"权重和:"<<sum<<endl;
cout<<"该生成树为:";
for(int i=1;i<=n;i++){
cout<<parent[i]<<" ";
}
}
int main(){
create();
prim(1);
output();
}
/*
7 12
1 2 2
1 3 4
1 4 1
2 4 3
2 5 10
3 4 2
3 6 5
4 5 7
4 7 4
4 6 8
5 7 6
6 3 5
6 7 1
结果:
被收录顺序:
4 2 3 7 6 5 45702 权重和为:20
该生成树为:
-1 1 4 1 7 3 4
*/
二第二种算法:cruskal算法
即每次收录最小的边,分别形成一颗颗树,最后去合并这些树,生成最小生成树
- 如何保证不会形成回路?
用并查集表示父节点,这样两个点只要父节点不相同,就可以合并 - 如何选取最小的边?
利用最小堆,如果要用stl的话,要重载运算符
#include<bits/stdc++.h>
using namespace std;
struct node{
int v1;
int v2;
int w;
bool operator < (const node & n)const{
return w>n.w;
}
};
priority_queue<node>q;//最小堆
vector<node>v;//存放收录的顺序
int parent[1000]={0};
int n,m;
int sum=0;
void create(){
cin>>n>>m;
for(int i=1;i<=n;i++){
parent[i]=-1;//并查集初始化
}
for(int i=0;i<m;i++){
node n;
int v1,v2,w;
cin>>v1>>v2>>w;
n.v1=v1;
n.v2=v2;
n.w=w;//把每一条边推进最小堆
q.push(n);
}
}
int find(int x){
if(parent[x]<0)return x;
else return parent[x]=find(parent[x]);
}
void Union(int x,int y){//按孩子个数归并,parent[]表示该根节点拥有的孩子个数
int l1=find(x);
int l2=find(y);
if(l1<l2){
parent[l1]+=parent[l2];
parent[l2]=l1;
}else{
parent[l2]+=parent[l1];
parent[l1]=l2;
}
}
void kruska(){
while(!q.empty()&&v.size()!=n-1){
node n=q.top();
q.pop();
if(find(n.v1)!=find(n.v2)){
v.push_back(n);
sum+=n.w;
Union(n.v1,n.v2);
}
}
}
void output(){
cout<<"权重和:"<<sum<<endl;
cout<<"收录的顺序为:"<<endl;
for(int i=0;i<n;i++){
cout<<v[i].w<<" ";
}
cout<<"该生成树为:";
for(int i=1;i<=n;i++){
cout<<parent[i]<<" ";
}
}
int main(){
create();
kruska();
output();
}
练习题
6 15
1 2 5
1 3 3
1 4 7
1 5 4
1 6 2
2 3 4
2 4 6
2 5 2
2 6 6
3 4 6
3 5 1
3 6 1
4 5 10
4 6 8
5 6 3
12