【筆記】最小生成樹Minimum Spanning Tree

最小生成樹

最小生成樹的定義:
(1)首先要有一個帶權重值的無向圖,也就是Edge不再僅僅是連接兩個vertex,還具有一個權重值weight
(2)最小生成樹就是這個Graph的一棵子樹,包含了Graph中的所有節點,並且沒有任何的循環和週期
(3)最小生成樹中的所有連接線的權重加起來爲最小
這裏寫圖片描述

要實現最小生成樹算法,首先需要一個表示帶有權重值的圖的API,之前博客已經寫過無向圖和有向圖的基本API,而帶有權重值的圖的API與之前類似,不過這裏需要將連接線Edge抽象爲一個類
如下爲Edge類


// Edge的Abstraction

class Edge{
private:
    int v;
    int w;
    double weight; // 該edge的權重

public:

    // 邊緣上的兩個節點
    Edge(){

    }
    Edge(int _v , int _w , double _weight){
        this->v = _v;
        this->w = _w;
        this->weight = _weight;
    }

    //返回其中的一個節點v
    int either(){
        return this->v;
    }

    //返回另外一個節點w
    int other(int vertex){
        if(vertex == v){
            return this->w;
        }
        return v;
    }

    //得到該權重值的大小
    double getWeight(){
        return this->weight;
    }

    // 兩條連接線比較大小
    int compareTo(Edge that){
        if(this->getWeight() > that.getWeight()){
            return -1;

        }else if (this->getWeight() == that.getWeight()) {
            return 0;

        }else if (this->getWeight() > that.getWeight()) {
            return 1;
        }
    }

    // 重載操作符用於比較兩個線的大小
    bool operator <(Edge & that){
        return this->getWeight() < that.getWeight();
    }

    bool operator ==(Edge & that){
        return this->getWeight() == that.getWeight();
    }

    bool operator >(Edge & that){
        return this->getWeight() > that.getWeight();
    }

};

接下來就是帶有權值的圖了:WeightedEdgesGraph


//*****************帶權連接線的圖API : Weighted edges Graph(Undirected graph)****************************

class WeightedEdgesGraph{
private:
    int num_vertex; // Graph中的頂點數量
    Bag<Edge> * adj; // 包中裝的都是edge了, 不再是int的節點了


public:
    WeightedEdgesGraph(int V){
        num_vertex = V;
        adj = new Bag<Edge>[num_vertex];
    }


    void addEdges(Edge e){
        int v = e.either();
        int w = e.other(v);

        adj[v].add(e);
        adj[w].add(e);
    }

    Bag<Edge> adjacent(int v){
        return adj[v];
    }

    int V(){
        return num_vertex;
    }

    int E(){
        int num_edges = 0;
        for(int i =0 ;i < num_vertex; ++i){
            num_edges += adj[i].size();
        }
        return num_edges;
    }

    // 返回所有的連接線(有重複連接線)
    Bag<Edge> edges(){
        Bag<Edge> total_edges;

        for(int i =0 ;i < num_vertex; ++i){
            for(int j = 0 ;j < this->adj[i].size() ; ++j){
                total_edges.add(this->adj[i][j]);
            }
        }

        return total_edges;
    }


};

Kruskal最小生成樹算法實現

首先是Kruskal的Minimum spanning tree的算法,該算法使用到了priority queue以及Union-find這兩個數據結構作爲輔助。priority queue用於將edges按從小到大的順序排列,並每次取出權重最小的edge,Union-find用於判斷edge的兩個端點是否構成了一個cycle。


// *************************************Kurskal 的Minimum spanning tree最小生成樹算法**************************************


// UnionFind 並查集算法,可以用於檢測兩個元素是否屬於同一個類
class UnionFind{
private:
    int * id;
    int N;
    int * size; // 用於評估這個並集裏的數量

    // 查找根節點
    int root(int p){
        while (p != id[p]) {
            p = id[p];
        }
        return p;
    }

public:
    UnionFind(int _N){
        this->N = _N;
        id = new int[N];
        size = new int[N];

        for(int i= 0; i < N;i++){
            this->id[i] = i;
            this->size[i] = 1;
        }
    }

    bool connected(int p , int q){
        return root(p) == root(q);
    }

    void union_(int p ,int q){
        if(p == q){
            return;
        }

        int root_p = root(p);
        int root_q = root(q);

        if(root_p == root_q){
            return;
        }

        int size_p = size[root_p];
        int size_q = size[root_q];

        if(size_p <= size_q){
            id[root_p] = root_q;
            size[root_q] += size_p;

        }else if (size_p > size_q) {
            id[root_q] = root_p;
            size[root_p] += root_q;

        }


    }

};

//Priority Queue


// 從小到大排序的priority queue
template<typename T>
class MinPriorityQueue{
public:
    int capacity; // 容量
    int N; // 當前元素位置

    T * pq;

    // 交換元素
    void swap(T a[] , int i , int j){
        T temp = a[i];
        a[i] = a[j];
        a[j] = temp;
    }

    // 比較兩個元素大小

    int compare(T a , T b){
        if(a < b){
            return -1;

        }else if (a == b) {
            return 0;

        }else if (a > b == 1) {
            return 1;
        }
    }


