數據結構(五)圖---最小生成樹(克魯斯卡爾算法)


一:回顧普里姆算法

數據結構(五)圖---最小生成樹(普里姆算法)

普里姆算法是以某個頂點為起點,逐步找各頂點上最小權值的邊來構建最小生成樹。是臨時決定路徑。
例如:我們參觀某個展會,從一個入口進入,然后我們會選擇最感興趣的場館進入觀看,看完后再用同樣的方法看下一個。

二:克魯斯卡爾算法(稀疏圖)

推文:https://www.cnblogs.com/qianbixin/p/5005161.html(轉載自)

例如上面參觀,我們為何不先決定去看那些展館,事先計划好,然后進園后直接按照所決定的路徑進行觀看。這就是克魯斯卡爾算法的思想

注意:

克魯斯卡爾算法需要我們了解生成樹的概念,我們可以回到普里姆算法回顧下

(一)基本思想

1)構造一個只含n個頂點,邊集為空的子圖。若將圖中各個頂點看成一棵樹的根節點,則它是一個含有n棵樹的森林。
(2)從網的邊集 E 中選取一條權值最小的邊,若該條邊的兩個頂點分屬不同的樹,則將其加入子圖。也就是說,將這兩個頂點分別所在的兩棵樹合成一棵樹;反之,若該條邊的兩個頂點已落在同一棵樹上,則不可取,而應該取下一條權值最小的邊再試之
(3)依次類推,直至森林中只有一棵樹,也即子圖中含有 n-1條邊為止。

或者

(1)將圖中的所有邊都去掉。
(2)將邊按權值從小到大的順序添加到圖中,保證添加的過程中不會形成環
(3)重復上一步直到連接所有頂點,此時就生成了最小生成樹。這是一種貪心策略。

(二)難點

判斷某條邊<u, v>的加入是否會在已經選定的邊集集合中形成環。

(三)解決思路

使用並查集,分別找出兩個頂點u, v所在樹的根節點。若根節點相同,說明u, v在同一棵樹中,則u, v連接起來會形成環;若根節點不同,則u, v不在一棵樹中,連接起來不會形成環,而是將兩棵樹合並。

推文:數據結構(四)樹---集合的表示及查找(並查集)

(四)圖解過程

         

         

三:代碼實現

數據結構定義

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>

#define MAXVEX 100    //最大頂點數
#define INFINITY 65535    //用0表示∞

typedef char VertexType;    //頂點類型,字符型A,B,C,D...
typedef int EdgeType;    //邊上權值類型10,15,...

//鄰接矩陣結構
typedef struct
{
    VertexType vers[MAXVEX];    //頂點表
    EdgeType arc[MAXVEX][MAXVEX];    //鄰接矩陣,可看作邊表
    int numVertexes, numEdges;    //圖中當前的頂點數和邊數
}MGraph;


typedef struct
{
    int begin;
    int end;
    int weight;
}Edge;

函數定義

//創建鄰接矩陣
void CreateMGraph(MGraph* G);
//鄰接矩陣轉邊集數組
void MGraph2EdgeArr(MGraph G, Edge* edge);
//顯示鄰接矩陣
void showGraph(MGraph G);
//找到頂點index的根節點下標返回
int Find(int* parent, int index);
//使用克魯斯卡爾算法進行最小生成樹的創建
void MiniSpanTree_Kruskal(MGraph G);

創建鄰接矩陣

void CreateMGraph(MGraph* G)
{
    int i, j, k, w;
    G->numVertexes = 9;
    G->numEdges = 15;
    //讀入頂點信息
    G->vers[0] = 'A';
    G->vers[1] = 'B';
    G->vers[2] = 'C';
    G->vers[3] = 'D';
    G->vers[4] = 'E';
    G->vers[5] = 'F';
    G->vers[6] = 'G';
    G->vers[7] = 'H';
    G->vers[8] = 'I';

    //getchar();    //可以獲取回車符
    for (i = 0; i < G->numVertexes; i++)
        for (j = 0; j < G->numVertexes; j++)
            G->arc[i][j] = INFINITY;    //鄰接矩陣初始化

    G->arc[0][1] = 10;
    G->arc[0][5] = 11;
    G->arc[1][2] = 18;
    G->arc[1][6] = 16;
    G->arc[1][8] = 12;
    G->arc[2][3] = 22;
    G->arc[2][8] = 8;
    G->arc[3][4] = 20;
    G->arc[3][7] = 16;
    G->arc[3][6] = 24;
    G->arc[3][8] = 21;
    G->arc[4][5] = 26;
    G->arc[4][7] = 7;
    G->arc[5][6] = 17;
    G->arc[6][7] = 19;

    for (k = 0; k < G->numVertexes; k++)    //讀入numEdges條邊,建立鄰接矩陣
    {
        for (i = k; i < G->numVertexes; i++)
        {
            G->arc[i][k] = G->arc[k][i];    //因為是無向圖,所有是對稱矩陣
        }
    }
}
CreateMGraph創建鄰接矩陣

