并查集quick union实现
- 将每个元素看成一个节点,每个节点都指向一个节点,当一个节点为根节点时其指向的节点为自身.
- 连接节点7和3,即找到7的根节点5和3的根节点2,将根节点5指向2.即将节点7和节点2连接在了一起.
- 在这种实现下,判断两个节点是否连接,只需要找到两个节点的根节点是否相同即可
- 由于每个节点只会指向一个节点,根节点指向自身,所以可以用数组即可实现,数组记录每个节点指向的节点是什么.`
class uf2 :uf
{
public:
uf2(int n)
{
sz = n;
parent = new int[n];
for (int i = 0;i < sz;i++)
{
parent[i] = i;
}//初始时,每个元素都不连接,每个元素的父节点都是它本身
}
int getsize()//并查集考虑的元素的个数
{
return sz;
}
void unionelement(int p, int q)
{
assert(p >= 0 && p < sz);
int proot = findroot(p);
int qroot = findroot(q);
if (proot == qroot)
return;
parent[proot] = qroot;
}
//查看元素p和元素q是否属于同一个集合
bool isconnented(int p, int q)
{
assert(p >= 0 && p < sz);
return findroot(p) == findroot(q);
}
private:
int sz;
int* parent;
//查找元素的根节点
//需要一直往上找时间复杂度为O(H)h为树的高度
int findroot(int p)
{
//assert(p >= 0 && q < sz);
//只要不是根节点一直向上找
while (parent[p] != p)
{
p = parent[p];
}
return parent[p];
}
};
基于size的优化
- 当找到p和q的根节点时,合并时直接将p节点的根节点指向q的根节点,没有考虑p节点所在的树和q节点所在树的性质.这样新和成的树的高度可能不是最优,高度越高,下次查找的时候时间复杂度越高,效率越差.
- - 假设合并 0,1 1,2 2,3最后树的退化成一条链表,查询时间复杂度高
- 基于size的优化,每次让proot和qrootsize小的那个指向大的,这样有很大概率降低树的层数
- 如上图,让3指向4,和让4指向3,让4指向3的树的高度低.
void unionelement(int p, int q)
{
assert(p >= 0 && p < sz);
int proot = findroot(p);
int qroot = findroot(q);
if (proot == qroot)
return;
if (szz[proot] < szz[qroot])
parent[proot] = qroot;
else //包含了proot的size数量等于qroot的size数量的情况
parent[qroot] = proot;
parent[proot] = qroot;
}
szz每个节点初始化为1;
- 基于size的优化其实本质是想优化树的高度,使得树的感度尽可能的小,这样查找根节点的时间复杂度O(h)也就小
但是在某些情况下,size的大小可能和树的高度没有本质的联系,所以使用树的高度rank优化更好
基于size的优化,会把8指向7,新生成的树的高度为4,但是此时吧7指向8,明显生成的树高度更小
基于rank的优化
合并的时候将rank小的根节点指向rank大根节点,那么rank大的节点的rank值还是不变.
只有当两个节点的rank值相等的时候,合并才需要更新rank值
void unionelement(int p, int q)
{
assert(p >= 0 && p < sz);
int proot = findroot(p);
int qroot = findroot(q);
if (proot == qroot)
return;
if (rank[proot] < rank[qroot])
{
parent[proot] = qroot;
//proot指向了qroot,qroot的rank比proot大,proot指向后,qootrank值不变
}
else if(rank[proot] > rank[qroot])
{ //同上
parent[qroot] = proot;
}
else
{
parent[proot] = qroot;
rank[qroot] += 1;
}
}
路径压缩
如上图所示,同样对4执行找根操作,由于树的高度不太,效率也不同
在向上找根节点的时候,当还未找到根节点的时候,不断执行parent[p]=parent[parent[p]];
例如上图找4,可以通过下面的操作将树变成右一的样子,树的高度降低
int findroot(int p)
{
//assert(p >= 0 && q < sz);
//只要不是根节点一直向上找
while (parent[p] != p)
{
p = parent[p];
parent[p] = parent[parent[p]];
}
return parent[p];
}
在路径压缩的过程中,树的高度一直在改变,但是没有更新rank的值.但是再合并的时候,仍可以通过rank的值来更新,只是rank数组此时不是记录的当前根所在树的高度,或者说是层数,这也是为什么拿rank命名而不拿height命名的原因
递归路径优化
利用递归进行路径压缩,让其经过路径压缩之后,该节点和到根节点路径上的所有节点全部直接指向根节点