並查集實現及其應用


並查集

1. 並查集是什么

並查集是一種用來管理元素分組情況的數據結構。並查集可以高效地進行如下操作。不過需要注意並查集雖然可以進行合並操作,但是無法進行分割操作。

  • 查詢元素a和元素b是否屬於同一組。

  • 合並元素a和元素b所在的組。

2. 並查集的結構

並查集也是使用樹形結構實現。不過,不是二叉樹。

每個元素對應一個節點,每個組對應一棵樹。在並查集中,哪個節點是哪個節點的父親以及樹的形狀等信息無需多加關注,整體組成一個樹形結構才是重要的。

3. 代碼

 1 /*
 2 6
 3 1 1
 4 2 1
 5 5 1
 6 6 6 
 7 4 6
 8 7 4
 9 */
10 #define _CRT_SECURE_NO_WARNINGS
11 #include <iostream>
12 #include <cstring>
13 #include <cstdio>
14 #include <cstdlib>
15 using namespace std;
16 
17 const int maxn = 1000 + 100;
18 int par[maxn];     //父親,  當par[x] = x時,x是所在的樹的根
19 int Rank[maxn];    //樹的高度
20 
21 //初始化n個元素
22 void init(int n)
23 {
24     for (int i = 0; i < n; i++) {
25         par[i] = i;
26         Rank[i] = 0;
27     }
28 }
29 
30 //查詢樹的根
31 int find(int x) {
32     if (par[x] == x) {
33         return x;
34     }
35     else {
36         return par[x] = find(par[x]);
37     }
38 }
39 
40 //合並x和y所屬集合
41 void unite(int x, int y) {
42     x = find(x);
43     y = find(y);
44     if (x == y) return;
45     
46     if (Rank[x] < Rank[y]) {
47         par[x] = y;
48     } else {
49         par[y] = x;
50         if (Rank[x] == Rank[y]) Rank[x]++;    //如果x,y的樹高相同,就讓x的樹高+1
51     }
52 }
53 
54 //判斷x和y是否屬於同一個集合
55 bool same(int x, int y) {
56     return find(x) == find(y);
57 }
58 
59 int main()
60 {
61     int n;
62     scanf("%d", &n);
63     init(n);
64 
65     int data, p;
66     cout << "輸入數據: \n";
67     for (int i = 0; i < n; i++) {
68         scanf("%d%d", &data, &p);
69         par[data] = p;
70         Rank[p]++;
71     }
72 
73     cout << "輸入合並集合: \n";
74     int p1, p2;
75     cin >> p1 >> p2;
76     unite(p1, p2);
77     cout << "查詢是否屬於一個集合: \n";
78     cin >> p1 >> p2;
79 
80     if (same(p1, p2)) {
81         puts("same");
82     }
83     else {
84         puts("diff");
85     }
86 
87     return 0;
88 }

4. 實例

食物鏈(POJ 1182)

Time Limit: 1000MS   Memory Limit: 10000K
Total Submissions: 67690   Accepted: 20010
Description

動物王國中有三類動物A,B,C,這三類動物的食物鏈構成了有趣的環形。A吃B, B吃C,C吃A。 現有N個動物,以1-N編號。每個動物都是A,B,C中的一種,但是我們並不知道它到底是哪一種。 有人用兩種說法對這N個動物所構成的食物鏈關系進行描述: 

  • 第一種說法是"1 X Y",表示X和Y是同類。 

  • 第二種說法是"2 X Y",表示X吃Y。 

此人對N個動物,用上述兩種說法,一句接一句地說出K句話,這K句話有的是真的,有的是假的。當一句話滿足下列三條之一時,這句話就是假話,否則就是真話。 1) 當前的話與前面的某些真的話沖突,就是假話; 2) 當前的話中X或Y比N大,就是假話; 3) 當前的話表示X吃X,就是假話。 你的任務是根據給定的N(1 <= N <= 50,000)和K句話(0 <= K <= 100,000),輸出假話的總數。 

Input

第一行是兩個整數N和K,以一個空格分隔。 以下K行每行是三個正整數 D,X,Y,兩數之間用一個空格隔開,其中D表示說法的種類。 若D=1,則表示X和Y是同類。 若D=2,則表示X吃Y。

Output

只有一個整數,表示假話的數目。

Sample Input
100 7
1 101 1 
2 1 2
2 2 3 
2 3 3 
1 1 3 
2 3 1 
1 5 5 
Sample Output
 3
Source

Noi 01

解析

由於N和K 很大,所以必須高效地維護動物之間關系,並快速判斷是否產生了矛盾。並查集是維護“屬於同一組”的數據結構,但是本題中,並不是只有屬於同一類的信息,還有捕食關系的存在。因此需要思考如何維護這些關系。

對於每只動物i創建3個元素 i - A, i - B, i - C, 並用這3 x N個元素建立並查集。這個並查集維護如下信息:

  • i-x表示 “i屬於種類x"

  • 並查集里每個組表示組內所有元素代表的情況都同時發生或不發生。

例如:如果i-A和j-B在同一個組里,就表示如果i屬於種類A那么j一定屬於種類B,如果i屬於種類B那么j一定屬於種類A。因此,得出下面的操作。

  • 第一種,x和y屬於同一種類·······合並x-A和y-A、x-B和y-B、x-C和y-C。

  • 第二種,x吃y······························合並x-A和y-B、x-B和y-C、x-C和y-A。

不過在合並前,需要先判斷合並是否會產生矛盾。例如在第一種信息的情況下,需要檢查(x-A, y-B) ||(x-A, y-C)是否在同一組等。

代碼:
#include <iostream> #include <cstring> #include <cstdio> using namespace std; const int maxn = 100000 * 3 + 500; //輸入(T是信息的類型) int N, K; //N-N種類, K-K條信息 int T[maxn], X[maxn], Y[maxn]; //在這里省略了並查集部分代碼 int par[maxn]; int Rank[maxn]; void solve(); void init(int n); int find(int x); void unite(int x, int y); bool same(int x, int y); void init(int n) { for (int i = 0; i < n; i++) { par[i] = i; Rank[i] = 0; } } //查詢樹根 int find(int x) { if (par[x] == x) { return x; } else { return par[x] = find(par[x]); } } //合並x和y所屬的集合 void unite(int x, int y) { x = find(x); y = find(y); if (x == y) return; if (Rank[x] < Rank[y]) { par[x] = y; } else { par[y] = x; if (Rank[x] == Rank[y]) Rank[x]++; } } bool same(int x, int y) { return find(x) == find(y); } void solve() { //初始化並查集 //元素x, x + N, x + 2 * N 分別代碼 x-A, x-B, x-C init(N * 3); int ans = 0; for (int i = 0; i < K; i++) { int t = T[i]; int x = X[i] - 1, y = Y[i] - 1; //把輸入變成0,...N-1的范圍 //不正確的編號 if (x < 0 || N <= x || y < 0 || N <= y) { ans++; continue; } if (t == 1) { // "x和y屬於同一類"的信息 if (same(x, y + N) || same(x, y + 2 * N)) { ans++; } else { unite(x, y); unite(x + N, y + N); unite(x + N * 2, y + N * 2); } } else { //"x吃y"的信息 if (same(x, y) || same(x, y + 2 * N)) { //A和A,A和C不能相等 ans++; } else { unite(x, y + N); unite(x + N, y + 2 * N); unite(x + 2 * N, y); } } } printf("%d\n", ans); } int main() { cin >> N >> K; for (int i = 0; i < K; i++) { scanf("%d%d%d", &T[i], &X[i], &Y[i]); } solve(); return 0; }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM