每日一题27:并查集

如何快速地判断一个元素是否在一个集合中?如何将一个元素或一个集合合并到另一个集合中?并查集就是一种支持集合快速合并与查找的结构。简单的实现可以做到在O(1)时间复杂度内找到一个元素所属的集合,但是却要在O(N)时间内完成合并。假设有N个元素,编号为0到N-1,那么可以构造一个同样大小的整数数组A,A中每个元素存放对应集合元素所属类的编号,查找一个元素所在的集合时,到A中检查对应下标的元素就知道了一个所属的集合(O(1))。当要合并时,把涉及的元素在A中对应下标的元素设为合并后的集合标识即可(O(N))。复杂一点的实现则可以将合并与查找复杂度都做到O(lgN)。复杂做法将集合元素在数组A中组织成树状结构。与简单实现不同,数组A中不再简单地放置元素所属集合的标号,而是放置元素的父节点在原数组中下标,而根节点处放置集合元素个数的相反数。并查集的原理图如下:
《每日一题27:并查集》
如图,查找元素5所在集合时,先看位置5处的值,4>0,所以查看位置4处的值,为0,返回0.查找元素3所在的集合时,先看位置3处的值,-1 < 0,返回3,又查找元素5和元素3返回的值不同,所以元素5和元素3属于不同的集合。
下面看并查集完整代码:

#ifndef _UNIONFINDSET_H_
#define _UNIONFINDSET_H_
#include "Vector.h"
namespace MyDataStructure
{
    class UnionFindSet
    {
    public:
        UnionFindSet(){}
        UnionFindSet(int capacity)
        {
            for (int i = 0; i < capacity;++i)
            {
                set.PushBack(-1);
            }
        }
        UnionFindSet(const UnionFindSet& rhs)
        {
            set = rhs.set;
        }
        UnionFindSet& operator = (const UnionFindSet& rhs)
        {
            set = rhs.set;
            return *this;
        }
        int Find(int index)
        {
            if (index < 0 || index >= set.Size()) return -1;
            else if (set[index] < 0)
                return index;
            else
            {
                //路径压缩,但是由于合并时总是先找到集合的根,
                //这一步的意义似乎不大了,所谓路径压缩是指
                //在寻找某一元素根节点的路径上,将其所有的父节点
                //都直接指向根节点,从而达到降低集合树高度的目的
                return set[index] = Find(set[index]);
            }
        }
        bool Union(int root1, int root2)
        {
            int s1 = Find(root1), s2 = Find(root2);
            if (s1 >= 0 && s2 >= 0 && s1 != s2)
            {
                if (set[s1] <= set[s2])
                {
                    set[s1] += set[s2];
                    set[s2] = s1;

                }
                else
                {
                    set[s2] += set[s1];
                    set[s1] = s2;
                }
                return true;
            }
            return false;
        }
    private:
        Vector<int> set;
    };
}

#endif

测试代码:

// UnionFindSetTest.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "../include/UnionFindSet.h"
#include <iostream>

using namespace MyDataStructure;
using namespace std;

int _tmain(int argc, _TCHAR* argv[])
{
    UnionFindSet ufs(21);
    ufs.Union(0, 1);
    ufs.Union(3, 1);
    ufs.Union(2, 3);
    ufs.Union(15, 19);
    ufs.Union(15, 2);
    cout << ufs.Find(0) << endl;
    cout << ufs.Find(1) << endl;
    cout << ufs.Find(2) << endl;
    cout << ufs.Find(3) << endl;
    cout << ufs.Find(91) << endl;
    cout << ufs.Find(19) << endl;
    cout << ufs.Find(15) << endl;

    return 0;
}

程序运行结果一如所期,不再贴出。
并查集实现简单,速度快,用途广泛,Kruscal最小生成树算法中就用到并查集判断新加入的边是否会构成回路,后面的文章会看到。

点赞