Powered by:AB_IN 局外人
- 首先什麼是並查集?
並查集,在一些有N個元素的集合應用問題中,我們通常是在開始時讓每個元素構成一個單元素的集合,然後按一定順序將屬於同一組的元素所在的集合合併,其間要反覆查找一個元素在哪個集合中。這一類問題近幾年來反覆出現在信息學的國際國內賽題中,其特點是看似並不複雜,但數據量極大,若用正常的數據結構來描述的話,往往在空間上過大,計算機無法承受;即使在空間上勉強通過,運行的時間複雜度也極高,根本就不可能在比賽規定的運行時間(1~3秒)內計算出試題需要的結果,只能用並查集來描述。
(摘自百度)
- 是不是覺得很難很抽象?菜雞一開始也這麼覺得,
其實看看標程,自己琢磨一下就懂了。
例題來自 寧波工程學院2020新生校賽(重現賽) L 小梁的道館-
題目描述:
小梁變強之後決定建設自己的道館,她特別喜歡去其他的道館串門。但是有些道館之間沒有道路連通,於是小梁想知道自己能不能去她想去的 道館。你能幫她寫一個程序來查詢兩個道館之間是否互相存在道路聯通嗎?如果存在輸出“YES”,反之輸出“NO”。 -
輸入描述:
第一行爲三個整數N爲道館個數,M爲線路條數,T爲查詢次數。第二行至第M+1行,每行兩個整數,代表兩個道館的編號,表示這兩個道館之間有道路相連。
第M+2行至第行,每行兩個整數,代表查詢這兩個道館。
-
輸出描述:
T行,每行對應一個查詢,假如查詢的道館之間可以連接則輸出YES,否則則輸出NO。
-
- 好,我們現在來看這道題。道館可以比作一個個點,一共有N個點。然後給出M條線路,再讓你查詢T次。
- 一開始我怎麼想的呢,我想把每次輸入的數變成一個列表,排個序,存在另一個大列表裏,然後查找時,看看輸入在不在大列表裏。(python實現)
- 但其實這麼想是錯誤的。因爲如果輸入的是
那麼就不知道1和3其實也連起來了,只知道1和2連起來,2和3連起來了。
所以這該怎麼實現啊? - 爲了方便起見,我們舉個栗子:
假設N=6
然後M=5
,線路條數分別是:
3 1
2 1
4 6
5 6
1 4
ok,是這個樣子的。這個其實對應了第一步(預處理),就是將數組的每個數的值都賦上下標的值。(就是圈圈裏填上數字)
好,接下來我們執行我們的3 1
操作。
可以看到我故意 讓第一個數指向了第二個數,好,我們暫且讓成爲父節點,成爲子節點,可以理解爲
好,我們繼續
OK,可以看到 也是 的小弟(子節點)了。
接下來就是 和 認 做大哥(父節點)了。
到了比較關鍵步驟了
好了,圖到這裏畫完了 。看這張圖,我們知道 是相連的,但是,其實 、 都是相連的啊,看這張圖的箭頭我走不過去啊!!
到了關鍵步驟了!!:核心思想:找老大(父節點)
-
在外面可都是小弟啊!怎麼辦?回去找老大唄!
-
的老大是,的老大是, 說明 的老大臣服於 的老大了,所以其實就相當於
-
所以真正的圖是這樣的:
解讀一下核心思想:找老大(父節點),我們就可以做題啦!其實判斷兩個數是否相連,其實就是看他們的父節點是否一致
舉個栗子:判斷 是否連通?
先看:到父節點的路徑爲
再看:到父節點的路徑爲
不難看出,他們兩個的父節點相同,所以他倆連通。
思想我們明白了,那麼如何代碼實現呢?
- 首先我們定義個數組
fa[1001]
- 預處理(前面說過的)
for(int i = 1;i <= n; i++) fa[i] = i;
- 接下來輸入兩個數
scanf("%d %d", &a, &b);
- 接下來讓這兩個小弟找老大!
這裏就牽扯到了find
函數
int find(int x) {
if(fa[x] == x) return x;
return fa[x] = find(fa[x]);
}
找老大的過程像是遞歸,沒錯,就是遞歸思想實現!
- 如果還是自己本身的值,有兩種可能:
個人練習生(沒有老大,之前還沒被利用)。- 是最終boss(目前的父節點,如上面例子的最終圖中的)
-
不是自己本身的值:
說明已經在連線中了,用遞歸不斷調用
find
函數,來找到父節點,如果遞歸中碰到了那個 函數值和下標相等的點,那就是父節點,跳出即可。 -
可能要問了 ,怎麼遞歸啊,怎麼賦值啊?
找到兩個的父節點,讓fa[左父節點]=右父節點
即可,有點數組鏈表的意思。
u = find(a); v = find(b);
fa[u] = v;
如果暈乎的話,菜雞再舉個栗子:
比如 上面我舉的栗子中 :
- 輸入 : 用
find
一找發現 就是 的老大, 用find
一找發現 是 的老大。
所以fa[3]=1
- 輸入 :我們從前面知道
fa[2]=1
,開始找的父節點,先發現fa[2]!=2
,執行else
,開始找fa[2]
(也就是1)的父節點,發現fa[1]=1
,那麼就return 1
回到上一層,fa[2]=1
,那麼就最終return 1
。 同理。
- 最終一步,輸入T,有T次查找,前面說了,看兩個數是否連通,就是看兩個數父節點是否相同。所以:
scanf("%d %d", &a, &b);
u = find(a); v = find(b);
if(u == v) puts("YES");
else puts("NO");
好啦,介紹完成!
完整代碼如下,可以邊看代碼邊debug縷一縷思路哦!
#include <bits/stdc++.h>
using namespace std;
int n, m, t, a, b, u, v;
int fa[1005];
int find(int x) {
if(fa[x] == x) return x;
return fa[x] = find(fa[x]);
}
int main() {
scanf("%d %d %d", &n, &m, &t);
for(int i = 1;i <= n; i++) fa[i] = i;
for(int i = 1;i <= m; i++) {
scanf("%d %d", &a, &b);
u = find(a); v = find(b);
fa[u] = v;
}
while(t--) {
scanf("%d %d", &a, &b);
u = find(a); v = find(b);
if(u == v) puts("YES");
else puts("NO");
}
}
完結。
困死了~~