数据结构 并查集
并查集是一种数据结构,字面意思上来说,就是一个支持合并和查询的集合。
并查集
并查集的建立
1 void init() 2 { 3 for (int i = 1; i <= n; i++) 4 { 5 father[i] = i; 6 } 7 }
建立一个并查集很简单,只要开一个数组。这个数组储存某个节点对应的父节点编号。初始化时,将所有节点的父节点设为自己。
并查集的查找
1 int get(int x) 2 { 3 // 如果当前节点的父节点就是自己,那么说明这个节点是其所在集合的根节点。 4 if (father[x] == x) 5 { 6 return x; 7 // 返回当前节点 8 } 9 // 否则递归查找该节点的父节点 10 return get(father[x]); 11 }
并查集的合并
1 void merge(int x, int y) 2 { 3 // 分别找到连个节点的根节点 4 x = get(x); 5 y = get(y); 6 if (x != y) 7 // 如果两个节点的根节点不是一个节点,那么意味这它们不在一个集合当中 8 // 此时就可以进行合并操作 9 // 合并操作就是将其中一个节点所在集合的根节点的父亲设为另外一个节点的根节点 10 { 11 father[y] = x; 12 } 13 }
路径压缩
在最坏情况下,树是一条链,那么它的查找时间复杂度会达到 O(n),这时我们所不希望看到的。
解决方法是:因为我们不关心这个树的结构而是关心它的节点所在集合的关系,我们可以把一个集合里的所有非根节点的父亲节点都设为集合的根节点。
要做的只是在查找的代码中稍微改动一下:
1 int get(int x) 2 { 3 if (father[x] == x) 4 { 5 return x; 6 } 7 return father[x] = get(father[x]); 8 // 返回时将当前节点的父节点设为根节点 9 }
简单例题
在社交的过程中,通过朋友,也能认识新的朋友。在某个朋友关系图中,假定 A 和 B 是朋友,B 和 C 是朋友,那么 A 和 C 也会成为朋友。即,我们规定朋友的朋友也是朋友。
现在,已知若干对朋友关系,询问某两个人是不是朋友。
请编写一个程序来解决这个问题吧。
输入格式
第一行:三个整数 n,m,p(n≤5000,m≤5000,p≤5000),分别表示有 n 个人,m 个朋友关系,询问 p 对朋友关系。
接下来 m 行:每行两个数 Ai,Bii,Bi,1≤Ai,Bi≤N1\leq A_i,B_i\leq N1≤Ai,Bi≤N,表示 AiA_iAi 和 BiB_iBi 具有朋友关系。
接下来 p 行:每行两个数,询问两人是否为朋友。
输出格式
输出共 p 行,每行一个Yes
或No
。表示第 i 个询问的答案为是否朋友。
样例输入
6 5 3 1 2 1 5 3 4 5 2 1 3 1 4 2 3 5 6
样例输出
Yes Yes No
1 #include <iostream> 2 using namespace std; 3 int father[5000+1],n,m,p; 4 void init() 5 { 6 for (int i = 1; i <= n; i++) 7 { 8 father[i] = i; 9 } 10 } 11 int get(int x) 12 { 13 if (father[x] == x) 14 { 15 return x; 16 } 17 return father[x] = get(father[x]); 18 } 19 void merge(int x, int y) 20 { 21 x = get(x); 22 y = get(y); 23 if (x != y) 24 { 25 father[y] = x; 26 } 27 } 28 int main() 29 { 30 memset(father,0,sizeof(father)); 31 cin >> n >> m >> p; 32 init(); 33 for (int i = 1; i <= m; i++) 34 { 35 int a,b; 36 cin >> a >> b; 37 merge(a,b); 38 } 39 for (int i = 1; i <= p; i++) 40 { 41 int a,b; 42 if (get(a) == get(b)) 43 cout << "Yes" << endl; 44 else 45 cout << "No" << endl; 46 } 47 return 0; 48 }
非常简单,只要把之前的几个函数塞进去,一边输入一边合并,最后查找就可以了。