算法與數據結構-並查集(並查集森林)(優化)(C++)

描述

並查集是一種用於處理一些不相交集合的合併和查詢問題的樹型數據結構。通常在使用中以森林來表示。用森林的好處是降低了在合併(Union)操作時的時間複雜度。本文以並查集森林實現爲例,學習該數據結構。

思路

  • 用順序表的形式來存儲每個結點,每個結點的內容爲該結點的直接父結點。例如,parent[i]表示第i個節點的父結點的索引。
  • 爲了使並(Union)操作時儘可能保持較低的樹高度,新增一個size順序表,size[i]表示以i爲根結點所在的樹中結點的個數。
  • 初始化令每個結點的父結點爲自身,相應的size都爲1。
  • 查找操作。對傳入的索引進行查找,返回所在樹的根節點的索引。就是一個向上遍歷的過程,直到索引的父結點等於自身就爲根節點。
  • 合併操作。把樹高度較低的那棵樹的根節點指向另一棵樹的根節點。parent[pRoot] = qRoot,兩棵樹合併爲了一棵樹。

實現代碼

//UF1.h
#include<iostream>
#include<cassert>
using namespace std;

namespace UF1 {
    class UnionFind {
    private:
        int *parent;    //表示第i個元素的父結點
        int *size;      //表示以i爲根的集合中的結點個數
        int count;      //結點總數

    public:
        UnionFind(int count) {
            parent = new int[count];
            size = new int[count];
            this->count = count;
            for(int i = 0; i < count; i++) {
                parent[i] = i;
                size[i] = 1;
            }
        }

        ~UnionFind() {
            delete[] parent;
            delete[] size;
        }

        int find(int p) {
            while(p != parent[p]) {
                p = parent[p];
            }
            return p;
        }

        bool isConnected(int p, int q) {
            return find(p) == find(q);
        }

        void unionElement(int p, int q) {
            int pRoot = find(p);
            int qRoot = find(q);

            if(pRoot == qRoot)
                return;
            if(size[pRoot] < size[qRoot]) {
                parent[pRoot] = qRoot;
                size[qRoot] += size[pRoot];
            }
            else {
                parent[qRoot] = pRoot;
                size[pRoot] += size[qRoot];
            }
            return;
        }
    };
}

優化

爲了降低樹的高度,這裏採用了計算每棵樹的結點個數從而判斷要接到哪棵樹的根上。但是,並不一定結點數多樹的高度就一定高,所以我們可以引入一個rank順序表,rank[i]表示以i爲根節點的樹的層數。
具體實現如下:

//UF2.h
#include<iostream>
#include<ctime>
using namespace std;

namespace UF2 {
    class UnionFind {
    private:
        int *parent;
        int *rank;      //rank[i]表示以i爲根的集合樹的層數
        int count;

    public:
        UnionFind(int count) {
            parent = new int[count];
            rank = new int[count];
            this->count = count;
            for(int i = 0; i < count; i++) {
                parent[i] = i;
                rank[i] = 1;
            }
        }

        ~UnionFind() {
            delete[] parent;
            delete[] rank;
        }

        int find(int p) {
            while(p != parent[p]) {
                p = parent[p];
            }
            return p;
        }

        bool isConnected(int p, int q) {
            return parent[p] == parent[q];
        }

        void unionElement(int p, int q) {
            int pRoot = find(p);
            int qRoot = find(q);

            if(pRoot == qRoot) 
                return;

            if(rank[pRoot] < rank[qRoot]) 
                parent[pRoot] = qRoot;
            else if(rank[qRoot] < rank[pRoot]) 
                parent[qRoot] = pRoot;
            else {
                parent[pRoot] = qRoot;
                rank[qRoot] += 1;
            }
        }
    };
}

測試

測試代碼如下:

#include<iostream>
#include<ctime>
#include"UF1.h"
#include"UF2.h"
using namespace std;

namespace test {
    void testUF1(int n) {
        UF1::UnionFind uf1(n);
        srand(time(NULL));
        clock_t beginTime = clock();    //開始計時
        //並操作
        for(int i = 0; i < n; i++) {
            uf1.unionElement(rand()%n, rand()%n);
        }

        for(int i = 0; i < n; i++) {
            uf1.isConnected(rand()%n, rand()%n);
        }
        clock_t endTime = clock();      //結束

        cout << "UF1 cost time is :" << double(endTime - beginTime) / CLOCKS_PER_SEC << " s" << endl;
    }

    void testUF2(int n) {
        UF2::UnionFind uf2(n);
        srand(time(NULL));

        clock_t beginTime = clock();
        //並操作
        for(int i = 0; i < n; i++) {
            uf2.unionElement(rand()%n, rand()%n);
        }

        for(int i = 0; i < n; i++) {
            uf2.isConnected(rand()%n, rand()%n);
        }
        clock_t endTime = clock();      //結束

        cout << "UF1 cost time is :" << double(endTime - beginTime) / CLOCKS_PER_SEC << " s" << endl;

    }
}

int main(int argc, char const *argv[])
{
    int n = 10000000;
    test::testUF1(n);
    test::testUF2(n);
    
    return 0;
}

測試結果

UF1 cost time is :1.459 s
UF1 cost time is :1.118 s

最後

  • 由於博主水平有限,難免有疏漏之處,歡迎讀者批評指正!
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章