鄰接矩陣轉邊集數組

//鄰接矩陣轉編輯數組,按照權值排序,由小到大
void MGraph2EdgeArr(MGraph G, Edge* edge)
{
    int i, j,k=0;
    Edge temp;
    int min;
    for (i = 0; i < G.numVertexes;i++)
    {
        for (j = i + 1; j < G.numVertexes;j++)
        {
            if (G.arc[i][j]!=INFINITY)    //有邊
            {
                edge[k].begin = i;
                edge[k].end = j;
                edge[k].weight = G.arc[i][j];
                k++;
            }
        }
    }

    //按照冒泡大小進行排序
    for (i = 0; i < k;i++)
    {
        for (j = i; j < k;j++)
        {
            if (edge[j].weight<edge[i].weight)
            {
                temp = edge[i];
                edge[i] = edge[j];
                edge[j] = temp;
            }
        }
    }
}

並查集操作,獲取一個頂點f的根節點下標,這里沒有使用結構體,而是將數組下標作為了數據,節省了不必要空間

int Find(int* parent, int f)
{
    while (parent[f] > 0)
        f = parent[f];
    return f;
}

使用克魯斯卡爾算法進行最小生成樹的創建

void MiniSpanTree_Kruskal(MGraph G)
{
    Edge edges[MAXVEX];    //定義邊集數組
    int parent[MAXVEX];    //定義生成樹的父節點,也可以使用結構體,但是更加浪費空間
    int i,n,m;
    MGraph2EdgeArr(G, edges);    //鄰接矩陣轉邊集數組

    //開始進行初始化
    for (i = 0; i < MAXVEX; i++)
        parent[i] = 0;    //這里是0代表根節點,我們也可以使用-1,正負無窮等
    //進行合並操作
    for (i = 0; i < G.numEdges;i++)
    {
        n = Find(parent, edges[i].begin);    //找到頂點edges[i].begin的根節點下標
        m = Find(parent, edges[i].end);        //找到頂點edges[i].end的根節點位置
        if (n!=m)    //若是根節點下標不是一樣的,就說不在一棵樹上,不會形成環,我們放心合並
        {
            parent[n] = m;    //將n樹合並到m樹,表示該邊被放入生成樹
            printf("(%d,%d) %d ", edges[i].begin, edges[i].end, edges[i].weight);
        }
    }
}

全部代碼

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>

#define MAXVEX 100    //最大頂點數
#define INFINITY 65535    //用0表示∞

typedef char VertexType;    //頂點類型,字符型A,B,C,D...
typedef int EdgeType;    //邊上權值類型10,15,...

//鄰接矩陣結構
typedef struct
{
    VertexType vers[MAXVEX];    //頂點表
    EdgeType arc[MAXVEX][MAXVEX];    //鄰接矩陣,可看作邊表
    int numVertexes, numEdges;    //圖中當前的頂點數和邊數
}MGraph;


typedef struct
{
    int begin;
    int end;
    int weight;
}Edge;

//創建鄰接矩陣
void CreateMGraph(MGraph* G);
//鄰接矩陣轉邊集數組
void MGraph2EdgeArr(MGraph G, Edge* edge);
//顯示鄰接矩陣
void showGraph(MGraph G);
//找到頂點index的根節點下標返回
int Find(int* parent, int index);
//使用克魯斯卡爾算法進行最小生成樹的創建
void MiniSpanTree_Kruskal(MGraph G);


int main()
{
    MGraph MG;
    CreateMGraph(&MG);
    showGraph(MG);
    MiniSpanTree_Kruskal(MG);
    system("pause");
    return 0;
}

