内容简介:并查集(Union-Find Set),也叫Disjoint Set,由Bernard A. Galler和Michael J. Fischer在1964年提出,它主要是用来解决动态连通性问题。什么是动态连通性问题?
一:背景
并查集(Union-Find Set),也叫Disjoint Set,由Bernard A. Galler和Michael J. Fischer在1964年提出,它主要是用来解决动态连通性问题。
什么是动态连通性问题?
如上图,若两点之间存在一条路线可以相互连接,那么这两个点就是连通的。现在如果我们一边新加入更多的点和路径,一边又要立即得到随机某两点是否连通,那么这该如何实现呢?
二:算法分析与实现
并查集的思想非常简单,把那些彼此连通的点连起来形成一棵树,如下图,
那么我们要判断某两点是否连通,只需判断它们的根是否相同即可。代码如下,
#include <iostream>
using namespace std;
#define N 10
int pre[N];
void Init()
{
for (int i = 0; i < N; i++)
pre[i] = i;
}
int Find(int i)
{
if (pre[i] == i)
return i;
int i_parent = pre[i];
int i_root = Find(i_parent);
return i_root;
}
void Union(int i, int j)
{
int i_root = Find(i);
int j_root = Find(j);
if (i_root == j_root)
return;
pre[i_root] = j_root;
}
int main()
{
Init();
Union(2, 9);
Union(4, 9);
Union(3, 4);
Union(5, 6);
// 判断 3 和 5 是否连通
if (Find(3) == Find(5))
cout << "3 和 5 连通.\n";
else
cout << "3 和 5 不连通.\n";
return 0;
}
三:算法优化
从上面的代码可以看出,算法的复杂度主要在 Find 函数中,而 Find 的快慢主要由树的高度决定,那么我们的问题就转化为 如何降低树的高度 ,常用的方法有两个: Path Compression 和 Union By Rank 。
(1)Path Compression(路径压缩)
因为我们只想要快速地找到根结点,对沿途经过的那些结点并不关心,因此可以在 Find 查找过程中,将沿途经过的每一个结点的父亲直接设置为根结点,如下图,
int Find(int i)
{
if (pre[i] == i)
return i;
int i_parent = pre[i];
int i_root = Find(i_parent);
pre[i] = i_root; // 路径压缩
return i_root;
}
从上图可以看到,压缩后,树的高度变低了。
但我们要清楚,路径压缩其实是牺牲了路径完整性来求得更高效的运行速度,对于某些需要输出路径的应用场景,路径压缩这种优化就无法使用了。
(2)Union By Rank(按秩合并)
Rank 翻译为"秩",在这里,我们可以简单地理解成"树的高度",见下图,
void Union(int i, int j)
{
int i_root = Find(i);
int j_root = Find(j);
if (i_root == j_root)
return;
if (rank[i_root] > rank[j_root])
swap(i_root, j_root);
pre[i_root] = j_root;
if (rank[i_root] == rank[j_root]) // 两个秩相同的树合并,则整体的秩就会增加 1
rank[j_root]++;
}
(3)更多的优化方法
上面讲的只是平时常用的两种,在维基上还有更多的优化方法,包括Path Having、Path Splitting等等,需要了解的朋友可以到 这里 。
四:优化后的代码
下面的代码同时使用了路径压缩和按秩合并优化。
#include <iostream>
#include <algorithm>
using namespace std;
#define N 10
int pre[N];
int rank[N];
void Init()
{
for (int i = 0; i < N; i++)
{
pre[i] = i;
rank[i] = 0;
}
}
int Find(int i)
{
if (pre[i] == i)
return i;
int i_parent = pre[i];
int i_root = Find(i_parent);
pre[i] = i_root; // 路径压缩
return i_root;
}
void Union(int i, int j)
{
int i_root = Find(i);
int j_root = Find(j);
if (i_root == j_root)
return;
if (rank[i_root] > rank[j_root])
swap(i_root, j_root);
pre[i_root] = j_root;
if (rank[i_root] == rank[j_root]) // 两个高度相同的树合并则整体的高度就会增加
rank[j_root]++;
}
int main()
{
Init();
Union(2, 9);
Union(4, 9);
Union(3, 4);
Union(5, 6);
// 判断 3 和 5 是否连通
if (Find(3) == Find(5))
cout << "3 和 5 连通.\n";
else
cout << "3 和 5 不连通.\n";
return 0;
}
五:算法复杂度
当不采用任何优化的情况下,并查集每次操作的最差时间复杂度为$O(n)$,即树退化为链表的时候。
当仅采用路径压缩优化时,每次操作的最差时间复杂度为$O(1+log_{2+f/n}n)$,这个值比$O(log_2n)$小,其中$f$为查找次数。
当仅使用按秩合并优化时,每次操作的最差时间复杂度为$Θ(log_2n) $。
当同时使用路径压缩和按秩合并优化时,摊还分析后,每次操作的最差时间复杂度为$O(log^∗n)$,其中$log^∗n$是 Iterated Logarithm 函数,实际使用中,它的值不超过5,如下图,这也就是说每次操作的实际复杂度,只有$O(1)$。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
SSA:用户搜索心理与行为分析
[美] 罗森菲尔德(Louis Rosenfeld) / 汤海、蔡复青 / 清华大学出版社 / 2014-4-1 / 59.00
何为站内搜索分析(SSA)?它如何帮助你挖掘用户搜索曰志,从中洞悉用户搜索心理和行为,从而有针对性地改善用户体验,提升网站价值?这些都可以从《SSA:用户搜索心理与行为分析》中找到答案。《SSA:用户搜索心理与行为分析》首先通过故事来说明SSA是如何使Vanguard集团起死回生的,简要介绍SSA并指导读者动手实践。其次,通过丰富的实例来介绍很多工具和方法,帮助读者着手分析用户查询数据,从中获得更......一起来看看 《SSA:用户搜索心理与行为分析》 这本书的介绍吧!