1. 程式人生 > >並查集(UnionFind)

並查集(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