1. 程式人生 > >並查集複習+Leetcode下並查集相關題目

並查集複習+Leetcode下並查集相關題目

目錄

題目:

解析:

一、並查集介紹及其模板

1、並查集介紹

並查集又名不相交集合,實質上是父指標表示法的一般樹,每個節點都有指向其父親的指標(根節點除外),通常用一維陣列實現。並查集最常見的操作有兩種,一是union(x, y),表示將x節點和y節點都指向同一根節點;二是find(x),表示找到x節點的根節點。通過兩種操作,可以將一個集合分為幾個子集合。
在union操作中,有兩種常見的優化策略:一是路徑壓縮策略,即當呼叫一次find(x)時,順帶將x指向根節點;二是按秩合併,即將有較少節點的樹的根指向具有較多節點的樹的根。

2、並查集模板

從1開始到num:

int num;
int* father = new int[100005];

void init(int x)
{
    num = x;
    for(int i = 1; i <= num; i++)
        father[i] = i;
}

int find(int x)
{
    if(father[x] != x)
        father[x] = find(father[x]);
    return father[x];
}

void merge(int x, int y)
{
    int a = find(x);
    int b = find(y);
    if(a != b)
        father[b] = a;
}

int getSubsetNum()
{
    int res = 0;
    for(int i = 1; i <= num; i++)
        if(father[i] == i)
            res++;
    return res;
}

二、Leetcode下並查集題目彙總

547. 朋友圈

題目:

班上有 名學生。其中有些人是朋友,有些則不是。他們的友誼具有是傳遞性。如果已知 A 是 B 的朋友,B 是 C 的朋友,那麼我們可以認為 A 也是 C 的朋友。所謂的朋友圈,是指所有朋友的集合。

給定一個 N * N 的矩陣 M,表示班級中學生之間的朋友關係。如果M[i][j] = 1,表示已知第 i 個和 j 個學生互為朋友關係,否則為不知道。你必須輸出所有學生中的已知的朋友圈總數。

解析:

典型的並查集問題,實質上是求無向圖的連通分支數。只需要將原始的集合分成幾個子集合,每個子集合代表一個朋友圈即可。

int findCircleNum(vector<vector<int>>& M)
{
	if (M.size() == 0)   return 0;
	num = M.size();
	init();
	for (int i = 0; i < M.size(); i++)
		for (int j = i + 1; j < M.size(); j++)
		{
			if (M[i][j])
				merge(i + 1, j + 1);
		}
	return getSubsetNum();
}
    int findCircleNum(vector<vector<int>>& M) {
        int n = M.size(), res = 0;
        vector<bool> visited(n, false);
        for (int i = 0; i < n; ++i) {
            if (visited[i]) continue;
            helper(M, i, visited);
            ++res;
        }
        return res;
    }
    void helper(vector<vector<int>>& M, int k, vector<bool>& visited) {
        visited[k] = true;
        for (int i = 0; i < M.size(); ++i) {
            if (!M[k][i] || visited[i]) continue;
            helper(M, i, visited);
        }
    }

684.冗餘連線

題目:

在本問題中, 樹指的是一個連通且無環的無向圖。

輸入一個圖,該圖由一個有著N個節點 (節點值不重複1, 2, ..., N) 的樹及一條附加的邊構成。附加的邊的兩個頂點包含在1到N中間,這條附加的邊不屬於樹中已存在的邊。

結果圖是一個以組成的二維陣列。每一個的元素是一對[u, v] ,滿足 u < v,表示連線頂點u 和v無向圖的邊。

返回一條可以刪去的邊,使得結果圖是一個有著N個節點的樹。如果有多個答案,則返回二維陣列中最後出現的邊。答案邊 [u, v] 應滿足相同的格式 u < v

解析:

很簡單的並查集應用。對於每條邊,若鄰接兩頂點不連通,則union,否則返回這條邊。遍歷的順序是從0開始,剛好滿足題目條件。

vector<int> findRedundantConnection(vector<vector<int>>& edges) 
{
    if(edges.size() == 0)
        return vector<int>{};
    num = edges.size();
    init(num);
    for(int i = 0; i < num; i++)
    {
        if(find(edges[i][0]) != find(edges[i][1]))
            merge(edges[i][0], edges[i][1]);
        else
            return edges[i];
    }
}

200. 島嶼的個數

題目:

給定一個由 '1'(陸地)和 '0'(水)組成的的二維網格,計算島嶼的數量。一個島被水包圍,並且它是通過水平方向或垂直方向上相鄰的陸地連線而成的。你可以假設網格的四個邊均被水包圍。

解析:

在這裡用並查集實現。先令並查集內個節點的值為0,每當遇到一塊陸地時,如果它未被放入並查集,則放入並查集;然後遍歷四周陸地,如果未放入並查集,也將其放入;然後union兩塊陸地。最後並查集中子集個數即為答案

int numIslands(vector<vector<char>>& grid) 
{
    if(grid.size() == 0)
        return 0;
    int direction[4][2] = {1,0,0,1,-1,0,0,-1};
    num = grid.size() * grid[0].size();
    int row = grid.size();
    int column = grid[0].size();
    memset(father, 0, sizeof(father));
    for(int i = 0; i < row; i++)
        for(int j = 0; j < column; j++)
            if(grid[i][j] == '1')
            {
                int x = 1 + i * column + j;
                if(father[x] == 0)
                    father[x] = x;
                for(int k = 0; k < 4; k++)
                {
                    int curx = i + direction[k][0], cury = j + direction[k][1];
                    if(curx < 0 || curx >= row || cury < 0 || cury >= column || grid[curx][cury] == '0')
                        continue;
                    else
                    {
                        int x1 = 1 + curx * column + cury;
                        if(father[x1] == 0)
                            father[x1] = x1;
                        merge(x, x1);
                    }
                }
            }
    return getSubsetNum();
}