我们首先实现的版本一的并查集,用数组实现,在Union(p,q)的操作中,我们遍历一遍数组,将值和p相同的元素的值都改为q所对应的值。
这样的话,我们的Union操作实际上时间复杂度是O(n)
版本二中,我们将在Union(p,q)的操作中,找到p和q的根节点,让其中一个的根结点指向另一个元素的根节点即可(指向根节点的原因是我们想尽量减少树的高度,从而在查找的时候效率更高。)
在测试10000 和 100000的数据中我们分别比较两种算法的性能,结果如下:
UnionFind1.h
#ifndef UNION_FIND1_H_
#define UNION_FIND1_H_
#include<iostream>
#include<cassert>
using namespace std;
namespace UF1
{
class UnionFind
{
private:
int* id;
int count;
public:
UnionFind( int n)
{
count = n;
id = new int[n];
for(int i = 0; i< n ; i++)
{
id[i] = i;
}
}
~UnionFind()
{
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 = find(p);
int qID = find(q);
if(pID == qID)
return;
for(int i = 0 ;i < count ; i++) //这里用的是 i < count 不是 i < n
{
if(id[i] == pID)
id[i] = qID;
}
}
};
};
#endif
UnionFind2.h
#ifndef UNION_FIND2_H_
#define UNION_FIND2_H_
#include<iostream>
#include<cassert>
namespace UF2
{
class UnionFind
{
private:
int* parent;
int count;
public:
UnionFind(int count)
{
this->count = count;
parent = new int[count];
for(int i = 0 ; i < count ; i++)
parent[i] = i;
}
~UnionFind()
{
delete [] parent;
}
int find(int p)
{
assert(p < count && p >= 0);
while( p != parent[p])
{
p = parent[p];
}
return p;
}
void unionElements(int p , int q)
{
int pRoot = find(p);
int qRoot = find(q);
if( pRoot == qRoot)
return;
parent[pRoot] = qRoot;
}
bool isConnected(int p , int q)
{
return find(p) == find(q);
}
};
};
#endif
UnionFindTestHelper.h
#ifndef UNION_FIND_TEST_HELPER_H_
#define UNION_FIND_TEST_HELPER_H_
//学习一下测试用例的写法
#include<iostream>
#include<ctime>
#include"UnionFind1.h"
#include"UnionFind2.h"
using namespace std;
namespace UnionFindTestHelper
{
void testUF1( int n)
{
srand(time(NULL));
UF1::UnionFind uf = UF1::UnionFind(n);
time_t startTime = clock();
for(int i = 0; i < n; i++) //O(n^2)
{
int a = rand()%n;
int b = rand()%n;
uf.unionElements(a,b); //O(n)
}
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;
}
void testUF2( int n)
{
srand(time(NULL));
UF2::UnionFind uf = UF2::UnionFind(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 << "UF2: " << 2*n << "ops, " << double(endTime-startTime) / CLOCKS_PER_SEC << " s. " << endl;
}
};
#endif
main.cpp
#include<iostream>
#include "UnionFindTestHelper.h"
int main(int argc, char const *argv[])
{
int n = 100000;
UnionFindTestHelper::testUF1(n);
UnionFindTestHelper::testUF2(n);
return 0;
}