1. 程式人生 > >最小生成樹&&次小生成樹

最小生成樹&&次小生成樹

對於一個邊上具有權值的圖來說,其邊權值和最小的生成樹叫做圖G的最小生成樹

求無向圖最小生成樹主要有prim和kruskal兩種演算法

1.prim

將點集V分成Va和Vb兩部分,Va為已經連入生成樹的點,Vb為沒有連入的點,按照邊的大小逐漸向Va中加點,直到Va中包含所有點,具體步驟,複雜度O(mlogn)

⑴.首先初始化生成樹的權值為0,任選一點放入Va,其餘點放入Vb

⑵.在Vb中找一點u,在Va中找一點v(其實v一直不變),使得uv間距離最短,並更新u所連邊,這也就是為什麼v不變的原因

⑶.重複步驟2,直到Vb中沒有點為止

#include <queue>
#include <vector>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <iostream>
#include <algorithm>
const double INF=0x3f3f3f3f;
using namespace std;
double dis[1005],G[1005][1005];
int n,x[1005],y[1005],vis[1005];
double prim(int v){
    int i,j,u;
    double sum,tmp;
    sum=0;
    memset(vis,0,sizeof(vis));
    for(i=1;i<=n;i++)
    dis[i]=G[v][i];
    vis[v]=1;
    for(i=1;i<n;i++){
        u=v;
        tmp=INF;
        for(j=1;j<=n;j++)
        if(dis[j]<tmp&&vis[j]==0){
            tmp=dis[j];
            u=j;
        }                                       //找出與v相連最小的邊
        sum+=tmp;
        vis[u]=1;
        for(j=1;j<=n;j++)
        if(!vis[j]){
            if(dis[j]>G[u][j])
            dis[j]=G[u][j];
        }                                       //更新與u相連的邊的權值
    }
    return sum;
}
int main(){
   int i,j;
   double ans;
   scanf("%d",&n);
   for(i=1;i<=n;i++)
   scanf("%d%d",&x[i],&y[i]);
   for(i=1;i<=n;i++)
   for(j=1;j<=n;j++)
   G[i][j]=sqrt((x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]));
   ans=prim(1);                                 //通過每個點的座標算出每個點間距離
   printf("%.2lf\n",ans);
   return 0;
}
2.kruskal

基於貪心的思想逐漸加入邊並判斷是否形成環,複雜度O(mlogm)

⑴.初始化,並將E進行排序

⑵.不斷將邊加入圖中,並判斷是否形成環

⑶.判斷選擇的邊是否是n-1,並計算步驟2的權值和

#include <queue>
#include <vector>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <iostream>
#include <algorithm>
using namespace std;
int V,E;
int par[505],ran[505],vis[505];
void init(int n){
    int i;
    for(i=0;i<=n;i++){
        ran[i]=0;
        par[i]=i;
    }
}
int find(int x){
    if(par[x]==x)
    return x;
    return par[x]=find(par[x]);
}
void unite(int x,int y){
    x=find(x);
    y=find(y);
    if(x==y)
    return;
    if(ran[x]<ran[y])
    par[x]=y;
    else{
        par[y]=x;
        if(ran[x]==ran[y])
        ran[x]++;
    }
}
bool same(int x,int y){
    return find(x)==find(y);
}                                               //並查集判斷是否有環
struct node{
    int u,v,cost;
};
bool cmp(node a,node b){
    return a.cost<b.cost;
}
node es[505];
int kruskal(){
    int i;
    int res=0;
    init(V);
    sort(es,es+E,cmp);
    for(i=0;i<E;i++){
        node e=es[i];
        if(!same(e.u,e.v)){
            unite(e.u,e.v);
            res+=e.cost;
        }
    }
    return res;
}
int main(){
    int i;
    scanf("%d%d",&V,&E);
    memset(vis,0,sizeof(vis));
    for(i=0;i<E;i++)
    scanf("%d%d%d",&es[i].u,&es[i].v,&es[i].cost);
    printf("%d\n",kruskal());
    return 0;
}

3.次小生成樹

基於最小生成樹的演算法演變出次小生成樹,其實基本的思想就是連入一條不在最小生成樹上的邊,從而形成一個環,去掉在環中並且在最小生成樹上最大的邊,遍歷所有不在最小生成樹上的邊並進行同樣的操作最小值即為次小生成樹,簡單證明就是連入一條邊後去掉一個最大值相當於比原來的值增加的值最小(增加量=新增的邊-環上的某一條邊(並且這條邊在最小生成樹上),新增的邊的權值一定,因此使環上的邊最大),因次去掉最大的邊,kruskal的複雜度是O(mlogm),求出環上的最大值複雜度是O(n*n),因此次小生成樹的複雜度是O(mlogm+n*n)

#include <math.h>
#include <stdio.h>
#include <string.h>
#include <iostream>
#include <algorithm>
const double INF=0x3f3f3f3f;
using namespace std;
int dis[105],path[105][105],G[105][105];
int n,m,pre[105],vis[105],used[105][105];
int prim(int v){
    int i,j,u,sum,tmp;
    sum=0;
    memset(vis,0,sizeof(vis));
    memset(used,0,sizeof(used));
    memset(path,0,sizeof(path));
    for(i=1;i<=n;i++){
        dis[i]=G[v][i];
        pre[i]=1;
    }
    vis[v]=1;
    for(i=1;i<n;i++){
        u=v;
        tmp=INF;
        for(j=1;j<=n;j++)
        if(dis[j]<tmp&&vis[j]==0){
            tmp=dis[j];
            u=j;
        }
        sum+=tmp;
        vis[u]=1;
        used[u][pre[u]]=used[pre[u]][u]=1;
        for(j=1;j<=n;j++){
        if(vis[j]&&j!=u)                        //從j到父節點上的邊的最大值和最小生成樹上的邊之間求最大值
        path[u][j]=path[j][u]=max(path[j][pre[u]],dis[u]);
        if(!vis[j]){                            //與dp有些類似
            if(dis[j]>G[u][j]){
                dis[j]=G[u][j];
                pre[j]=u;
            }
        }
        }
    }
    return sum;
}
int second_prim(int tmp){
    int i,j,ans;
    ans=INF;
    for(i=1;i<=n;i++)
    for(j=1;j<=n;j++){
        if(i!=j&&used[i][j]==0)
        ans=min(ans,tmp+G[i][j]-path[i][j]);
    }                                           //遍歷每條邊求次小生成樹
    return ans;
}
int main(){
   int i,j,x,y,z,ans,tmp;
   scanf("%d%d",&n,&m);
   memset(G,INF,sizeof(G));
   for(i=1;i<=m;i++){
   scanf("%d%d%d",&x,&y,&z);
   G[x][y]=G[y][x]=z;
   }
   tmp=prim(1);                                 //先求出最小生成樹
   ans=second_prim(tmp);        
   printf("%d\n",ans);
   return 0;
}