1. 程式人生 > >最小生成樹(prim演算法與kruskal演算法)(模板)

最小生成樹(prim演算法與kruskal演算法)(模板)

th寫的總結,很不錯,轉載一下:點選開啟連結

首先說一下什麼是樹:

    1、只含一個根節點

    2、任意兩個節點之間只能有一條或者沒有線相連

    3、任意兩個節點之間都可以通過別的節點間接相連

    4、除了根節點沒一個節點都只有唯一的一個父節點

    5、也有可能是空樹(不含任何節點)

最小生成樹就是:

   在所有資料滿足是一棵樹的情況下一條將所有節點都連線起來且長度最短的一條路(因為任意兩個節點之間有權值

 (相連的兩點之間權值為一個具體的數,不相連的兩個點之間權值為無窮大))

下面介紹通用的求最小生成樹的兩種演算法:

 ps:這裡用的兩種演算法都是用鄰接矩陣實現適合點稠密型資料或者資料較小的情況:

 (1)prim演算法:

<span style="font-size:12px;">/*
*  陣列tree[]用來記錄最小生成樹的節點
*  陣列lowdis[]記錄從起點到其餘所有點的距離並不斷更新
*  陣列map[][]記錄所有資料兩點之間的距離
*  point是所有節點的數目,begin是起點
*  mindis是最小生成樹的長度
*/
void prime()
{
    int i,j,min,mindis=0,next;
    memset(tree,0,sizeof(tree));
    for(i=1;i<=point;i++)
    {
        lowdis[i]=map[begin][i];//用lowdis[]陣列記錄下從起點到剩下所有點的距離
    }
    tree[begin]=1;//標記起點(即最小生成樹中的點)
    for(i=1;i<point;i++)
    {
        min=INF;
        for(j=1;j<=point;j++)
        {
            if(!tree[j]&&min>lowdis[j])
            {
                min=lowdis[j];//求出從當前起點到其餘所有點的距離中最短的
                next=j;
            }
        }
        mindis+=min;//記錄下整條最小樹的長度
        tree[next]=1;
        for(j=1;j<=point;j++)
        {
            if(!tree[j]&&lowdis[j]>map[next][j])
            lowdis[j]=map[next][j];//更新lowdis[]陣列
        }
    }
    printf("%d\n",mindis);
}</span>

kruskal演算法:

 find()函式用來查詢根節點

<span style="font-size:12px;">int find(int father)//查詢根節點
{
    int t;
    int children=father;
    while(father!=set[father])
    father=set[father];
    while(fa!=set[children])
    {
        t=set[children];
        set[children]=father;
        children=t;
    }
    return father;
}</span>

mix函式用來合併兩個節點,使兩個節點的父節點相同

<span style="font-size:12px;">void mix(int x,int y)//將兩個點合併(即另兩點根節點相同)
{
    int fx;
    int fy;
    fx=find(x);
    fy=find(y);
    if(fx!=fy)
    set[fx]=fy;
}</span>

 利用結構體排序:

<span style="font-size:12px;">struct record
{
    int begin;//記錄兩個點中的一個
    int end;//記錄兩個點中的一個
    int dis;//記錄兩點之間距離
}num[MAX];
bool cmp(int a,int b)
{
    return a.dis<b.dis;//對兩點之間距離進行從大到小的排序
}</span>

用一個題來實現上述兩個演算法:

省政府“暢通工程”的目標是使全省任何兩個村莊間都可以實現公路交通(但不一定有直接的公路相連,只要能間接通過公路可達即可)。經過調查評估,得到的統計表中列出了有可能建設公路的若干條道路的成本。現請你編寫程式,計算出全省暢通需要的最低成本。 Input 測試輸入包含若干測試用例。每個測試用例的第1行給出評估的道路條數 N、村莊數目M ( < 100 );隨後的 N 
行對應村莊間道路的成本,每行給出一對正整數,分別是兩個村莊的編號,以及此兩村莊間道路的成本(也是正整數)。為簡單起見,村莊從1到M編號。當N為0時,全部輸入結束,相應的結果不要輸出。 Output 對每個測試用例,在1行裡輸出全省暢通需要的最低成本。若統計資料不足以保證暢通,則輸出“?”。    Sample Input   3 3   1 2 1   1 3 2   2 3 4   1 3   2 3 2 0 100 Sample Output 3 ?

 prim演算法:

