并查集

0_并查集

  • 如果A,B两个节点有一条路径,则A,B是互连(连通)的
  • 我们的主要任务是让集合中任意2个节点产生一条路径,或者确认2个节点是否是连通的
  • 实现上,我们可以用一个数组来保存所有的数据,然后根据其内容来实现具体的实现

1_并查集Test类

  • 对于n各节点的集合,随机执行n次的连通,再随机执行n的查找,确认所用的时间
namespace UnionFindTestHelper{

    void testUF1( int n ){

        srand( time(NULL) );
        UF1::UnionFind1 uf = UF1::UnionFind1(n);

        time_t startTime = clock();

        for( int i = 0 ; i < n ; i ++ ){
            int a = rand()%n;
            int b = rand()%n;
            uf.unionElements(a,b);
        }
        for(int i = 0 ; i < n ; i ++ ){
            int a = rand()%n;
            int b = rand()%n;
            uf.isConnected(a,b);
        }
        time_t endTime = clock();

        cout<<"UF1, "<<2*n<<" ops, "<<double(endTime-startTime)/CLOCKS_PER_SEC<<" s"<<endl;
    }
}

2_最简单的并查集实现

  • 查找非常快,但是连通操作由于需要将所有已经连通的节点依次找到,所以平均时间复杂度为O(N);
  • 实现: 一开始每个元素都保存自己的下标,如果2个元素需要连通,将其中一个的所有连通的元素内容都改成需要连通的那个元素的内容即可
  • 优缺点: 查询时间很快,但是连通需要遍历整个数组
namespace UF1 {
    class UnionFind1 {
    private:
        int *id;
        int count;

    public:
        UnionFind1(int n) {
            count = n;
            id = new int[n];
            for (int i = 0; i < n; ++i) {
                id[i] = i;
            }
        }

        ~UnionFind1() {
            delete[] id;
        }

        //QuickFind操作 ,时间复杂度为O(1);
        int find(int p) {
            assert(p >= 0 && p <= count);
            return id[p];
        }

        bool isConnected(int p, int q) {
            return find(p) == find(q);
        }
     //O(N)
        void unionElements(int p, int q) {
            int pId = id[p];
            int qId = id[q];
            if (pId == qId) return;
            for (int i = 0; i < count; ++i) {
                if (pId == id[i])
                    id[i] = qId;
            }
        }
    };
}

3_并查集的改进1

  • 改进的思路为:让每个元素都保存自己向上连通的那个节点的下标,如果向上没有连通,则保存自己的下标
  • 优缺点:查找速度会慢一点,每次找到自己最上面的父节点,连通速度有提升,但不是数量级上的,因为直接让第二个节点的父节点与第一个节点的父节点连接即可(但是连通之前还是需要找到其父节点)
namespace UF2 {
class UnionFind2 {
private:
    int *parent;
    int count;

public:
    UnionFind2(int n) {
        count = n;
        parent = new int[n];
        for (int i = 0; i < n; ++i) {
            parent[i] = i;
        }
    }

    ~UnionFind2() {
        delete[] parent;
    }

    int find(int p) {
        assert(p >= 0 && p <= count);
        while(p != parent[p])
            p = parent[p];
        return p;
    }

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

    void unionElements(int p, int q) {
        int pParent = find(p);
        int qParent = find(q);

        if( pParent == qParent )
            return;
        parent[qParent] = pParent ;
    }
};
}

4_并查集的改进2

  • 思路: 给每个节点增加一个数据项来保存其作为根节点的情况下,这个集合树的层数(rank)/节点个数(sz[]),我们尽量让层数低的节点去链接在层数高的节点上,这个树的高度会有保障
  • 这个版本的查找和连通平均时间复杂度接近O(1),证明可以查看下算法导论
  • 路径压缩:连通的时候如果树的高度越低速度越快,如果每次查找的时候我们都可以压缩一下树的高度,那么可能会对连通有帮助(实测帮助不大,但是思路是好的)
namespace UF3 {
class UnionFind3{
private:
    int *parent;
 //   int *sz; //sz[i]表示以i为根的集合中元素个数
    int* rank; //rank[i]表示以i为根的集合中树的层数
    int count;

public:
    UnionFind3(int n) {
        count = n;
        parent = new int[n];
        rank = new int[n];
        for (int i = 0; i < n; ++i) {
            parent[i] = i;
            rank[i] = 0;
        }
    }

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

    //QuickFind操作 ,时间复杂度为O(N);
    int find(int p) {
        assert(p >= 0 && p <= count);
//            while(p != parent[p]){
//                p = parent[parent[p]];
//            }
        //路径压缩,树的高度最高只有2层
        if(p != parent[p])
            parent[p] = find(parent[p]);
        return parent[p];
    }

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

    void unionElements(int p, int q) {
        int pParent = find(p);
        int qParent = find(q);

        if( pParent == qParent )
            return;
        if(rank[pParent] < rank[qParent]){
            parent[p] = qParent;
        }else if(rank[qParent] < rank[pParent]){
            parent[q] = pParent;
        }else{
            parent[p] = qParent;
            rank[qParent]++;
        }

    }
};
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值