新學一招 excel 的substring
並查集
基本介紹
資料結構一共分為四大類:集合、線性表、樹、圖,本文的資料結構就是第一種結構集合。
假設,一個集合即是一個小團伙,每個小團伙都有一個老大,每個團伙成員認識另一個成員,而團隊比較大不可能每個成員都互相認識吧,但是他們認識的人去找別的認識的人中最終都可以找到老大,這些人就構成了一個集合。那麼這個老大我們稱之為”祖宗”,每個人認識的那個人就是它的“父親“。
有點類似於樹的結構,這種尋找一般都是單向的,但是也可以增加資料域儲存”孩子“的資訊(一般不用)。類似於樹,我們就可以像操作樹那樣,用遞迴的方式實現尋找(當然也可以非遞迴)。
核心思想
一個節點一般包含兩個資訊域,一是自身的資訊,二是父親的資訊。
常見用法:定義一個數組,令陣列角標為資料的地址,單個數組元素的一個數據域就可以用來儲存“父親“的角標,以實現元素之間的連通。
判斷兩個元素是否在同一個集合,只需要不斷找“父親”指導找到“祖宗“,判斷“祖宗“是否相同即可。
圖 方框內是陣列角標,圓內是單個數組元素的資料域。圖中即為兩個集合。
程式碼實現
初始化
並查集在使用前一定要初始化,讓每個節點自己做自己的”父親”,因為最開始每個元素獨立自己是一個集合。
void init(int len) { for (int i = 0; i < len; i++) family[i].parent = i;return; }
找“祖宗“
單純找
核心原理就是不斷的通過資料域的資訊去找“父親”,直到找到一個自己是自己“父親”的節點即“祖宗”。這個過程可以用遞迴也可以非遞迴實現。
1. 遞迴
int find(int aim) { if (family[aim].parent != aim) aim = find(family[aim].parent); return aim; }
2. 非遞迴
int find(int x) { while (x != fa[x]) x = fa[x]; return x; }
路徑壓縮
對於有些題目,實際路徑不會對解題有影響,就可以採用路徑壓縮,其目的就是減少find使用的時間。如上例子,“6”號位找祖宗要找三次,採用路徑壓縮只需要找一次即可。
int find(int x, int *fa){ if(fa[x] != x) fa[x] = find(fa[x], fa);//查詢+路徑壓縮,如果沒有祖先就回溯 return fa[x]; }
合併
合併操作就是檢查兩個元素是否是同一個集合,如果不是就需要合併在同一個集合。對於誰做誰的父節點一般情況下沒有任何關係,因為並查集只是表述集合關係。但是在某些對路徑有要求的題目下,就要考慮誰做誰的父節點。
void merge(int a, int b, int *fa) { a = find(a, fa); b = find(b, fa); if (a != b) fa[a] = b; // 如果兩個元素不相等就讓一個元素成為另一個元素的父節點 return; }
練習題
https://www.luogu.com.cn/problem/P1551 P1551 親戚
https://www.luogu.com.cn/problem/P1536 P1536 村村通
https://www.luogu.com.cn/problem/P1525 P1525 [NOIP2010 提高組]關押罪(種類並查集)
https://www.luogu.com.cn/problem/P1621 P1621 集合
https://www.luogu.com.cn/problem/P1892 P1892 [BOI2003]團伙
https://www.luogu.com.cn/problem/P1955 P1955 [NOI2015] 程式自動分析(這個有趣)
https://www.luogu.com.cn/problem/P2814 P2814 家譜(這個簡單,不過字串處理一下)
https://vjudge.net/contest/445444 這個並查集題單還是挺有趣的
http://acm.hdu.edu.cn/showproblem.php?pid=3038 How Many Answers Are Wrong(這是一個帶權並查集,感興趣的可以試試)
我的題解
P1551 親戚 - Kirk~~ - 部落格園 (cnblogs.com)
P1536 村村通 - Kirk~~ - 部落格園 (cnblogs.com)