最小生成樹問題


最小生成樹:

 

一個有N個點的圖,邊一定是大於等於N-1條的。圖的最小生成樹,就是在這些邊中選擇N-1條出來,連接所有的N個點。這N-1條邊的邊權之和是所有方案中最小的。

最小生成樹用來解決什么問題?   

就是用來解決如何用最小的“代價”用N-1條邊連接N個點的問題。

例題:洛谷P3366

乾坤大挪移

最小生成樹共有兩種算法:

prim算法與Kruskal算法

1.prim算法

Prim算法采用與Dijkstra、Bellman-Ford算法一樣的“藍白點”思想:

白點代表已經進入最小生成樹的點,藍點代表未進入最小生成樹的點。

算法思想:

以1為起點生成最小生成樹,min[v]表示藍點v與白點相連的最小邊權。

MST表示最小生成樹的權值之和。

1)初始化:min[v]= ∞(v≠1); min[1]=0;MST=0; b)for (i = 1; i<= n; i++) 1.尋找min[u]最小的藍點u。

2.將u標記為白點

3.MST+=min[u]

4.for 與白點u相連的所有藍點v if (w[u][v]<min[v]) min[v]=w[u][v]; c)

算法結束: MST即為最小生成樹的權值之和

推論:

初始時所有點都是藍點,min[1]=0,min[2、3、4、5]=∞。權值之和MST=0。

第一次循環自然是找到min[1]=0最小的藍點1。將1變為白點,接着枚舉與1相連的所有藍點2、3、4,修改它們與白點相連的最小邊權。

第二次循環是找到min[2]最小的藍點2。將2變為白點,接着枚舉與2相連的所有藍點3、5,修改它們與白點相連的最小邊權。

第三次循環是找到min[3]最小的藍點3。將3變為白點,接着枚舉與3相連的所有藍點4、5,修改它們與白點相連的最小邊權。   

最后兩輪循環將點4、5以及邊w[2][5],w[3][4]添加進最小生成樹。

最后權值之和MST=6。

這n次循環,每次循環我們都能讓一個新的點加入生成樹,n次循環就能把所有點囊括到其中;

每次循環我們都能讓一條新的邊加入生成樹,n-1次循環就能生成一棵含有n個點的樹;

每次循環我們都取一條最小的邊加入生成樹,n-1次循環結束后,我們得到的就是一棵最小的生成樹。    

這就是Prim采取貪心法生成一棵最小生成樹的原理。 算法時間復雜度:O (n2)。

例題代碼:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define inf 0x7fffffff
#define maxn 5005
int cost[maxn][maxn],minn,n,m,v2[maxn],tot=1,now,ans;
bool v1[maxn];
inline void getcost()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=n;j++)
        {
            cost[i][j]=inf;
        }
    }
    //先將數組賦為極大值
    for(int i=1,u,v,w;i<=m;i++)
    {
        scanf("%d%d%d",&u,&v,&w);
        if(cost[u][v]>w)
        {
            cost[u][v]=cost[v][u]=w;
        }
    }
    //初始化cost數組
    for(int i=1;i<=n;i++)
    {
        v2[i]=cost[1][i];
    }
    v1[1]=1;
    //找出與1節點相連的邊並進行標記
}
inline int prim()
{
    while(tot<n)
    //最小生成樹的概念,邊數比點數小一
    {
        minn=inf; 
        //附上初值
        tot++;
        //已經使用邊數++
        for(int i=1;i<=n;i++)
        {
            if(!v1[i]&&v2[i]<minn)
            {
                minn=v2[i];
                now=i;
            }
        }
        //找出與該點相連的最小邊
        ans+=minn;
        //更新答案
        for(int i=1;i<=n;i++)
        {
            if(v2[i]>cost[now][i]&&!v1[i])
            {
                v2[i]=cost[now][i];
            }
        }
        v1[now]=1;
        //在找出與now節點相連的邊並進行標記
    }
    return ans;
}
int main()
{
    getcost();
    printf("%d",prim());
    return 0;
}

2.Kruskal算法

算法描述:

1.初始化並查集。father[x]=x。

2.tot=0

3.將所有邊用快排從小到大排序。

4.計數器 k=0;

5.for (i=1; i<=M; i++) //循環所有已從小到大排序的邊

if 這是一條u,v不屬於同一集合的邊(u,v)(因為已經排序,所以必為最小)

begin  

①合並u,v所在的集合,相當於把邊(u,v)加入最小生成樹。  

②tot=tot+W(u,v)

③k++

④如果k=n-1,說明最小生成樹已經生成,則break; end;

6. 結束,tot即為最小生成樹的總權值之和。

Kruskal算法的時間復雜度為O(E*logE),E為邊數。

例題代碼:

#include<iostream>
#include<cstring>
#include<cstdio> 
#include<algorithm>
using namespace std;
int r[5005],n,m,ans,w,e,cnt;
struct Edge
{
    int u,v,w;
}
edge[222222];
bool cmp(Edge a,Edge b)//交換函數 
{ 
    return a.w<b.w; 
}
int find(int x)
{
    while(x!=r[x]) x=r[x]=r[r[x]];
    return x;
}
void kruskal()//算法精華 
{
    sort(edge,edge+m,cmp);
    for(int i=0;i<m;i++){
        w=find(edge[i].u), e=find(edge[i].v);
        if(w==e) continue;
        ans+=edge[i].w;
        r[e]=w; cnt++;
        if(cnt==n-1) break;
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) 
    r[i]=i;
    for(int i=0;i<m;i++)
    scanf("%d%d%d",&edge[i].u,&edge[i].v,&edge[i].w);
    kruskal();
    printf("%d",ans);
    return 0;
}

例題2:

洛谷P1546

傳送門

運用kruskal算法解決

#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
struct point
       {
          int x;
          int y;
          int v;
       };
point a[9901];
int fat[101];
int n,i,j,x,m,tot,k;
int father(int x)
{
    if (fat[x] != x) fat[x] = father(fat[x]);
    return fat[x];   
}
void unionn(int x,int y)
{
    int fa = father(x);
    int fb = father(y);
    if (fa != fb) fat[fa] = fb;  
}
int cmp(const point&a,const point&b)
{
     if (a.v < b.v) return 1;
        else return 0; 
}
int main()
{
    cin >> n;
    for (i = 1; i <= n; i++)
        for (j = 1; j <= n; j++)
        {
            cin >> x;
            if (x != 0)
            {
                m++;
                a[m].x = i; a[m].y = j; a[m].v = x;
             }
        }
    for (i=1;i<=n;i++) 
        fat[i]=i;
    sort(a+1,a+m+1,cmp); 
        for(i=1;i<=m;i++)
        {
            if (father(a[i].x) != father(a[i].y))
            {
                unionn(a[i].x,a[i].y);
                tot += a[i].v;
                k++;
            }
        if (k == n-1) break;
        }
    cout << tot;
    return 0;
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM