1
知识点1
并查集:
1.将两个集合合并
2.询问两个元素是否在一个集合当中
基本原理:每个集合用一棵树来表示。树根的编号就是整个集合的编号。每个结点存储它的父结点,p[x]表示x的父结点。
问题1:如何判断树根:if(p[x] == x)
问题2:如何求x的集合编号:while(p[x] != x) x = p[x]
问题3:如何合并两个集合:px是x的集合编号,py是y的集合编号,p[px] = py
知识点2
注意求集合编号的同时,一定要压缩路径!
int p[N];//p[x]为x的父节点
int cnt[N];//cnt[find(i)]表示结点i所在集合中的元素数目
int find(int x)//查找结点x的祖宗结点
{
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
int main()
{
for (int i = 1; i <= n; i ++ )//并查集的初始化
{
p[i] = i;
cnt[i] = 1;
}
//(1)将结点a所在集合和结点b所在集合合并
if(find(a) != find(b))
{
cnt[find(b)] += cnt[find(a)];//在更新数组p之前,先更新数组cnt
p[find(a)] = find(b);//将结点a所在集合和结点b所在集合合并
}
//(2)查询结点a和结点b是否属于同一个集合
if (find(a) == find(b)) puts("Yes");
else puts("No");
//(3)查询结点a所在集合中的元素数目
cout << cnt[find(a)] << endl;
}
//力扣并查集模板
//685. 冗余连接 II
struct UnionFind {
vector <int> ancestor;
UnionFind(int n) {
ancestor.resize(n);
for (int i = 0; i < n; ++i) {
ancestor[i] = i;
}
}
int find(int index) {
return index == ancestor[index] ? index : ancestor[index] = find(ancestor[index]);
}
void merge(int u, int v) {
ancestor[find(u)] = find(v);
}
};
class Solution {
public:
vector<int> findRedundantDirectedConnection(vector<vector<int>>& edges) {
int n = edges.size();
UnionFind uf = UnionFind(n + 1);
auto parent = vector<int>(n + 1);
for (int i = 1; i <= n; ++i) {
parent[i] = i;
}
int conflict = -1;
int cycle = -1;
for (int i = 0; i < n; ++i) {
auto edge = edges[i];
int node1 = edge[0], node2 = edge[1];
if (parent[node2] != node2) {
conflict = i;
} else {
parent[node2] = node1;
if (uf.find(node1) == uf.find(node2)) {
cycle = i;
} else {
uf.merge(node1, node2);
}
}
}
if (conflict < 0) {
auto redundant = vector<int> {edges[cycle][0], edges[cycle][1]};
return redundant;
} else {
auto conflictEdge = edges[conflict];
if (cycle >= 0) {
auto redundant = vector<int> {parent[conflictEdge[1]], conflictEdge[1]};
return redundant;
} else {
auto redundant = vector<int> {conflictEdge[0], conflictEdge[1]};
return redundant;
}
}
}
};
知识点3
带边权的并查集。
2
class UnionFind {
private:
vector<int> parent;
vector<int> size;
public:
//parent数组标记结点的根,初始化为-1,表示当前结点的根是它本身
//size数据记录当前根结点的大小,初始值为1
UnionFind(int n) {
parent.resize(n, -1);
size.resize(n, 1);
}
//查询p结点的根
int find(int p) {
if (parent[p] == -1) return p; //如果当前结点的根是它本身,则直接返回它
return parent[p] = find(parent[p]); //否则递归查询当前结点的父节点,同时路径压缩优化
}
//合并两个结点所在的连通域,返回合并后结点所在的根
int merge(int a, int b) {
int root1 = find(a);
int root2 = find(b);
if (root1 == root2) return root1; //已经在同一个连通域中,直接返回其中一个结点的根
//根据连通域的大小进行合并,保证合并后连通域的深度更小
if (size[root1] < size[root2]) {
parent[root1] = root2;
size[root2] += size[root1];
return root2;
} else {
parent[root2] = root1;
size[root1] += size[root2];
return root1;
}
}
//获取结点p的连通域的大小
int get_size(int p) {
int root = find(p);
return size[root];
}
};
class Solution {
public:
vector<int> pondSizes(vector<vector<int>>& land) {
int m = land.size(), n = land[0].size();
UnionFind uf(m * n); //创建并查集对象
vector<int> sizes; //记录每个连通域的大小
int dirs[8][2] = {{-1, -1}, {-1, 0}, {-1, 1},
{0, -1}, {0, 1},
{1,-1}, {1, 0}, {1, 1}}; //8个方向的偏移量
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
if (land[i][j] != 0) continue; //如果当前位置不是水,则跳过
int cur = i * n + j; //将二维坐标映射成一维
for (auto& dir : dirs) {
int ni = i + dir[0], nj = j + dir[1];
if (ni < 0 || nj < 0 || ni >= m || nj >= n) continue; //超过边界,跳过
if (land[ni][nj] != 0) continue; //如果邻居不是水,则跳过
int neighbor = ni * n + nj; //将邻居的二维坐标映射成一维
int root1 = uf.find(cur), root2 = uf.find(neighbor); //查询当前位置和邻居所在的连通域的根结点
if (root1 != root2) { //如果两个位置不在同一个连通域中
uf.merge(cur, neighbor);
}
}
}
}
unordered_set<int> visitedroot;
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
if (land[i][j] == 0) {
int p = i * n + j;
int root = uf.find(p);
int size = uf.get_size(p);
if (!visitedroot.count(root)) {
visitedroot.insert(root);
sizes.emplace_back(size);
}
}
}
}
sort(sizes.begin(), sizes.end()); //对所有连通域的大小进行排序
return sizes;
}
};