1. 程式人生 > >Prim和Kruskal演算法之C++實現

Prim和Kruskal演算法之C++實現

最近好長時間都困惑在這兩個演算法中,其實也不難,就是寫的時候比較費勁。現在總結一下。
首先說一下兩個演算法是幹嘛呢?
都是求解一個無向圖G的最小生成樹(minimum spanning tree),就是由該圖的那些連線G的所有頂點的邊構成的樹,其總值最低。這裡很重要一點就是要求G是連通的。
克魯斯卡爾(Kruskal)演算法因為只與邊相關,則適合求稀疏圖的最小生成樹。而prime演算法因為只與頂點有關,所以適合求稠密圖的最小生成樹。

克魯斯卡爾(Kruskal)演算法因為只與邊相關,則適合求稀疏圖的最小生成樹。而prime演算法因為只與頂點有關,所以適合求稠密圖的最小生成樹。

Prim

設圖G頂點集合為U,首先任意選擇圖G中的一點作為起始點a,將該點加入集合V,再從集合U-V中找到另一點b使得點b到V中任意一點的權值最小,此時將b點也加入集合V;以此類推,現在的集合V={a,b},再從集合U-V中找到另一點c使得點c到V中任意一點的權值最小,此時將c點加入集合V,直至所有頂點全部被加入V,此時就構建出了一顆MST。因為有N個頂點,所以該MST就有N-1條邊,每一次向集合V中加入一個點,就意味著找到一條MST的邊。

演算法要維持兩個陣列:
lowcost[i]:表示以i為終點的邊的最小權值,當lowcost[i]=0說明以i為終點的邊的最小權值=0,也就是表示i點加入了MST。
mst[i]: 表示對應lowcost[i]的起點,即說明邊(mst[i],i)是MST的一條邊,當mst[i]=0表示起點i加入MST。
具體過程詳:

http://blog.csdn.net/yeruby/article/details/38615045

程式碼

//最小生成樹的prim演算法
//http://blog.csdn.net/yeruby/article/details/38615045
#include<iostream>
#include<fstream>
using  namespace std;

#define  MAX 100
#define MAXCOST 0x7fffffff
int graph[MAX][MAX];

int prim(int graph[][MAX], int n,int begin)//begin作為初始頂點,n總的頂點數目
{ int lowcost[MAX]; int mst[MAX]; int sum = 0; for (int i = 1;i <= n;i++) //初始化 { if (i == begin) continue; lowcost[i] = graph[begin][i];//頂點1到其他定點的代價 mst[i] = begin;//初始都指向頂點begin } mst[begin] = 0;//頂點begin在MST集合裡 lowcost[begin] = 0; for (int i = 1;i <= n;i++) { if (i == begin) continue; int min = MAXCOST; int minid = 0; for (int j = 1;j <= n;j++)//尋找lowcost中最小的邊 { if (j == begin) continue; if (lowcost[j] < min&&lowcost[j] != 0) { min = lowcost[j]; minid = j; } } lowcost[minid] = 0; cout << "V" << mst[minid] << "-V" << minid << "=" << min << endl;//輸出找到的最小一條邊 sum = sum + min; for (int j = 1;j <= n;j++)//更新其他定點(主要變化的就是以minid為起始的邊) { if (j == begin) continue; if (graph[minid][j] < lowcost[j]) { lowcost[j] = graph[minid][j]; mst[j] = minid; } } } return sum; } int main() { ifstream in("input.txt"); int m, n; int begin = 2; in >> m >> n; for (int i = 1;i <= m;i++) { for (int j = 1;j <= m;j++) graph[i][j] = MAXCOST; } for (int k = 1;k <= n;k++) { int i, j, cost; in >> i >> j >> cost; graph[i][j] = cost; graph[j][i] = cost; } int cost = prim(graph, m, begin); cout << "最小權值和=" << cost << endl; system("pause"); return 0; }

Kruskal

演算法過程:
1.將圖各邊按照權值進行排序
2.將圖遍歷一次,找出權值最小的邊,(條件:此次找出的邊不能和已加入最小生成樹集合的邊構成環),若符合條件,則加入最小生成樹的集合中。不符合條件則繼續遍歷圖,尋找下一個最小權值的邊。
3.遞迴重複步驟1,直到找出n-1條邊為止(設圖有n個結點,則最小生成樹的邊數應為n-1條),演算法結束。得到的就是此圖的最小生成樹。

程式碼

//Kruskal 演算法
#include<iostream>
#include<algorithm>
#include<fstream>
using namespace std;
#define MAX 1000

int father[MAX], son[MAX];//father[i]表示i的祖先(根),son[i]表示i的兒子數目,如果沒有記為1,如果有一個記為2...

typedef struct Edge
{
    int a;
    int b;
    int value;
};

bool cmp(Edge &a, Edge &b)
{
    return a.value < b.value;
}

int unionsearch(int x)
{
    return x == father[x] ? x : unionsearch(father[x]);
}

bool join(int x, int y)//合併x與y
{
    int root1, root2;
    root1 = unionsearch(x);//每個資料都有一個根(祖先)
    root2 = unionsearch(y);
    if (root1 == root2) //兩個的祖先一樣,說明已經屬於同一顆樹了,不用再合併了
        return false;
    else if (son[root1] >= son[root2]) //如果root1的兒子數目比較多,則把root2的祖先定為root1,同時root1的兒子數目更新
    {
        father[root2] = root1;
        son[root1]= son[root1] + son[root2];
    }
    else
    {
        father[root1] = root2;
        son[root2]= son[root1] + son[root2];
    }
}
int main()
{
    ifstream in("input.txt");
    Edge edge[MAX];
    int v, l;
    bool  flag=0;
    int addedge_num=0, sum=0;
    in >> v >> l;
    for (int i = 1; i <= v; i++)
    {
        father[i] = i;
        son[i] = 1; //初始時,每個頂點的兒子數為1,即本身自己
    }
    for (int i = 1; i <= l; i++)
    {
        in >> edge[i].a >> edge[i].b >> edge[i].value;
    }
    sort(edge + 1, edge + 1 + l, cmp);//按照升序排列
    for (int i = 1; i < l; i++)
    {
        if (join(edge[i].a, edge[i].b))//說明能合併
        {
            addedge_num++;
            sum = sum + edge[i].value;
            cout<< edge[i].a << "->" << edge[i].b << endl;
        }
        if (addedge_num == v - 1)
        {
            flag = 1;
            break;
        }
    }
    if (flag)
    {
        cout << sum << endl;
    }
    else
    {
        cout << "Data error!" << endl;
    }
    system("pause");
    return 0;
}

注意裡面涉及到並查集合的巧妙運用,仔細體會。