算法基础 - 并查集

来源:互联网 发布:孕妇不能吃薄荷糖 知乎 编辑:程序博客网 时间:2024/05/16 07:24

并查集

在一些有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];}

以上。

0 0
原创粉丝点击