并查集
在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。这一类问题近几年来反复出现在信息学的国际国内赛题中,其特点是看似并不复杂,但数据量极大,若用正常的数据结构来描述的话,往往在空间上过大,计算机无法承受;即使在空间上勉强通过,运行的时间复杂度也极高,根本就不可能在比赛规定的运行时间(1~3秒)内计算出试题需要的结果,只能用并查集来描述。
问题举例
例如:
给你一堆人的名字,叫:a , b, c, d, e, f, g
然后呢,告诉你们这些人是有亲戚关系的,每次只告诉你两个人之间是有亲戚关系的,然后给两个人,询问他们是不是有亲戚关系。
解答:
最简单的办法是,最开始每个人都是独立的,每个人都是一个集合,{a}, {b}, {c}, {d}, {e}
这样子,然后假如告诉你说a,b
两个人是有亲戚的关系,那么就把这两个人放入一个集合里。变成{a,b}
,这样他们在查询的时候就知道有亲戚关系了。
假如有{a,b}, {c,d}
两个集合,然后告诉你b,c
有亲戚关系,就要变成{a, b, c, d}
这样四个人就都有亲戚关系了。
那么如何表示呢? 其实假如这些人之间是有长辈关系的话,就比较容易明白了,例如:b是a的长辈,c是b的长辈,c是d的长辈:那么就记成:parent[a] = b, parent[b] = c, parent[d] = c
这里的parent
就是一个映射的数组。
搜索的时候就搜索他们的最上面的长辈就好了,因为a -> b -> c
那么a
的长辈就是c
。而d -> c
那么d
的长辈也是c,那么假如问a和d
的话,他们两个就是有亲戚了。
集合表示
这里只记录每个人的第一个长辈是谁,就是map[a] = b
这样子来记。假如是亲戚,并没有说谁是长辈,那么表示的时候,就需要注意了。例如:a b
是亲戚关系,那么记录map[a] = b
,假如a c
也是亲戚关系,再记录map[a] = c
就会出现覆盖的情况,这个时候要知道,这是一个链式结构,这个链式结构是有一个头的,这个头指向的是自己,所以每次只要用链表的头部就可以了。
代码表示
#include <iostream>
#include <unordered_map>
#include <string>
#include <cstring>
using namespace std;
unordered_map<string, string> map;
string find_parent(string name){
if(map.find(name) == map.end()){
return name;
}
if(name == map[name]){
return map[name];
}
map[name] = find_parent(map[name]); //这里进行状态压缩了
return map[name];
}
int main(){
int T;
cin>>T;
while(T--){
int s;
string name_1, name_2;
cin>>s;
cin>>name_1>>name_2;
if(s == 0){
name_1 = find_parent(name_1);
name_2 = find_parent(name_2);
if(name_1 == name_2){
continue;
}
map[name_1] = name_2;
}else{
if(find_parent(name_1) == find_parent(name_2)){
cout<<"yes"<<endl;
}else{
cout<<"no"<<endl;
}
}
}
return 0;
}
状态压缩
所谓的状态压缩就是,假如我们是a -> b -> c -> d
的话,那么我们找到a的最祖先的节点就是d,也就是需要寻找四次,如果两个都是要寻找四次,那么判断两个节点是不是在一个祖先的时候,就需要8次操作,然后我们进行状态压缩,也就是把上面的链式结构,变成多个子节点指向父节点的结构,a -> d, b -> d, c->d d->d
这样每次查找的时候就可以一次就查找到了。
假如不状态压缩的话:
string find_parent(string name){
if(map.find(name) == map.end()){
return name;
}
if(name == map[name]){
return map[name];
}
return find_parent(map[name]);
}
如果进行状态压缩的话就要把当前节点,指向自己的祖宗。
string find_parent(string name){
if(map.find(name) == map.end()){
return name;
}
if(name == map[name]){
return map[name];
}
map[name] = find_parent(map[name]); //这里状态压缩
return map[name];
}
以上。