日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

C/C++并查集的查詢與合并實現原理_C 語言

作者:Ggggggtm ? 更新時間: 2023-04-29 編程語言

標題:并查集的查詢與合并詳解 作者:@Ggggggtm 寄語:與其忙著訴苦,不如低頭趕路,奮路前行,終將遇到一番好風景

一、并查集的概念

并查集是一種樹形的數據結構。使用樹型結構來存儲數據。樹根的編號即為整個樹的標號,且每個節點存儲的數據是他的父節點下標。

并查集被很多OIer認為是最簡潔而優雅的數據結構之一,主要用于解決一些元素分組的問題。它管理一系列不相交的集合,并支持兩種操作:

  • 合并(Union):把兩個不相交的集合合并為一個集合。
  • 查詢(Find):查詢兩個元素是否在同一個集合中。

二、并查集的實現

1.并查集不同集合(樹)的形成

我們把并查集不同集合(樹)的實現主要分為以下幾個點:

  • 我們先給出n個數據,把n個數據存儲到不同的集合當中(p[ i ] = i),在這里我們把每個p[ i ]分別看成一個不同集合(也就是一棵樹)。
  • p[ i ] = i,i即為這棵樹的編號,這顆樹下面的孩子節點存儲的數據是父節點的下標。
  • 當p[ i]=i 時,就相當于找到了根節點。
  • 我們剛開始每個集合中的元素只有一個。后續合并后,集合元素個數不斷增加。

2.find()函數找一個元素集合的編號

(元素所屬于樹的祖宗)

我們查找一個元素的集合,把元素的當作下標傳給find()函數,代碼如下:

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

我們p[x]中存儲的正是他的父節點,從而就可以一直往上查找,直到p[ x ]=x時結束。當p[ x ]=x時,就相當于找到了根節點。此時的p[ x ]存儲的是這棵樹的編號。我這發現,剛開始每個集合當中都只有一個元素,也就是p[ x ],后面我們會對不同的集合進行合并,使得一個集合有多個元素。

我們再找祖宗節點時進行了路徑壓縮。什么是路徑壓縮呢?路徑壓縮就是我們在查找某個元素的祖宗時,在找父節點的這條路經上的元素都指向祖宗節點,以便于我們后面的查找的時間復雜度近乎O(1)。

3.合并兩個不同集合(合并兩棵不同的樹)

我們直到了每棵樹的根節點存儲的是這個樹的編號,而不是父節點。當我們要合并兩顆樹時,我們只需要把一棵樹的根節點存儲的編號改為另一棵樹的根節點編號。簡單的理解就是一個樹的根節不再是根節點,而是一個子節點,該樹的根節點存儲的也不再是編號,而是存儲的父節點,該父節點就是另一棵樹的根節點。我們看代碼:

//合并  把a的祖宗節點的父節點當作b的祖宗結點
p[find(a)]=find(b);

4.查詢兩個元素是否在一個集合

我們有了find()函數,就可以很簡單的判斷出兩個元素的是否在同一個集合當中(兩個元素是否在同一個樹中)。我們只需要判斷兩個元素集合的編號是否相同(兩個元素的祖宗節點是否相同)即可。我們看代碼:

//看a、b兩個元素是否在同一個集合當中
if(find(a)==find(b))
    cout<<"Yes"<<endl;
else
    cout<<"No"<<endl;

5.并查集例題訓練1

一共有 n 個數,編號是1~n,最開始每個數各自在一個集合中。

現在要進行m個操作,操作共有兩種:

  • M a b,將編號為a和b的兩個數所在的集合合并,如果兩個數已經在同一個集合中,則忽略這個操作;
  • Q a b,詢問編號為a和b的兩個數是否在同一個集合中;

輸入格式:

第一行輸入整數n和m。

接下來m行,每行包含一個操作指令,指令為M a bQ a b中的一種。

輸出格式:

對于每個詢問指令Q a b,都要輸出一個結果,如果a和b在同一集合內,則輸出Yes,否則輸出No

每個結果占一行。

數據范圍:

1≤n,m≤10e5 1≤n,m≤10e5

輸入樣例:

4 5
M 1 2
M 3 4
Q 1 2
Q 1 3
Q 3 4

輸出樣例:

Yes
No
Yes

答案如下:

#include<iostream>
using namespace std;
const int N=100010;
int p[N];
//找祖宗+路徑壓縮
int find(int x)
{
    if(p[x]!=x)
    {
        p[x]=find(p[x]);
    }
    return p[x];
}
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        p[i]=i;
    while(m--)
    {
        char op[2];
        int a,b;
        cin>>op>>a>>b;
        if(op[0]=='M')
        {
            //合并  把a的祖宗節點的父節點當作b的祖宗結點
            p[find(a)]=find(b);
        }
        else
        {
            if(find(a)==find(b))
                cout<<"Yes"<<endl;
            else
                cout<<"No"<<endl;
        }
    }
    return 0;
}

6.并查集例題訓練2

給定一個包含nn個點(編號為1~n1~n)的無向圖,初始時圖中沒有邊。

現在要進行mm個操作,操作共有三種:

  • C a b,在點aa和點bb之間連一條邊,aa和bb可能相等;
  • Q1 a b,詢問點aa和點bb是否在同一個連通塊中,aa和bb可能相等;
  • Q2 a,詢問點aa所在連通塊中點的數量;

輸入格式

第一行輸入整數nn和mm。

接下來mm行,每行包含一個操作指令,指令為C a bQ1 a bQ2 a中的一種。

輸出格式

對于每個詢問指令Q1 a b,如果aa和bb在同一個連通塊中,則輸出Yes,否則輸出No

對于每個詢問指令Q2 a,輸出一個整數表示點aa所在連通塊中點的數量

每個結果占一行。

數據范圍

1≤n,m≤1051≤n,m≤105

輸入樣例:

5 5
C 1 2
Q1 1 2
Q2 1
C 2 5
Q2 5

輸出樣例:

Yes
2
3

我們這個題相對于上個題就是對出了一個統計一個集合元素的個數。整體思路大同小異,我們直接看代碼解析:

#include<iostream>
using namespace std;
const int N=100010;
int p[N],cnt[N];
int find(int x)
{
    if(p[x]!=x)
    {
        p[x]=find(p[x]);
    }
    return p[x];
}
int main()
{
    int n,m;
    cin>>n>>m;
    for(int i=0;i<n;i++)
    {
        p[i]=i;
        cnt[i]=1;
    }
    while(m--)
    {
        char op[5];
        int a,b;
        scanf("%s",op);
        if(op[0]=='C')
        {
            scanf("%d%d",&a,&b);
            if(find(a)==find(b))
                continue;
            cnt[find(b)]+=cnt[find(a)];
            p[find(a)]=find(b);
        }
        else if(op[1]=='1')
        {
            scanf("%d%d",&a,&b);
            if(find(a)==find(b))
                printf("Yes\n");
            else
                printf("No\n");
        }
        else
        {
            scanf("%d",&a);
            printf("%d\n",cnt[find(a)]);
        }
    }
    return 0;
}

注意,我們剛開始每個集合中的元素只有一個。后續合并后,集合元素個數不斷增加。

三、總結

我們主要掌握find()函數,并查集算法中,最為核心的就是find()函數。在這個算法中,路徑壓縮給我們的算法效率提高了很多,這個也是需要理解的。合并、查詢是并查集的兩個主要操作,我們也應該熟悉理解。

原文鏈接:https://blog.csdn.net/weixin_67596609/article/details/128622883

欄目分類
最近更新