    // 將元素游上來
    void swim(int k){

        // 只要K還沒有swim到頂部, 並且 父元素比k要大,則繼續往上swim
        while (k > 1 && compare(pq[k / 2] , pq[k] ) == 1 ) {
            swap(pq , k / 2 , k);
            k = k / 2;
        }

    }

    // 將元素沉下去
    void sink(int k){

        while (2 * k + 1 < N) {
            int j = 2 * k;
            if(j < N && compare(this->pq[j] , this->pq[j + 1]) == 1){
                j++;
            }

            if(compare(this->pq[k] , this->pq[j]) != 1){
                break;
            }

            swap(this->pq , k , j);

            k = j;
        }

    }

    // 是否已滿
    bool isFull(){
        return N == capacity + 1;
    }

    // 是否爲空
    bool isEmpty(){
        return N == 1;
    }

    void resizing(int capacity){

        T * old_pq = this->pq;
        this->pq = new T[capacity + 1];
        this->capacity = capacity;

        for(int i =0 ;i < N; i++){
            pq[i] = old_pq[i];

        }

    }

public:

    //  構造函數
    MinPriorityQueue(){
        this->capacity = 1;
        this->N = 1;
        this->pq = new T[capacity + 1];
    }

    // 插入元素T

    void insert(T item){
        if(this->isFull()){
            this->resizing(capacity * 2);
        }

        this->pq[N] = item;
        swim(N);
        N++;

    }

    // 取出最小的
    T delMin(){

        if(isEmpty()){
            cout << "Error : the prioiry queue is empty !" << endl;

        }else {

            T item  = this->pq[1];

            swap(this->pq , 1 , --N);

            sink(1);

            return item;

        }



    }

    // 返回priority queue的大小
    int size(){
        return this->N - 1;
    }


};


//Kruskal's Minimum Spanning Tree
class KruskalMST{

private:
    MinPriorityQueue<Edge> min_pq;
    vector<Edge> mst;

    int size_of_edges ;

public:


    KruskalMST(WeightedEdgesGraph graph){
        Bag<Edge> all_edges = graph.edges();

        size_of_edges = 0;
        //先將所有Edge插入到min priority queue中, 以方便從小到大排序
        for(int i =0 ;i < all_edges.size() ; ++i){
            min_pq.insert(all_edges[i]);
        }

        // 造一個Union-Find
        UnionFind UF(graph.V());

        while (!min_pq.isEmpty() && this->size_of_edges == graph.V() - 1) {
            Edge e = min_pq.delMin(); // 獲取權重值最小的那條edge

            int v = e.either(); // edge的一個節點
            int w = e.other(v);

            if(!UF.connected(v , w)){
                mst.push_back(e);
                size_of_edges++;

                UF.union_(v , w);

            }

        }
    }




    // 重載構造函數 ,可直接接受priority qeueue作爲輸入參數
    KruskalMST(MinPriorityQueue<Edge> edges_pq){
        UnionFind UF(edges_pq.size());

        while (!edges_pq.isEmpty() && this->size() < edges_pq.size() - 1) {

            Edge e = edges_pq.delMin();
            int v = e.either();
            int w = e.other(v);

            if(!UF.connected(v , w)){
                mst.push_back(e);
                size_of_edges++;

                // 再將v , w連接起來
                UF.union_(v , w);
            }

        }
    }



    int size(){
        return this->size_of_edges;
    }

    // 返回所有的Minimal spanning tree
    vector<Edge> edges(){
        return mst;
    }




};

Prim最小生成樹

之後又是Prim的最小生成樹,Prim的MST算法並非一上來就把所有的edges按從小到大的順序排列好再一個一個加到MST中。Prim算法實際上是循序漸進的,一次加幾條edge,一個節點一個節點的來,不慌不忙的,不過本質上都差不多。

// *************************************Prim的Minimul spanning tree最小生成樹算法**************************************

class PrimMST{
private:
    MinPriorityQueue<Edge> min_pq;
    vector<Edge> mst;
    int size_of_edges;
    bool * marked;

    void visit(WeightedEdgesGraph graph , int v){
        if(marked[v]){
            return;
        }
        marked[v] = true;
        Bag<Edge> edge_of_v = graph.adjacent(v);

        for(int i = 0 ;i < edge_of_v.size() ; ++i){
            Edge e = edge_of_v[i];

            int w = e.other(v);

            if(!marked[w]){
                min_pq.insert(e);
            }
        }
    }

public:

    //構造函數
    PrimMST(WeightedEdgesGraph graph){

        // 先初始化
        size_of_edges = 0;

        marked = new bool[graph.V()];

        for(int i = 0; i < graph.V() ; ++i){
            marked[i] = false;
        }

        visit(graph , 0);


        while ( !min_pq.isEmpty() && size_of_edges < graph.V() - 1) {
            Edge e = min_pq.delMin();
            int v = e.either();
            int w = e.other(v);

            if(marked[v] && marked[w]){
                continue;
            }

            mst.push_back(e);
            size_of_edges++;

            if(!marked[v]){
                visit(graph , v);
            }

            if(!marked[w]){
                visit(graph , w);
            }

        }


    }


    vector<Edge> edges(){
        return mst;
    }



};
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章