1. 程式人生 > >最小生成樹模板(prim+kruskal+prim的優化)

最小生成樹模板(prim+kruskal+prim的優化)

最小生成樹:解決極小連通子圖連線所有點使得花費最小問題

1.prim演算法 

思想:隨機選擇一個點,作為初始集合,並儲存所有點到這個集合的最短距離,然後找與這個集合距離最短的點,也將其加入這個集合,由於新加入了一個點,所以要更新所有未加入集合點到這個集合的最短距離,不斷重複,直到連通整個圖。

1)鄰接矩陣建圖樸素法(無向圖)複雜度o(v^2) 

#include<iostream>
#include<cstdio>
#include<cstring>
#define maxn 111
#define inf 1<<31-1
using namespace std;
int n,m,ans;
int vis[maxn],mapp[maxn][maxn],dis[maxn];//dis記錄未被加入集合的點到集合的最短距離
void init()//初始將所有邊權值賦為正無窮大
{
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++) mapp[i][j]=inf;
}
void prim()
{
    fill(vis,vis+maxn,0);
    for(int i=1;i<=n;i++) dis[i]=mapp[1][i];//隨便選擇一個點,這裡選擇第一個點,dis記錄所有點到這個點的距離
    dis[1]=0;//自己到自己距離為0
    vis[1]=1;//標記為訪問過
    for(int i=1;i<n;i++)//第一個點已經加入集合,還有m-1個點
    {
        int Min=inf,k;
        for(int j=1;j<=n;j++) if(!vis[j]&&Min>dis[j]) Min=dis[j],k=j;//選擇到集合距離最短的點,更新最短距離並記錄該點
        if(Min==inf) {ans=-1;return;}//Min為無窮大表明沒有與該圖連通的點,也就是說這個圖不是完全圖
        ans+=Min;
        vis[k]=1;//將與該集合距離最短的點標記為訪問過
        for(int j=1;j<=n;j++) if(!vis[j]&&dis[j]>mapp[k][j]) dis[j]=mapp[k][j];//更新所有與該集合的最近距離
    }
 }

2)prim+鄰接矩陣建圖+堆優化

#include<iostream>
#include<cstdio>
#include<queue>
#define maxn 111
#define inf 1<<29
using namespace std;
int vis[maxn],mapp[maxn][maxn],n,m,dis[maxn];
struct node
{
    int v,len;//v表示當前節點,len表示該點到已經連通集合的最短距離
    friend bool operator <(node a,node b)
    {
        return a.len>b.len;
    }
};
int prim()
{
    node cur;
    int ans = 0;
    priority_queue<node>q;
    fill(dis,dis+maxn,inf);
    fill(vis,vis+maxn,0);
    cur.v=1;
    cur.len=0;
    q.push(cur);
    while(q.size())
    {
        cur=q.top();
        q.pop();
        if(vis[cur.v]) continue;
        vis[cur.v]=1;
        ans+=cur.len;
        for(int i=1;i<=n;i++)
        {
            if(!vis[i]&&dis[i]>mapp[i][cur.v])
            {
                node next;
                next.v=i;
                next.len=mapp[i][cur.v];
                dis[i]=mapp[i][cur.v];
                q.push(next);
            }
        }
    }
    return ans;
}

2.kruskal  複雜度o(eloge)

kruskal的思想就是先記錄兩點和這兩個點的權值,然後對所有邊進行排序。這裡還用到了了並查集,若這兩點不屬於同一個集合,說明這兩個點之間沒有路,因為已經是從小到大排了序,所以直接把他們連通,讓他們屬於同一個集合,不斷判斷,直到所有邊都進行了判斷,然後再根據是否只有一個節點的父親是他本身,若是,則說明最小生成樹已經求出,否則不存在這樣的樹.

以hdu 1863為例

#include<iostream>
#include<algorithm>
#include<cstdio>
#define maxn 111
using namespace std;
int father[maxn];
struct node
{
    int l,r;
    int value;
};
int Find(int x)//並查集查詢是否屬於同一個集合
{
   if(father[x]!=x)
      father[x]=Find(father[x]);
   return father[x];
}
int Merge(int x,int y)//若不是同一集合,則加入集合
{
    x=Find(x);
    y=Find(y);
    if(x!=y) father[x]=y;
}
bool same(int x,int y)//判斷是否是同一集合
{
    return Find(x)==Find(y) ? 1:0;
}
bool rule(node x,node y)
{
    return x.value<y.value;
}
int main()
{
    int n,m;
    node a[maxn];
    while(cin>>m>>n&&m)
    {
        for(int i=1;i<=n;i++) father[i]=i;//並查集初始化
        for(int i=1;i<=m;i++) cin>>a[i].l>>a[i].r>>a[i].value;
        sort(a+1,a+n+1,rule);//排序
        int ans=0;
        for(int i=1;i<=m;i++)
        {
            if(!same(a[i].l,a[i].r))//若兩點之間沒有路徑,連線這條路,即加入同一個集合
            {
                 Merge(a[i].l,a[i].r);
                 ans+=a[i].value;
            }
        }
        int sum = 0;
        for(int i=1;i<=n;i++) if(father[i]==i) sum++;//看是否所有點都連通,若只有一個點的父親是它本身,則表明所有點已經連通
        if(sum==1) cout<<ans<<endl;
        else cout<<'?'<<endl;
    }
    return 0;
}