<span style="font-size:12px;">#include<stdio.h>
#include<string.h>
#define INF 0x3f3f3f
int lowcost[110];//此陣列用來記錄第j個節點到其餘節點最少花費 
int map[110][110];//用來記錄第i個節點到其餘n-1個節點的距離 
int visit[110];//用來記錄最小生成樹中的節點 
int city;
void prime()
{
    int min,i,j,next,mincost=0;
    memset(visit,0,sizeof(visit));//給最小生成樹陣列清零 
    for(i=1;i<=city;i++)
    {
        lowcost[i]=map[1][i];//初始化lowcost陣列為第1個節點到剩下所有節點的距離 
    }
    visit[1]=1;//選擇第一個點為最小生成樹的起點 
    for(i=1;i<city;i++)
    {
        min=INF;
        for(j=1;j<=city;j++)
        {
            if(!visit[j]&&min>lowcost[j])//如果第j個點不是最小生成樹中的點並且其花費小於min 
            {
                min=lowcost[j];
                next=j;//記錄下此時最小的位置節點 
            }
        }
        if(min==INF)
        {
            printf("?\n");
            return ;
        }
        mincost+=min;//將最小生成樹中所有權值相加 
        visit[next]=1;//next點加入最小生成樹 
        for(j=1;j<=city;j++)
        {
            if(!visit[j]&&lowcost[j]>map[next][j])//如果第j點不是最小生成樹中的點並且此點處權值大於第next點到j點的權值 
            {
                lowcost[j]=map[next][j];         //更新lowcost陣列 
            }
        }
    }
    printf("%d\n",mincost);
}
int main()
{
    int road;
    int j,i,x,y,c;
    while(scanf("%d%d",&road,&city)&&road!=0)
    {
        memset(map,INF,sizeof(map));//初始化陣列map為無窮大 
        while(road--)
        {
            scanf("%d%d%d",&x,&y,&c);
            map[x][y]=map[y][x]=c;//城市x到y的花費==城市y到想的花費 
        }
        prime();
    }
    return 0;
}</span>

 kruskal演算法: 

/*
*  set[]陣列用來存放根節點
*  find()函式用來查詢根節點
*  mix()函式用來將兩個點合併(即使其父節點相同)
*/
#include<stdio.h>
#include<algorithm>
using namespace std;
int set[110];
struct record
{
    int beg;
    int end;
    int money;
}s[11000];
int find(int fa) //查詢根節點
{
    int ch=fa;
    int t;
    while(fa!=set[fa])
    fa=set[fa];
    while(ch!=fa)
    {
        t=set[ch];
        set[ch]=fa;
        ch=t;
    }
    return fa;
}
void mix(int x,int y) //合併兩點
{
    int fx,fy;
    fx=find(x);
    fy=find(y);
    if(fx!=fy)
    set[fx]=fy;
}
bool cmp(record a,record b)
{
    return a.money<b.money; //對價格進行排序
}
int main()
{
    int city,road,n,m,j,i,sum;
    while(scanf("%d",&road)&&road!=0)
    {
        scanf("%d",&city);
        for(i=0;i<road;i++)
        {
            scanf("%d%d%d",&s[i].beg,&s[i].end,&s[i].money);
        }
        for(i=1;i<=city;i++)
        set[i]=i; //初始化set[]陣列
        sort(s,s+road,cmp);
        sum=0;
        for(i=0;i<road;i++) //這個地方可能不好理解  我在下面作出解釋
        {
            if(find(s[i].beg)!=find(s[i].end)) //當兩點沒有連線時才可以連線兩點
            {
                mix(s[i].beg,s[i].end); //因為已經按照價錢從高到低排序好了所以可以找到
                sum+=s[i].money; //在兩個點 未相連的情況下的最小權值並將其相連
            }
        }  
        j=0;
        for(i=1;i<=city;i++)
        {
            if(set[i]==i) 
            j++;
            if(j>1)
            break;
        }
        if(j>1) //如果根節點不只一個則證明此組資料不是一棵樹
        printf("?\n");
        else
        printf("%d\n",sum);
    }
    return 0;
}

 這裡對上面程式碼中所說的地方作出解釋:

<span style="font-size:12px;">/*
*例如給出一組資料
*3 3
*1 2 1
*2 3 4
*1 3 2
*我們按照價錢遞減的順序排號之後就是
*1 2 1
*1 3 2
*2 3 4
*通過查詢根節點我們首先是對1 2進行根節點查詢即
*find(1) , find(2)可知find(1)==1,find(2)==2 不相等
*所以合併兩點的根節點即find(2)==1;同理find(3)==1
*因為2,3都已經合併所以第三組資料2 3 4的價格4就不會
* 加到最小樹的結果中去
*/
for(i=0;i<road;i++) <br>{
    if(find(s[i].beg)!=find(s[i].end)) //當兩點沒有連線時才可以連線兩點
    {
        mix(s[i].beg,s[i].end); //因為已經按照價錢從高到低排序好了所以可以找到
        sum+=s[i].money; //在兩個點 未相連的情況下的最小權值並將其相連
    }
} </span>