算法導論——最小生成樹

  對於一個連通圖來講,咱們能夠去掉其中一些邊依然保持其連通的性質,在這些圖中存在一個或多個圖,他們的路徑總和是最小的,這樣的圖必然是樹。由於,若是說圖中存在環,則去掉環的一條邊依然能夠保證連通性,這與總路徑和最小是矛盾的。這樣的圖被稱爲最下生成樹。城市間鋪設電路就能夠利用最小生成樹來進行規劃。node

如圖所示的黑色路徑構成了最小生成樹,去掉bc並加入ah也是一棵最小生成樹,可見一個圖的最小生成樹並不必定是惟一的。算法

  最小生成樹可使用安全邊的策略進行生成:假設集合A是最小生成樹的子集,咱們能夠找到一條邊加入到A中,依然保持A是最小生成樹的子集,這樣的邊就被稱爲安全邊。爲了尋找安全邊,咱們定義下方黑色部分ab與de邊構成了集合A,(A,V-A)稱爲G的一個切割。若是一條邊(v,u)一個端點屬於A而另外一個端點屬於V-A,則稱它爲橫跨切割(A,V-A)。在橫跨切割的邊中,能夠找到一條或多條權重最小的邊,該邊稱爲輕量級邊,輕量級邊便是安全邊。由於A必定要與V-A部分產生鏈接,這就必需要經過橫跨切割的邊,而輕量級邊是其中最短的,因此一定屬於最小生成樹。下圖所示ah bj bc cd df ef爲橫跨切割的邊,其中cd爲輕量級邊。爲了尋找輕量級邊,有兩種基於貪心策略的算法。安全

Kruskal算法

  Kruskal算法的思想是,尋找鏈接森林中兩棵不一樣樹的邊裏面的最短邊做爲安全邊加入集合A。可使用不相交集合來維護這樣的結構,對每一個結點創建一棵樹。經過FIND-SET來返回結點屬於哪棵樹,有邊加入集合A時合併u和v所在的樹。時間複雜度可表示爲O(ElgV)。spa

圖中所示爲各邊按照長度順序不斷遍歷檢查是否要加入到集合A中,直到遍歷完全部的邊結束。3d

#include<stdio.h>
#include<vector>
#include <algorithm>
using namespace std;
#define SIZE 10

class Node{
public:
    int rank;
    Node *p;
};

class Road{
public:
    int u;
    int v;
    int weight;
};

int G[SIZE][SIZE];//鄰接矩陣,參數初始化略
Node nodes[SIZE];

void makeSet(Node x){
    x.p = &x;
    x.rank = 0;
}

void Union(Node x,Node y){
    if(x.rank > y.rank)
        y.p = &x;
    else{
        x.p = &y;
        if(x.rank == y.rank)
            y.rank++;
    }
}

Node* findSet(Node x){
    if(x.p != &x)
        x.p = findSet(*x.p);
    return x.p;
}

bool com (Road a,Road b) {
    return (a.weight<b.weight); //升序排列
}

vector<Road> MSTKruskal(){
    vector<Road> A;
    int i,j;
    vector<Road> roads;
    for(i = 0; i < SIZE; i++){
        nodes[i] = *new Node();
        makeSet(nodes[i]);//每棵樹包含一個結點
    }
    for(i = 0; i < SIZE; i++){
        for(j = i + 1; j < SIZE; j++){
            if(G[i][j] != 0){
                Road road = *new Road();
                road.u = i;
                road.v = j;
                road.weight = G[i][j];
                roads.push_back(road);
            }                
        }
    }
    sort(roads.begin(),roads.end(),com);//對全部路徑進行降序排列
    for(i = 0; i < roads.size(); i++){
        Road road = roads[i];
        if(findSet(nodes[road.u]) != findSet(nodes[road.v])){
            A.push_back(road);
            Union(nodes[road.u],nodes[road.v]);
        }
    }
    return A;
}

Prim算法

  Prim算法的思路是從根結點開始加入集合A,不斷尋找A與V-A相連邊中最短的,即橫跨(A,V-A)的輕量級邊。能夠將V-A到A距離的最小值存儲到優先隊列Q中來減小每次遍歷尋找最短邊的時間。優先隊列能夠經過最小二叉堆或者斐波那契堆來實現,前者的漸進時間爲O(ElgV)後者改進爲O(E+VlgV)code

#include<stdio.h>
#include<vector>
using namespace std;
#define SIZE 10
#define INFI 10000

class Road{
public:
    int u;
    int v;
    int weight;
};

int G[SIZE][SIZE];//鄰接矩陣,參數初始化略

vector<Road> MSTPrim(int root){
    vector<Road> A;
    int i;
    Road roads[SIZE];//記錄到達A的最短路徑
    for(i = 0; i < SIZE; i++){
        roads[i] = *new Road();
        if(G[root][i] != 0){
            roads[i].u = root;
            roads[i].v = i;
            roads[i].weight = G[root][i];
        }
        else
            roads[i].weight = INFI;
    }
    while(A.size() != SIZE - 1){
        int min = 0;
        for(i = 1; i < SIZE; i++){
            if(roads[i].weight < roads[min].weight && roads[i].weight > 0)
                min = i;//尋找最短的路徑
        }
        A.push_back(roads[min]);
        roads[min].weight = -1;//表示該點已在A中
        for(i = 0; i < SIZE; i++){
            if(G[min][i] < roads[i].weight)
                roads[i].weight = G[min][i];//更新到達A的最短長度
        }
    }
    return A;
}
相關文章
相關標籤/搜索