//生成鄰接矩陣
void CreateMGraph(MGraph* G)
{
    int i, j, k, w;
    G->numVertexes = 9;
    G->numEdges = 15;
    //讀入頂點信息
    G->vers[0] = 'A';
    G->vers[1] = 'B';
    G->vers[2] = 'C';
    G->vers[3] = 'D';
    G->vers[4] = 'E';
    G->vers[5] = 'F';
    G->vers[6] = 'G';
    G->vers[7] = 'H';
    G->vers[8] = 'I';

    //getchar();    //可以獲取回車符
    for (i = 0; i < G->numVertexes; i++)
        for (j = 0; j < G->numVertexes; j++)
            G->arc[i][j] = INFINITY;    //鄰接矩陣初始化

    G->arc[0][1] = 10;
    G->arc[0][5] = 11;
    G->arc[1][2] = 18;
    G->arc[1][6] = 16;
    G->arc[1][8] = 12;
    G->arc[2][3] = 22;
    G->arc[2][8] = 8;
    G->arc[3][4] = 20;
    G->arc[3][7] = 16;
    G->arc[3][6] = 24;
    G->arc[3][8] = 21;
    G->arc[4][5] = 26;
    G->arc[4][7] = 7;
    G->arc[5][6] = 17;
    G->arc[6][7] = 19;

    for (k = 0; k < G->numVertexes; k++)    //讀入numEdges條邊,建立鄰接矩陣
    {
        for (i = k; i < G->numVertexes; i++)
        {
            G->arc[i][k] = G->arc[k][i];    //因為是無向圖,所有是對稱矩陣
        }
    }
}

//鄰接矩陣轉編輯數組,按照權值排序,由小到大
void MGraph2EdgeArr(MGraph G, Edge* edge)
{
    int i, j,k=0;
    Edge temp;
    int min;
    for (i = 0; i < G.numVertexes;i++)
    {
        for (j = i + 1; j < G.numVertexes;j++)
        {
            if (G.arc[i][j]!=INFINITY)    //有邊
            {
                edge[k].begin = i;
                edge[k].end = j;
                edge[k].weight = G.arc[i][j];
                k++;
            }
        }
    }

    //按照冒泡大小進行排序
    for (i = 0; i < k;i++)
    {
        for (j = i; j < k;j++)
        {
            if (edge[j].weight<edge[i].weight)
            {
                temp = edge[i];
                edge[i] = edge[j];
                edge[j] = temp;
            }
        }
    }
}

//顯示鄰接矩陣
void showGraph(MGraph G)
{
    for (int i = 0; i < G.numVertexes; i++)
    {
        for (int j = 0; j < G.numVertexes; j++)
        {
            if (G.arc[i][j] != INFINITY)
                printf("%5d", G.arc[i][j]);
            else
                printf("    0");
        }
        printf("\n");
    }
}

//並查集操作,獲取一個頂點f的根節點下標
int Find(int* parent, int f)
{
    while (parent[f] > 0)
        f = parent[f];
    return f;
}

//使用克魯斯卡爾算法進行最小生成樹的創建
void MiniSpanTree_Kruskal(MGraph G)
{
    Edge edges[MAXVEX];    //定義邊集數組
    int parent[MAXVEX];    //定義生成樹的父節點,也可以使用結構體,但是更加浪費空間
    int i, n, m;
    MGraph2EdgeArr(G, edges);    //鄰接矩陣轉邊集數組

    //開始進行初始化
    for (i = 0; i < MAXVEX; i++)
        parent[i] = 0;    //這里是0代表根節點,我們也可以使用-1,正負無窮等
    //進行合並操作
    for (i = 0; i < G.numEdges; i++)
    {
        n = Find(parent, edges[i].begin);    //找到頂點edges[i].begin的根節點下標
        m = Find(parent, edges[i].end);        //找到頂點edges[i].end的根節點位置
        if (n != m)    //若是根節點下標不是一樣的,就說不在一棵樹上,不會形成環,我們放心合並
        {
            parent[n] = m;    //將n樹合並到m樹,表示該邊被放入生成樹
            printf("(%d,%d) %d ", edges[i].begin, edges[i].end, edges[i].weight);
        }
    }
}
View Code

四:總結

將圖中各邊按其權值由小到大的次序順序選取,若選邊后不形成回路,則保留作為一條邊, 若形成回路則除去.依次選夠(n-1)條邊,即得最小生成樹.(n為頂點數)
克魯斯卡爾算法的時間復雜度為O(eloge)跟邊的數目有關,適合稀疏圖。(若圖的頂點數為n,邊數為e)。

 


免責聲明!

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



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