并查集
1.1并查集主要是处理一些不相交集合的合并和查询的问题。不相交集合,顾名思义,就是两个集合的交集为空集的一些集合
使用并查集时,首先会存在一组不相交的动态集合 S=(S1,S2.·,Sk),一般都会使用一个整数表示集合中的一个元素。
每个集合可能包含一个或多个元素,并选出集合中的某个元素作为代表。每个集合中具体包含了哪些元素是不关心的具体选择哪个元素作为代表一般也是不关心的。我们关心的是,对于给定的元素,可以很快的找到这个元素所在的集合(的代表),以及合并两个元素所在的集合,而且这些操作的时间复杂度都是常数级。
1.2基本操作
所以说对于并查集,主要就是有如下的操作:
1、建立一个新的并查集,其中包含5个单元素集合
2、合并集合,把元素x和元索y所在的集合合并,要求x和y所在的集合不相交,如果相交就不合并。
3、找到元素x所在的集合的代表,这个操作也可以用于判断两个元素是否位于同一集合,只要将他们各自的代表比较一下就可以了。
1.3树的表现形式
并查集的实现原理也比较简单,就是用树来表示一个集合,树的每个结点都是集合的一个元素,树根对应的那个结点就是该集合的代表。
1.4查找
查找操作就是找到的祖先直接返回.
1.5合并
比如我们合并 (4,3), (3,2),(2,1)。如果此时我们合并了一万个结点,这个时候,其实从时间复杂度来说,查找的次数就非常非常多了。
这个时候,我们就想出来了一个策略,路径压缩。
1.6路径压缩
就是在每次查找时,令查找路径上的每个节点都直接指向根节点。
Hash表
使用链表(或std::vector等结构)将Hash冲突的元素保存起来。这样,查找一个元素时只需要与Hash冲突的较少元素进行比较。
用这种方式可以完全解决Hash冲突问题。
但是查找元素的复杂度会有所上升(取决于Hash冲突的次数)。
vector<longlong>hash[maxh];
//以下是插入集合的方式,查找也是类似
voidintinsert(x){
inth=f(x);
//计算哈希值
for(inti==0,sz=hash[h].size();i<sz;i++)
if(x==hash[h][i])//从数组中找到了这一项
return;//什么都不做退出
hash[h].push_back(x);//插入这个元素
}
另一种解决方式是将映射푓调整为高维,例如同时使用两个模数:此时,只有当多个Hash函数值同时相等才会导致Hash冲突。冲突概率大幅降低。注意MultiHash对空间的开销较大,因为需要使用二维数组。
STL中的集合
STL中也有集合的实现,分为无序集和偏序集。其中分为集合(set)和映射(map)。
•
无序集在STL中是unordered_set和unordered_map。
其本质为Hash表,因此增删改查均为O(1)。
对于复杂数据类型,需要手动实现Hash函数。
•
偏序集在STL中是set和map。
本质为排序树(将来介绍),增删改查均为O(logn)。
对于复杂数据类型,需要手动实现偏序关系,即<运算符。
集合在STL中有两种,分别是有序集合和无序集合
需头文件<unordered_set>
需头文件<set>
unordered_set的行为(无序):
set的行为(有序):
功能上类似,但有序集可找前驱后继
unordered_set<Type>s;//创建Type类型的集合
s.insert(x);
//插入元素x
s.erase(x);
//删除值为x的元素
s.erase(it);
//删除it所指的元素
s.end();
//返回末位哨兵的迭代器
s.find(x);
//查询x;不存在则返回s.end()
s.empty();
//判断是否为空
s.size();
//返回集合的大小
set<Type>s;
//创建一个Type类型的集合
s.insert(x);
//插入元素x
s.erase(x);
//删除值为x的元素
s.erase(it);
//删除it所指的元素
s.end();
//返回末位哨兵的迭代器
s.find(x);
//查询x;不存在则返回s.end()
s.empty();
//判断是否为空
s.size();
//返回集合的大小
s.upper_bound(x);
//查询大于x的最小元素
s.lower_bound(y);
//查询不小于x的最小元素
//使用方法与二分查找一