數據結構與算法之最好學的最小生成樹

 

數缺形時少直觀,形缺數時難入微。html

——華羅庚算法

最小生成樹問題是我在各項圖論問題中最早理解與解決的,其目的就是在連通圖中選擇出:數據結構

使得各點構成聯通的最小邊權的邊集app

其中用到的數據結構與算法也是相對很好理解的並查集Kruskal算法,我在我以前的文章小話數據結構-圖 (聚焦與於實現的理解)也有提到過,如今再來系統的闡述一下這問題的解決思路。函數

並查集

並查集是一種樹型的數據結構,用於處理一些不相交集合的合併及查詢問題spa

並查集是一個寫法簡單,常常使用到的數據結構,主要操做有如下三種code

初始化操做htm

int p[N]; //存儲每一個點的祖宗節點
for (int i = 1; i <= n; i ++ ) 
        p[i] = i;// 初始化,節點編號是1~n

 

查找函數blog

int find( int x ){
        if(p[x] != x) p[x] = find(p[x]);
        return p[x];//返回的是x的祖宗節點
    }

 

合併操做排序

p[find(a)] = find(b);//將a加入b的祖宗的集合

 

並查集還能夠維護每個子集的大小、或是自子集到祖宗節點的距離,給出如下代碼,只是使用Kruskal算法只須要使用樸素的並查集就能夠了。

    int p[N], size[N];//p[]存儲每一個點的祖宗節點, size[]表示祖宗節點所在集合中的點的數量
int find(int x){
        if (p[x] != x) p[x] = find(p[x]);
        return p[x];
    }
​
    for (int i = 1; i <= n; i ++ ){// 初始化,節點編號是1~n
        p[i] = i;
        size[i] = 1;
    }
​
    // 合併a和b所在的兩個集合並儲存集合中元素個數:
    size[find(b)] += size[find(a)];
    p[find(a)] = find(b);

 

    
int p[N], d[N];//p[]存儲每一個點的祖宗節點, d[x]存儲x到p[x]的距離
int find(int x){
        if (p[x] != x){
            int u = find(p[x]);
            d[x] += d[p[x]];//繼承偏移量
            p[x] = u;
        }
        return p[x];
    }
​
    for (int i = 1; i <= n; i ++ ){// 初始化,節點編號是1~n
        p[i] = i;
        d[i] = 0;
    }
​
    // 合併a和b所在的兩個集合:
    p[find(a)] = find(b);
    d[find(a)] = distance; // 初始化find(a)的偏移量

 

Kruskal算法【O(mlogm)】

這個頂着一個高端名字的針對解決最小生成樹的算法,也就是一個徹頭徹尾的貪心思想的算法,基本的步驟以下

①:將全部邊按照權值從小到大排序

②:將全部邊依次放入圖中,若是沒有連入新的點,則丟棄不要。

③:當整個圖聯通時,返回結果

這裏給一張別人博客裏很是直觀的動圖

在②步驟中,並查集就能夠發揮出其做用,快速的斷定出當前選擇的邊的點是否在一個集合中,從而方便的實現算法。

那咱們直接用代碼來實現:

int n, m;
int p[N];
​
struct Edge{
    int a, b, w;
}edges[M];
​
int find(int x){
    if (p[x] != x) 
        p[x] = find(p[x]);
    return p[x];
}
​
int kruskal(){
    sort(edges, edges + m);//排序
    for (int i = 1; i <= n; i ++ ) 
        p[i] = i;
    int res = 0, cnt = 0;//res記錄權值,cnt記錄已選擇的邊數
    for (int i = 0; i < m; i ++ ){
        int a = edges[i].a, b = edges[i].b, w = edges[i].w;
​
        a = find(a), b = find(b);
        if (a != b) {//將選擇的邊併入圖中
            p[a] = b;
            res += w;
            cnt ++ ;
        }
    }
​
    if (cnt < n - 1) return INF;//若結束後不能使整個圖聯通,則沒法求出結果
    return res;
}

 

至此,kruskal算法就成功實現了,能夠根據實際狀況改變部分參數,從而得到須要求解的部分。

但願個人拋磚引玉能引發更多的思考😄!

相關文章
相關標籤/搜索