0_并查集
如果A,B两个节点有一条路径,则A,B是互连(连通)的 我们的主要任务是让集合中任意2个节点产生一条路径,或者确认2个节点是否是连通的 实现上,我们可以用一个数组来保存所有的数据,然后根据其内容来实现具体的实现
1_并查集Test类
对于n各节点的集合,随机执行n次的连通,再随机执行n的查找,确认所用的时间
namespace UnionFindTestHelper{
void testUF1( int n ){
srand ( time (NULL) );
UF1::UnionFind1 uf = UF1::UnionFind1(n);
time_t startTime = clock();
for ( int i = 0 ; i < n ; i ++ ){
int a = rand ()%n ;
int b = rand ()%n ;
uf.unionElements(a,b);
}
for (int i = 0 ; i < n ; i ++ ){
int a = rand ()%n ;
int b = rand ()%n ;
uf.isConnected(a,b);
}
time_t endTime = clock();
cout<<"UF1, " <<2 *n <<" ops, " <<double(endTime-startTime)/CLOCKS_PER_SEC<<" s" <<endl;
}
}
2_最简单的并查集实现
查找非常快,但是连通操作由于需要将所有已经连通的节点依次找到,所以平均时间复杂度为O(N) ; 实现: 一开始每个元素都保存自己的下标,如果2个元素需要连通,将其中一个的所有连通的元素内容都改成需要连通的那个元素的内容即可 优缺点: 查询时间很快,但是连通需要遍历整个数组
namespace UF1 {
class UnionFind1 {
private:
int *id ;
int count;
public:
UnionFind1(int n) {
count = n;
id = new int [n];
for (int i = 0 ; i < n; ++i) {
id [i] = i;
}
}
~UnionFind1() {
delete[] id ;
}
int find(int p) {
assert(p >= 0 && p <= count);
return id [p];
}
bool isConnected(int p, int q) {
return find(p) == find(q);
}
void unionElements(int p, int q) {
int pId = id [p];
int qId = id [q];
if (pId == qId) return ;
for (int i = 0 ; i < count; ++i) {
if (pId == id [i])
id [i] = qId;
}
}
};
}
3_并查集的改进1
改进的思路为:让每个元素都保存自己向上连通的那个节点的下标,如果向上没有连通,则保存自己的下标 优缺点:查找速度会慢一点,每次找到自己最上面的父节点,连通速度有提升,但不是数量级上的,因为直接让第二个节点的父节点与第一个节点的父节点连接即可(但是连通之前还是需要找到其父节点)
namespace UF2 {
class UnionFind2 {
private :
int *parent;
int count ;
public :
UnionFind2(int n) {
count = n;
parent = new int [n];
for (int i = 0 ; i < n; ++i) {
parent[i] = i;
}
}
~UnionFind2() {
delete[] parent;
}
int find(int p) {
assert(p >= 0 && p <= count );
while (p != parent[p])
p = parent[p];
return p;
}
bool isConnected(int p, int q) {
return find(p) == find(q);
}
void unionElements(int p, int q) {
int pParent = find(p);
int qParent = find(q);
if ( pParent == qParent )
return ;
parent[qParent] = pParent ;
}
};
}
4_并查集的改进2
思路: 给每个节点增加一个数据项来保存其作为根节点的情况下,这个集合树的层数(rank)/节点个数(sz[]),我们尽量让层数低的节点去链接在层数高的节点上,这个树的高度会有保障 这个版本的查找和连通平均时间复杂度接近O(1) ,证明可以查看下算法导论 路径压缩:连通的时候如果树的高度越低速度越快,如果每次查找的时候我们都可以压缩一下树的高度,那么可能会对连通有帮助(实测帮助不大,但是思路是好的)
namespace UF3 {
class UnionFind3{
private:
int *parent ;
int * rank;
int count;
public:
UnionFind3(int n) {
count = n;
parent = new int [n];
rank = new int [n];
for (int i = 0 ; i < n; ++i) {
parent [i] = i;
rank[i] = 0 ;
}
}
~UnionFind3() {
delete [] parent ;
delete [] rank;
}
int find(int p) {
assert(p >= 0 && p <= count);
if (p != parent [p])
parent [p] = find(parent [p]);
return parent [p];
}
bool isConnected (int p, int q) {
return find(p) == find(q);
}
void unionElements(int p, int q) {
int pParent = find(p);
int qParent = find(q);
if ( pParent == qParent )
return ;
if (rank[pParent] < rank[qParent]){
parent [p] = qParent;
}else if (rank[qParent] < rank[pParent]){
parent [q] = pParent;
}else {
parent [p] = qParent;
rank[qParent]++;
}
}
};
}