并查集的基础知识
Quick-Find算法
染色方法:
- 基于染色的思想,一开始所有点的颜色不同
- 连接两个点的操作,可以看成将一种颜色的点染成另一种颜色
- 如果两个点颜色一样,证明联通,否则不联通
- 这中方法叫做并查集的:【Quick-Find 算法】
联通判断O(1) 合并操作O(n)
class UnionSet {
public:
int *color, n;
UnionSet(int n): n(n){
color = new int[n + 1];
for(int i = 0; i <= n; i++){
color[i] = i;
}
}
int find(int x){
return color[x];
}
void merge(int a, int b){
if(color[a] === color[b]) return;
int cb = color[b];
for(int i = 0;i <= n; i++){
if(color[i] === cb) color[i] = color[a];
}
return;
}
}
int main(){
return 0;
}
- quick-find算法的联通判断非常快,可是合并操作非常慢
- 本质上问题中只是需要知道一个点与哪些点的颜色相同
- 而若干点的颜色可以通过间接指向同一个节点
- 合并操作时,实际上是将一棵树作为另一棵树的子树
Quick-Union算法
使用树型结构
联通判断 与tree-height 树高有关 合并操作O(n) 与tree-height 树高有关
class UnionSet {
public:
int *boss, n;
UnionSet(int n): n(n){
boss = new int[n + 1];
for(int i = 0; i <= n ; i++){
boss[i] = i;
}
}
int find(int x){
if(boss[x] === x) return x;
return find(boss[x]);
}
void merge(int a, int b){
int fa = find(a), fb = find(b);
if(fa === fb) return;
boss[fa] = fb;
return;
}
}
int main(){
return;
}
有效的减少树高能够提高效率
- 极端情况下会退化成一条链
- 将节点数量多的接到少的树上面,导致了退化
- 将树高深的接到浅的上面,导致了退化
问题:若要改进,是按照节点数量还是按照树的高度为合并参考?
指标:平均查找次数 = 所有节点的深度数量相加/ 总节点数量
节点少的作为儿子,节点多的作为爸爸
优化后:
class UnionSet {
public:
int *fa, *size, n;
UnionSet(int n): n(n){
fa = new int[n + 1];
size = new int[n + 1];
for(int i = 0; i <= n; i++){
fa[i] = i;
size[i] = 1;
}
}
int find(int x){
if(fa[x] == x) return x;
return find(fa[x]);
}
void merge(int a, int b){
int ra = find(a), rb = find(b);
if(ra == rb) return;
if(size[ra] < size[rb]){
fa[ra] = rb;
size[rb] += size[ra];
}else{
fa[rb] = ra;
size[ra] += size[rb];
}
return;
}
}
int main(){
return;
}
对树进行扁平化操作,让效率变高
class UnionSet {
public:
int *fa, *size, n;
UnionSet(int n): n(n){
fa = new int[n + 1];
size = new int[n + 1];
for(int i = 0; i <= n; i++){
fa[i] = i;
size[i] = 1;
}
}
int find(int x){
if(fa[x] == x) return x;
int root = find(fa[x]);
fa[x] = root;
return root;
}
void merge(int a, int b){
int ra = find(a), rb = find(b);
if(ra == rb) return;
if(size[ra] < size[rb]){
fa[ra] = rb;
size[rb] += size[ra];
}else{
fa[rb] = ra;
size[ra] += size[rb];
}
return;
}
}
int main(){
return;
}
并查集模版代码
class UnionSet {
public:
int *fa, n;
UnionSet(int n): n(n){
fa = new int[n + 1];
for(int i = 0;i <= n; i++){
fa[i] = i;
}
}
int get(int x){
return fa[x] = (fa[x] == x ? x : get(fa[x]));
}
void merge(int a, int b){
fa[get(a)] = get(b);
}
};
int main(){
return 0;
}
百变大咖:并查集
并查集是一类抽象化程度很高的数据结构
经典面试题 - 并查集基础题目
547. 省份数量
class Solution {
public:
class UnionSet {
public:
int *fa, n;
UnionSet(int n): n(n){
fa = new int[n + 1];
for(int i = 0;i <= n; i++){
fa[i] = i;
}
}
int get(int x){
return fa[x] = (fa[x] == x ? x : get(fa[x]));
}
void merge(int a, int b){
fa[get(a)] = get(b);
}
};
int findCircleNum(vector<vector<int>>& isConnected) {
int n = isConnected.size();
UnionSet u(n);
for(int i = 0; i < n; i++){
for(int j = 0; j < i; j++){
if(isConnected[i][j]) u.merge(i, j);
}
}
int ans = 0;
for(int i = 0; i < n; i++){
if(u.get(i) == i) ans += 1;
}
return ans;
}
};
200.岛屿数量
- 如果当前点和上面的点都是1,就把当前点和上面的点进行联通
- 如果当前点和左边的点都是1,就把当前点和左边的点进行联通
- 最后统计并查集中根节点的数量
class Solution {
public:
class UnionSet {
public:
int *fa, n;
UnionSet(int n): n(n){
fa = new int[n + 1];
for(int i = 0;i <= n; i++){
fa[i] = i;
}
}
int get(int x){
return fa[x] = (fa[x] == x ? x : get(fa[x]));
}
void merge(int a, int b){
fa[get(a)] = get(b);
}
};
int numIslands(vector<vector<char>>& grid) {
int n = grid.size(), m = grid[0].size();
UnionSet u(n * m);
#define ind(i, j) ((i) * m + (j))
for(int i = 0 ; i < n; i++){
for(int j = 0; j < m; j++){
if(grid[i][j] == '0') continue;
if(i > 0 && grid[i - 1][j] == '1') u.merge(ind(i, j), ind(i - 1, j));
if(j > 0 && grid[i][j - 1] == '1') u.merge(ind(i, j), ind(i, j - 1));
}
}
int ans = 0;
for(int i = 0; i < n; i++){
for(int j = 0;j < m; j++){
if(grid[i][j] == '1' && u.get(ind(i, j)) == ind(i, j)) ans += 1;
}
}
#undef ind;
return ans;
}
};
990. 等式方程的可满足性
class Solution {
public:
class UnionSet {
public:
int *fa, n;
UnionSet(int n): n(n){
fa = new int[n + 1];
for(int i = 0;i <= n; i++){
fa[i] = i;
}
}
int get(int x){
return fa[x] = (fa[x] == x ? x : get(fa[x]));
}
void merge(int a, int b){
fa[get(a)] = get(b);
}
};
bool equationsPossible(vector<string>& equations) {
UnionSet u(26);
for(auto s: equations){
if(s[1] == '!') continue;
int a = s[0] - 'a';
int b = s[3] - 'a';
u.merge(a, b);
}
for(auto s: equations){
if(s[1] == '=') continue;
int a = s[0] - 'a';
int b = s[3] - 'a';
if (u.get(a) == u.get(b)) return false;
}
return true;
}
};
684. 冗余连接
class Solution {
public:
class UnionSet {
public:
int *fa, n;
UnionSet(int n): n(n){
fa = new int[n + 1];
for(int i = 0;i <= n; i++){
fa[i] = i;
}
}
int get(int x){
return fa[x] = (fa[x] == x ? x : get(fa[x]));
}
void merge(int a, int b){
fa[get(a)] = get(b);
}
};
vector<int> findRedundantConnection(vector<vector<int>>& edges) {
UnionSet u(edges.size());
for(auto e: edges){
int a = e[0];
int b = e[1];
if(u.get(a) == u.get(b)) return e;
u.merge(a, b);
}
return vector<int>();
}
};
1319. 连通网络的操作次数
class Solution {
public:
class UnionSet {
public:
int *fa, n;
UnionSet(int n): n(n){
fa = new int[n + 1];
for(int i = 0;i <= n; i++){
fa[i] = i;
}
}
int get(int x){
return fa[x] = (fa[x] == x ? x : get(fa[x]));
}
void merge(int a, int b){
fa[get(a)] = get(b);
}
};
int makeConnected(int n, vector<vector<int>>& connections) {
if(connections.size() < n - 1) return -1;
UnionSet u(n);
for(auto e: connections){
int a = e[0];
int b = e[1];
u.merge(a, b);
}
int cnt = 0;
for(int i = 0; i < n; i++){
if(u.get(i) == i) cnt += 1;
}
return cnt - 1;
}
};
128. 最长连续序列
class Solution {
public:
class UnionSet {
public:
int *fa, *cnt, n;
UnionSet(int n): n(n){
fa = new int[n + 1];
cnt = new int[n + 1];
for(int i = 0;i <= n; i++){
fa[i] = i;
cnt[i] = 1;
}
}
int get(int x){
return fa[x] = (fa[x] == x ? x : get(fa[x]));
}
void merge(int a, int b){
if(get(a) == get(b)) return;
cnt[get(b)] += cnt[get(a)];
fa[get(a)] = get(b);
return;
}
};
int longestConsecutive(vector<int>& nums) {
unordered_map<int, int> ind;
UnionSet u(nums.size());
for(int i = 0; i < nums.size(); i++){
int x = nums[i];
if(ind.find(x) != ind.end()) continue;
if(ind.find(x - 1) != ind.end()){
u.merge(i, ind[x - 1]);
}
if(ind.find(x + 1) != ind.end()){
u.merge(i, ind[x + 1]);
}
ind[x] = i;
}
int ans = 0;
for(int i = 0;i < nums.size(); i++){
if(u.get(i) == i && u.cnt[i] > ans) ans = u.cnt[i];
}
return ans;
}
};
947. 移除最多的同行或同列石头
class Solution {
public:
class UnionSet {
public:
int *fa, n;
UnionSet(int n): n(n){
fa = new int[n + 1];
for(int i = 0;i <= n; i++){
fa[i] = i;
}
}
int get(int x){
return fa[x] = (fa[x] == x ? x : get(fa[x]));
}
void merge(int a, int b){
fa[get(a)] = get(b);
}
};
int removeStones(vector<vector<int>>& stones) {
UnionSet u(stones.size());
unordered_map<int, int> ind_x, ind_y;
for(int i = 0; i < stones.size(); i++){
int x = stones[i][0];
int y = stones[i][1];
if(ind_x.find(x) != ind_x.end()){
u.merge(i, ind_x[x]);
}
if(ind_y.find(y) != ind_y.end()){
u.merge(i, ind_y[y]);
}
ind_x[x] = i;
ind_y[y] = i;
}
int cnt = 0;
for(int i = 0; i < stones.size(); i++){
if(u.get(i) == i) cnt += 1;
}
return stones.size() - cnt;
}
};
1202. 交换字符串中的元素
class Solution {
public:
class UnionSet {
public:
int *fa, n;
UnionSet(int n): n(n){
fa = new int[n + 1];
for(int i = 0;i <= n; i++){
fa[i] = i;
}
}
int get(int x){
return fa[x] = (fa[x] == x ? x : get(fa[x]));
}
void merge(int a, int b){
fa[get(a)] = get(b);
}
};
string smallestStringWithSwaps(string s, vector<vector<int>>& pairs) {
UnionSet u(s.size());
priority_queue<char, vector<char>, greater<char>> h[s.size()];
for(auto p: pairs){
int i = p[0];
int j = p[1];
u.merge(i, j);
}
for(int i = 0; i< s.size(); i++ ){
h[u.get(i)].push(s[i]);
}
string ret = "";
for(int i = 0; i < s.size(); i++){
ret += h[u.get(i)].top();
h[u.get(i)].pop();
}
return ret;
}
};