1. 程式人生 > >圖相關(三)圖的鄰接矩陣表示(C++)及最最小生成樹演算法(prim和kruskal)

圖相關(三)圖的鄰接矩陣表示(C++)及最最小生成樹演算法(prim和kruskal)

一.測試用圖

鄰接矩陣表示:

//prim注意是無向圖
    vector<vector<int>> p(6, vector<int>(6, INF));//類似dijikstra,沒有邊的點設為INF
    p[0][1] = 10;
    p[0][3] = 30;
    p[0][4] = 45;
    p[1][0] = 10;
    p[3][0] = 30;
    p[4][0] = 45;
 
    p[1][2] = 50;
    p[1][4] = 40;
    p[1][5] = 25;
    p[2][1] = 50;
    p[4][1] = 40;
    p[5][1] = 25;
 
    p[2][4] = 35;
    p[2][5] = 15;
    p[4][2] = 35;
    p[5][2] = 15;
 
    p[3][5] = 20;
    p[5][3] = 20;
 
    p[4][5] = 55;
    p[5][4] = 55;

 

注意:prim演算法的圖為無向圖,和dijikstra一樣,沒有邊的地方要用INF填充

其中INF表示無窮:


const int INF = 0x3f3f3f;//不能取得太大

二.prim演算法:

//prim演算法,返回最小生成樹的權值
int Prim(vector<vector<int>> cost)//下標從0開始
{
    int n = cost.size();
    vector<bool> visited(n, false);
    vector<int> lowc(n, 0);
    int ans = 0;
    visited[0] = true;//第一個點標記為已訪問
    for (int i = 1;i < n;i++)lowc[i] = cost[0][i];
    for (int i = 1;i < n;i++)
    {
        int minc = INF;
        int p = -1;
        for (int j = 0;j < n;j++)
            if (!visited[j] && minc > lowc[j])
            {
                minc = lowc[j];
                p = j;
            }
        if (minc == INF)return -1;//原圖不連通
        ans += minc;
        visited[p] = true;
        for (int j = 0;j < n;j++)
            if (!visited[j] && lowc[j] > cost[p][j])
                lowc[j] = cost[p][j];
    }
    return ans;
}

 測試:

int minWeight =Prim(p);
    cout << "minWeight= " << minWeight << endl;

測試結果:

 

 三.kruskal演算法:

圖改為邊陣列表示,利用並查集很容易理解。此處直接用一個數組來表示並查集,陣列的下標表示節點的編號,對應的值為其父指標的值,初始時,將並查集中的所有元素的父指標指向-1.

3.1圖的邊陣列表示法:

//kruskal:使用並查集
struct Edge {
    int from;//起點
    int to;//終點
    int weight;
    bool operator<(const Edge&rhs){//過載operator <之後可以直接呼叫sort
        return weight < rhs.weight;//此處也可以單獨寫一個仿函式,傳給sort作比較器
    }
};

此時上面的測試用圖表示為:

 vector<Edge> kru =
    { { 0,1,10 },{ 1,0,10 },{ 0,3,30 },{ 3,0,30 },{ 0,4,45 },{ 4,0,45 },{ 1,2,50 },{ 2,1,50 },
    { 1,4,40 },{ 4,1,40 },{ 1,5,25 },{ 5,1,25 },{ 2,4,35 },{ 4,2,35 },
    { 3,5,20 },{ 5,3,20 },{ 5,4,55 },{ 4,5,55 },{ 2,5,15 },{ 5,2,15 } };
 

3.2並查集的find函式

int Find(vector<int>& UnionFindSet,int x){//索引表示節點標號,對應的值為其父節點的標號
    if (UnionFindSet[x] == -1)//代表元素是-1,說明只有一個元素
        return x;
    return UnionFindSet[x] = Find(UnionFindSet,UnionFindSet[x]);//遞迴修改,扁平化
}

3.3 kruskal演算法

int kruskal(vector<Edge> graph,int vertexNum) {
    size_t N = graph.size();
    int ans = 0;
    int cnt = 0;//已經選取的邊數
    vector<int> UnionFindSet(N, -1);//初始化並查集,每個集合的代表元素置為-1;
    sort(graph.begin(), graph.end());//給邊排序,拍好序後邊的權值由小到大排列
    for (size_t i = 0;i < N;++i) {
        int from = graph[i].from;
        int to = graph[i].to;
        int weight = graph[i].weight;
        //找出這條邊的兩個頂點所在的集合
        int t1 = Find(UnionFindSet, from);
        int t2 = Find(UnionFindSet, to);
        if (t1 != t2) {//t1和t2不在一個集合時,即沒有形成迴路時,選取該邊
            ans += weight;
            UnionFindSet[t1] = t2;//合併兩個集合
            ++cnt;
        }
        if(cnt==vertexNum-1)//選夠頂點數減1條邊時,退出,最小生成樹的邊數為頂點數減一
            break;
    }
    if (cnt < vertexNum - 1) return -1;//不連通
    else return ans;
}

3.4 kruskal測試

cout << "kruskal: " << endl;
    vector<Edge> kru =
    { { 0,1,10 },{ 1,0,10 },{ 0,3,30 },{ 3,0,30 },{ 0,4,45 },{ 4,0,45 },{ 1,2,50 },{ 2,1,50 },
    { 1,4,40 },{ 4,1,40 },{ 1,5,25 },{ 5,1,25 },{ 2,4,35 },{ 4,2,35 },
    { 3,5,20 },{ 5,3,20 },{ 5,4,55 },{ 4,5,55 },{ 2,5,15 },{ 5,2,15 } };
 
    int ret = kruskal(kru, 6);
    cout << "kruskal= " << ret << endl;

3.5 kruskal測試結果