并查集(UnionFind)

为什么引入并查集

并查集的引入是为了解决动态连通问题。在动态连通场景中解决给定的两节点,判断它们是否连通,如果连通,不需要给出具体路径。(而对于要给出具体路径的问题可以采用DFS).

什么是并查集

在计算机科学中,并查集是一种树型的数据结构,其保持着用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。一般该数据结构定义了两种操作:
- Find:确定一个元素属于哪个子集
- Union:将两个子集合并成一个集合

并查集是一种树形结构:初始让所有元素独立成树,然后根据需要将连通元素合并成一棵树,合并方式为将一棵树的父节点索引到另一颗树上。

并查集的详解

并查集的基本操作有:
1、MakeSet(x):把每一个元素初始化为一个集合,初始化后每一个元素的父节点就是它自己

int parent[MAX];
int rank[MAX];

void MakeSet(int x)
{
    father[x] = x;
    rank[x] = 0;
}

2、FindSet(x):查找一个元素所在的集合。不断向上查找,直到根节点。

int FindSet(int x)
{
    int r = x, tmp;
    while (parent[r] != r)  //找到根节点
        r = parent[r];
    //路径压缩
    while (x != r)
    {
        tmp = parent[x];
        parent[x] = r;
        x = tmp;
    }

    return x;
}

3、Union(x,y):合并两个集合。关键是将一个集合的根节点变成另一个集合的根节点。

void Union(int x, int y)
{
    x = FindSet(x);
    y = FindSet(y);
    if (x == y)         //有相同的根节点,不用合并啦
        return;
    if (rank[x] > rank[y])
        parent[y] = x;  //x所在的树高度比y高,让x成为y的根节点
    else
    {
        parent[x] = y;
        if (rank[x] == rank[y])
            rank[y]++;
    }
}

并查集的优化方法:
- FindSet(x)的路径压缩:当寻找根节点时,最坏的情况是一棵树变为一条链,这样时间复杂度为O(n);因此这里采用路径压缩,即每次递推找到根节点的后,回溯的时候将它的子孙节点都指向根节点。这样FindSet(x)复杂度变为O(1)了。
- Union(x,y)合并时按照元素少的合并到元素多的集合,这样合并后树的高度相对会小。

并查集实现及例子
/*
leetcode 130. Surrounded Regions

Given a 2D board containing 'X' and 'O' (the letter O), capture all regions 
surrounded by 'X'.
A region is captured by flipping all 'O's into 'X's in that surrounded region.

For example,
X X X X
X O O X
X X O X
X O X X
After running your function, the board should be:

X X X X
X X X X
X X X X
X O X X

题目大意:将被'X'包围的'O'换为'X'
解题思路:一定要读懂题目的意思。这里被'X'包围的'O'的对立一面就是在边缘上的'O'以及
和边缘连通的'O'

*/

#include <iostream>
#include <vector>

using namespace std;

class Solution {

private:
    vector<int> root;       //并查集的根节点
    vector<int> isEdge;     //是否为边缘上的'O'或其连通的
    vector<int> rank;       //树的高度,为了加速
    //初始化并查集
    void MakeSet(int len)
    {
        root = vector<int>(len, 0);
        isEdge = vector<int>(len, 0);
        rank = vector<int>(len, 0);
        for (int i = 0; i < len; ++i)
        {
            root[i] = i;
        }
    }
    //查找根节点
    int Find(int p)
    {
        int r = p;
        while (root[r] != r)
            r = root[r];    //find the root
        int tmp;
        while (p != r)      //路径压缩
        {
            tmp = root[p];
            root[p] = r;
            p = tmp;
        }

        return r;
    }
    //合并:这里要注意,合并时边缘也需要改变
    void Union(int p, int q)
    {
        p = Find(p);
        q = Find(q);
        if (p == q)
            return;
        if (rank[p] >= rank[q])
        {
            root[q] = p;
            if (isEdge[q])
                isEdge[p] = 1;
            if (rank[p] == rank[q])
                ++rank[p];
        }
        else//(rank[q] > rank[p])
        {
            root[p] = q;
            if (isEdge[p])
                isEdge[q] = 1;
        }
    }
public:

    void solve(vector<vector<char>>& board) {

        int rows = board.size();
        if (rows == 0)
            return;
        int cols = board[0].size();
        if (rows < 3 || cols < 3)
            return;

        int len = rows*cols;
        //初始化
        MakeSet(len);
        for (int i = 0; i < rows; ++i)
        {
            for (int j = 0; j < cols; ++j)
            {
                if ((i == 0 || i == rows - 1 || j == 0 || j == cols - 1) && (board[i][j] == 'O'))
                {
                    isEdge[i*cols + j] = 1;
                }
            }
        }
        //合并连通域
        for (int i = 0; i < rows; ++i)
        {
            for (int j = 0; j < cols; ++j)
            {
                //cout << "(" << i << "," << j << ")" << endl;
                if (board[i][j] == 'O' && i < rows - 1 && board[i][j] == board[i + 1][j] )
                    Union(i*cols + j, (i + 1)*cols + j);
                if (board[i][j] == 'O' && j < cols - 1 && board[i][j] == board[i][j + 1])
                    Union(i*cols + j, i*cols + j + 1);
            }
        }
        //替换
        for (int i = 0; i < rows; ++i)
        {
            for (int j = 0; j < cols; ++j)
            {
                if (board[i][j] == 'O' && !isEdge[Find(i*cols + j)])
                    board[i][j] = 'X';
            }
        }

    }
};



void TEST()
{
    /*vector<vector<char>>v {
        {'X', 'X', 'X', 'X'},
        {'X', 'O', 'O', 'X'},
        {'X', 'X', 'O', 'X'},
        {'X', 'O', 'X', 'X'}
    };*/
    vector<vector<char>>v{
        { 'X', 'O', 'X', 'X' },
        { 'O', 'X', 'O', 'X' },
        { 'X', 'O', 'X', 'O' },
        { 'O', 'X', 'O', 'X' },
        { 'X', 'O', 'X', 'O' },
        { 'O', 'X', 'O', 'X' }
    };
    Solution sol;

    sol.solve(v);

    for (auto vec : v)
    {
        for (auto ch : vec)
            cout << ch << " ";
        cout << endl;
    }
}

int main()
{
    TEST();

    return 0;
}

例子2:
poj1611 http://blog.csdn.net/charles1e/article/details/59536384

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值