并查集
参考文章一
参考文章二
并查集(Disjoint-Set)是一种可以动态维护若干个不重叠的集合,并支持合并与查询两种操作的一种数据结构。
并查集有两个基本操作:
- Find: 查找元素所属子集;
- Union:合并两个子集为一个新的集合;
根节点的父节点是其本身,若某个节点的父节点不是其本身,则继续find(父节点)直至找到父节点为本身的根节点。
#include <vector>
class DisjSet
{
private:
std::vector<int> parent;
public:
DisjSet(int max_size) : parent(std::vector<int>(max_size))
{
// 初始化每一个元素的根节点都为自身
for (int i = 0; i < max_size; ++i)
parent[i] = i;
}
int find(int x)
{
return parent[x] == x ? x : find(parent[x]);
}
//非递归版本
int find(int p)
{
while(parent[p] != p ) p = parent[p];
return p;
}
void to_union(int x1, int x2)
{
parent[find(x1)] = find(x2);
}
// 判断两个元素是否属于同一个集合
bool is_same(int e1, int e2)
{
return find(e1) == find(e2);
}
};
并查集的优化
- 按秩合并:实际上就是在合并两棵树时,将高度较小的树合并到高度较大的树上。这里我们使用“秩”(rank)代替高度,秩表示高度的上界,通常情况我们令只有一个节点的树的秩为0,严格来说,rank + 1才是高度的上界;两棵秩分别为r1、r2的树合并,如果秩不相等,我们将秩小的树合并到秩大的树上,这样就能保证新树秩不大于原来的任意一棵树。如果r1与r2相等,两棵树任意合并,并令新树的秩为r1 + 1。
- 路径压缩:在执行Find的过程中,将路径上的所有节点都直接连接到根节点上。
#include <vector>
class DisjSet
{
private:
std::vector<int> parent;
std::vector<int> rank; // 秩
public:
DisjSet(int max_size) : parent(std::vector<int>(max_size)),
rank(std::vector<int>(max_size, 0))
{
for (int i = 0; i < max_size; ++i)
parent[i] = i;
}
int find(int x)
{
//这里一直在不断更新父节点的值,将一条路径上所有的数的父节点都更新为根节点
return x == parent[x] ? x : (parent[x] = find(parent[x]));
}
// 非递归版本
int find(int x)
{
int p = x, t;
while (uset[p] != p) p = uset[p];
while (x != p) { t = uset[x]; uset[x] = p; x = t; }
return x;
}
void to_union(int x1, int x2)
{
int f1 = find(x1);
int f2 = find(x2);
if (rank[f1] > rank[f2])
parent[f2] = f1;
else
{
parent[f1] = f2;
if (rank[f1] == rank[f2])
++rank[f2];
}
}
bool is_same(int e1, int e2)
{
return find(e1) == find(e2);
}
};
两次遍历法计算连通域
#include <iostream>
using namespace std;
class DisjSet
{
private:
int* parent;
int* rank;
public:
DisjSet(int maxsize) : parent(new int[maxsize]), rank(new int[maxsize])
{
for (int i = 0; i < maxsize; ++i)
{
parent[i] = i;
rank[i] = 0;
}
}
~DisjSet()
{
delete[] parent;
delete[] rank;
}
int find(int x)
{
return x == parent[x] ? x : (parent[x] = find(parent[x]));
}
void to_union(int x1, int x2)
{
int f1 = find(x1);
int f2 = find(x2);
if (rank[f1] > rank[f2])
parent[f2] = f1;
else
{
parent[f1] = f2;
if (rank[f1] == rank[f2])
rank[f2] += 1;
}
}
bool is_same(int e1, int e2)
{
return find(e1) == find(e2);
}
};
void TwoPassLabel(int* imgsrc,int* imglabel,int w, int h)
{
int label = 0;
DisjSet equivalences(w * h);
int left = -1;
int up = -w;
int loc = 0;
int cur = 0;
//第一次遍历
for (int j = 0; j < h; j++)
{
for (int i = 0; i < w; i++)
{
loc = j * w + i;
cur = imgsrc[loc];
if (cur == 0)
{
continue;
}
if (i > 0 && imgsrc[loc + left])
{
imglabel[loc] = imglabel[loc + left];
if (j > 0 && imgsrc[loc + up + left] == 0 && imgsrc[loc + up])
{
equivalences.to_union(imglabel[loc], imglabel[loc + up]);
}
}
else if (j > 0 && imgsrc[loc + up])
{
imglabel[loc] = imglabel[loc + up];
}
else
{
label++;
imglabel[loc] = label;
}
}
}
//对标签排序:因为可能出现1234 3的父节点为1,更新标签后为124
int next_label = 1;
int *renumber=new int[label+1]();
for (int i = 1; i <= label; i++)
{
int temp = equivalences.find(i);
if (renumber[temp]==0)
{
renumber[temp] = next_label;
renumber[i] = next_label;
next_label++;
}
else
{
renumber[i] = renumber[temp];
}
}
//第二次遍历
for (int j = 0; j < h; j++)
{
for (int i = 0; i < w; i++)
{
imglabel[j * w + i] = renumber[imglabel[j * w + i]];
}
}
delete[]renumber;
}
int main()
{
int img[30] = {
0, 0, 1, 0 ,1 ,
0, 1, 0, 1, 1 ,
1, 1, 1, 0, 1,
0, 0, 0, 1 ,0 ,
0, 1, 0, 1, 1 ,
1, 1, 0, 0, 1, };
int imglabel[30] = {0};
TwoPassLabel(img, imglabel, 5, 6);
for (int i = 0; i < 30; i++)
{
cout << imglabel[i] << endl;
}
return 0;
}