C++并查集的原理與使用方法
一、并查集的概念
在一些場(chǎng)景中,需要將n個(gè)不同元素劃分為一些不相交的集合。開始時(shí),每個(gè)元素各成一個(gè)元素,然后按一定的規(guī)律將屬于同一組的元素合并。這個(gè)過程中需要反復(fù)用到查詢一個(gè)元素是否屬于某個(gè)集合的算法。適合用于這種場(chǎng)景的數(shù)據(jù)結(jié)構(gòu)是并查集(Union-Find Set)!
并查集的底層結(jié)構(gòu)本質(zhì)上是一片森林(多棵樹的集合)
比如,我現(xiàn)在有九個(gè)數(shù)據(jù)元素,給他們編號(hào)0~8:

按照某種需求,這些數(shù)據(jù)被分組合并為:

按照其他需求,這些樹可以繼續(xù)合并下去…
而這個(gè)森林,可以用一個(gè)數(shù)組記錄下來元素的關(guān)系!
我們可以規(guī)定并查集用數(shù)組下標(biāo)代表每個(gè)元素,數(shù)組內(nèi)容代表元素之間的關(guān)系:
- 數(shù)組下標(biāo)代表元素編號(hào)
- 如果數(shù)組內(nèi)容為負(fù)整數(shù),代表這個(gè)下標(biāo)是根,絕對(duì)值表示它這棵樹的元素個(gè)數(shù)
- 如果數(shù)組內(nèi)容為非負(fù)整數(shù),代表這個(gè)下標(biāo)不是根,數(shù)組內(nèi)容是它的父親在數(shù)組中的下標(biāo)
比如,上面的森林例子,用并查集數(shù)組表示,就是:

如果元素的數(shù)據(jù)類型不能直接作為數(shù)組下標(biāo),只要在實(shí)現(xiàn)中用std::map之類的結(jié)構(gòu),建立元素到下標(biāo)的映射關(guān)系,就能解決了!
通過并查集的特點(diǎn),可以看出并查集一般能解決:
- 查找元素屬于哪個(gè)集合:沿著數(shù)組一直找到元素為負(fù)數(shù),就是根
- 查看兩個(gè)元素是否屬于一個(gè)集合:看看這兩個(gè)元素的根是否相同
- 將兩個(gè)集合歸并為一個(gè)集合:假如要將下標(biāo)a的樹合并到下標(biāo)b的樹中,arr[b] += arr[a],arr[a] = b即可,即令1成為0的一個(gè)孩子
- 統(tǒng)計(jì)集合的個(gè)數(shù):統(tǒng)計(jì)數(shù)組中元素為負(fù)數(shù)的個(gè)數(shù)
二、并查集的實(shí)現(xiàn)
#pragma once
#include<vector>
#include<iostream>
using namespace std;
class UnionFindSet
{
public:
UnionFindSet(int size)
:_set(size, -1) // 初始時(shí)每個(gè)數(shù)據(jù)各是一棵樹,元素均為-1
{ }
// 查找一個(gè)數(shù)據(jù)屬于哪個(gè)集合,找根元素的下標(biāo)
int FindRoot(int i)
{
while (_set[i] >= 0)
{
i = _set[i];
}
return i;
}
// 合并兩個(gè)數(shù)據(jù)所在的集合
void Union(int i1, int i2)
{
// 找這兩個(gè)數(shù)據(jù)的根下標(biāo)
int root1 = FindRoot(i1);
int root2 = FindRoot(i2);
if (root1 != root2)
{
_set[root1] += _set[root2];
_set[root2] = root1;
}
// 如果root1 == root2,說明這兩個(gè)數(shù)據(jù)本就在一個(gè)集合,不用合并
}
// 判斷兩個(gè)數(shù)據(jù)是否在同一個(gè)集合
bool IsSameSet(int i1, int i2)
{
return FindRoot(i1) == FindRoot(i2);
}
// 統(tǒng)計(jì)集合個(gè)數(shù)
int SetCount()
{
int ret = 0;
for (int n : _set)
{
if (n < 0)
ret++;
}
return ret;
}
private:
vector<int> _set;
};
測(cè)試:

三、算法題中的應(yīng)用
并查集的特點(diǎn)在某些算法題中很有用:
class Solution {
public:
// 并查集, 統(tǒng)計(jì)集合數(shù)量
int findCircleNum(vector<vector<int>>& isConnected) {
vector<int> ufs(isConnected.size(), -1);
auto findRoot = [&ufs](int i)
{
while(ufs[i] >= 0)
{
i = ufs[i];
}
return i;
};
auto Union = [&ufs, &findRoot](int i1, int i2)
{
int root1 = findRoot(i1);
int root2 = findRoot(i2);
if(root1 != root2)
{
ufs[root1] += ufs[root2];
ufs[root2] = root1;
}
};
auto SetCount = [&ufs]()
{
int ret = 0;
for(int n : ufs)
{
if(n < 0)
ret++;
}
return ret;
};
for(int i = 0; i < isConnected.size(); i++)
{
for(int j = 0; j < isConnected[i].size(); j++)
{
if(isConnected[i][j] == 1)
{
Union(i, j);
}
}
}
return SetCount();
}
};
class Solution {
public:
// 并查集,數(shù)組大小26
// 遍歷一次把所有==的兩個(gè)字母放到一個(gè)集合,再遍歷一次看!=的兩個(gè)字符是否都在集合中出現(xiàn)過,出現(xiàn)過則false
bool equationsPossible(vector<string>& equations) {
vector<int> ufs(26, -1);
auto findRoot = [&ufs](int i)
{
while(ufs[i] >= 0)
{
i = ufs[i];
}
return i;
};
auto Union = [&ufs, &findRoot](int i1, int i2)
{
int root1 = findRoot(i1);
int root2 = findRoot(i2);
if(root1 != root2)
{
ufs[root1] += ufs[root2];
ufs[root2] = root1;
}
};
for(string& s : equations)
{
if(s[1] == '=')
{
Union(s[0]-'a', s[3]-'a');
}
}
for(string& s : equations)
{
if(s[1] == '!')
{
int root1 = findRoot(s[0]-'a');
int root2 = findRoot(s[3]-'a');
if(root1 == root2)
{
return false;
}
}
}
return true;
}
};
總結(jié)
到此這篇關(guān)于C++并查集的原理與使用方法的文章就介紹到這了,更多相關(guān)C++并查集原理與使用內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++中for循環(huán)與while循環(huán)的區(qū)別總結(jié)
這篇文章主要給大家介紹了關(guān)于C++中for循環(huán)與while循環(huán)的區(qū)別的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-10-10
c++臨時(shí)對(duì)象導(dǎo)致的生命周期問題
對(duì)象的生命周期是c++中非常重要的概念,它直接決定了你的程序是否正確以及是否存在安全問題,這篇文章主要介紹了c++臨時(shí)對(duì)象導(dǎo)致的生命周期問題 ,需要的朋友可以參考下2024-07-07
opencv+arduino實(shí)現(xiàn)物體點(diǎn)追蹤效果
這篇文章主要為大家詳細(xì)介紹了opencv+arduino實(shí)現(xiàn)物體點(diǎn)追蹤效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-01-01
C++如何計(jì)算結(jié)構(gòu)體與對(duì)象的大小
這篇文章主要給大家介紹了關(guān)于C++如何計(jì)算結(jié)構(gòu)體與對(duì)象大小的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-05-05
Qt解析XML的三種常見方法實(shí)現(xiàn)與比較
XML是一種可擴(kuò)展的標(biāo)記語言,用于存儲(chǔ)和傳輸結(jié)構(gòu)化數(shù)據(jù),廣泛應(yīng)用于配置、數(shù)據(jù)交換和Web服務(wù)等領(lǐng)域,本文主要介紹了Qt解析XML的三種常見方法實(shí)現(xiàn)和性能對(duì)比,需要的可以了解下2025-06-06
C語言中strcpy和strcat的使用和模擬實(shí)現(xiàn)
strcpy()?函數(shù)是?C語言中一個(gè)非常重要的字符串處理函數(shù),其功能是將一個(gè)字符串復(fù)制到另一個(gè)字符串中,strcat函數(shù)可以將一個(gè)字符串拼接到另一個(gè)字符串的末尾,本文給大家介紹了C語言中strcpy和strcat的使用和模擬實(shí)現(xiàn),需要的朋友可以參考下2024-03-03



