数据结构:森林与并查集

一、算法原理

(一)概念:

森林(Forest)在计算机科学中,在数据结构中,可以看作是一组不相交的树(Tree)的集合。每棵树都是独立的,它们之间没有边直接相连。在数据结构和算法中,森林经常用于表示和处理一系列没有共同祖先的节点集合。
并查集(Union-Find)是一种数据结构,用于高效地处理一些不交集的合并及查询问题。它主要用于处理一些动态连通性问题,如判断两个元素是否属于同一个集合,或者合并两个集合。

(二)Quick_Find算法

图中每一个结点代表一棵树,我们如果要连接两棵树,可以通过染色法将两个结点染成相同的颜色。
在这里插入图片描述
例:
连接结点1——结点4
在这里插入图片描述
连接结点2——结点5
在这里插入图片描述
连接结点4——结点5

代码演示

void init(int n) {
    for (int i = 0; i <= n; i++) color[i] = i;
    return ;
}

int find(int a) {
    return color[a];
}

int merge(int a, int b, int n) {
    int aa = find(a), bb = find(b);
    if (aa == bb) return 0;
    for (int i = 0; i <= n; i++) {
        if (color[i] == aa) {
            color[i] = bb;
        }
    }
    return 1;
}

复杂度分析

查找判断:O(1)
联通操作:O(n)

(三)Quick_Union算法

图中每一个结点代表一棵树,我们如果要连接两棵树,可以通过将一棵树作为另一棵树的子树。
例:
注:默认将前一棵子树连接到后一颗上面
连接结点1——结点4
在这里插入图片描述
连接结点2——结点5
在这里插入图片描述

连接结点6——结点4
在这里插入图片描述

代码演示

void init(int n) {
    for (int i = 0; i <= n; i++) {
        fa[i] = i;
    }
    return ;
}

int find(int x) {
    if (fa[x] == x) return x;
    return find(fa[x]);
}

int merge(int a, int b) {
    int aa = find(a), bb = find(b);
    if (aa == bb) return 0;
    fa[aa] = bb;
    return 1;
}

复杂度分析

查找判断:
时间复杂度:最好情况O(lgn)最坏情况下是O(n),其中n是节点的总数。这是因为在极端情况下(如树退化为链表),从任意节点到根节点的路径可能包含n-1个边。
联通操作:O(1)

(四)并查集优化

按秩优化

通过加入每棵树的权重(即结点数量)使得每棵树不至于退化为链表。

代码演示
void init(int n) {
    for (int i = 0; i <= n; i++) {
        fa[i] = i;
        size[i] = 1;
    }
    return ;
}

int find(int x) {
    if (fa[x] == x) return x;
    return find(fa[x]);
}

int merge(int a, int b) {
    int aa = find(a), bb = find(b);
    if (aa == bb) return 0;
    if (size[aa] < size[bb]) {
        fa[aa] = bb;
        size[bb] += size[aa];
    } else {
        fa[bb] = aa;
        size[aa] += size[bb];
    }
    return 1;
}
复杂度分析

查找判断:
时间复杂度:加入权重使得复杂度稳定在O(lgn)
联通操作:O(1)

路径压缩

在查找过程中每查找一条路径,就将路径上的所有结点放在根节点底下,使得数的整体高度变扁,查找单次的操作次数可能比较高,但是均摊时间复杂度为1。

代码演示
void init(int n) {
    for (int i = 0; i <= n; i++) {
        fa[i] = i;
        size[i] = 1;
    }
    return ;
}

int find(int x) {
    return fa[x] = (fa[x] == x ? x : find(fa[x]));
}

int merge(int a, int b) {
    int aa = find(a), bb = find(b);
    if (aa == bb) return 0;
    if (size[aa] < size[bb]) {
        fa[aa] = bb;
        size[bb] += size[aa];
    } else {
        fa[bb] = aa;
        size[aa] += size[bb];
    }
    return 1;
}

二、算法演示

(一)Leetcode_128

Leetcode_128 点击跳转
在这里插入图片描述

代码演示

class Unionset{
    public:
    Unionset(int n):fa(n + 1),wight(n + 1){
        for(int i = 0; i <= n; i++){
            fa[i] = i;
            wight[i] = 1;
        }
    }
    int get(int i){
        return fa[i] = (fa[i] == i ? i : get(fa[i]));
    }
    int merge(int a, int b){
    
        /*
        if(get(a) == get(b)) return 0;
        fa[get(a)] = get(b);
        wight[get(b)] += wight[get(a)];
        return 1;
        */这里a的父节点更新,导致错误
        
        int aa = get(a), bb = get(b);
        if(get(a) == get(b)) return 0;
        fa[aa] = bb;
        wight[bb] += wight[aa];
        return 1;
    }
    vector<int> fa,wight;
};

class Solution {
public:
    int longestConsecutive(vector<int>& nums) {
        int n = nums.size(), cnt = 1;
        if(n == 0) return 0;
        Unionset set(n);
        unordered_map<int,int> map;
        for(int i = 0; i < n; i++){
            if(map.find(nums[i]) != map.end()) continue;
            map[nums[i]] = cnt++; 
            if(map.find(nums[i] - 1) != map.end()) set.merge(map[nums[i] - 1], map[nums[i]]);
            if(map.find(nums[i] + 1) != map.end()) set.merge(map[nums[i] + 1], map[nums[i]]);
        }
        int ans = 0;
        for(int i = 0; i <= n; i++) if(set.wight[i] > ans) ans = set.wight[i];
        return ans;
    }
};

(二)Leetcode_130

Leetcode_130 点击跳转
在这里插入图片描述

代码演示

class UnionSet{
    public:
    vector<int> fa;
    UnionSet(int n):fa(n + 1){
        for(int i = 0; i <= n; i++){
            fa[i] = i;
        }
    }
    int find(int x){
        return fa[x] = (fa[x] == x ? x : find(fa[x]));
    }
    void merge(int a, int b){
        fa[find(a)] = find(b);
        return;
    }
};

class Solution {
public:
    void solve(vector<vector<char>>& board) {
        int n = board.size(), m = board[0].size(), ind;
        UnionSet u(n*m);
        for(int i = 0; i < n; i++){
            for(int j = 0; j < m; j++){
                if(board[i][j] != 'O') continue;
                ind = i * m + j + 1;
                if(i == 0 || i == n - 1) u.merge(ind, 0);
                if(j == 0 || j == m - 1) u.merge(ind, 0);
                if(j < (m - 1) && board[i][j + 1] == 'O') u.merge(ind, ind + 1);
                if(i < (n - 1) && board[i + 1][j] == 'O') u.merge(ind, ind + m);
            }
        }
        for(int i = 0; i < n; i++){
            for(int j = 0; j < m; j++){
                if(board[i][j] != 'O') continue;
                ind = i * m + j + 1;
                if(u.find(ind) != u.find(0)) board[i][j] = 'X';
            }
        }
        return;
    }
};

(三)HZOJ_322

在这里插入图片描述

代码演示

class UnionSet {
public :
    UnionSet(int n) : fa(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> fa;
};

struct Data {
    int i, j, e;
};

void solve() {
    int n, cnt = 0;
    scanf("%d", &n);
    vector<Data> arr(n);
    unordered_map<int, int> h;
    for (int i = 0; i < n; i++) {
        Data &x = arr[i];
        scanf("%d%d%d", &x.i, &x.j, &x.e);
        if (h.find(x.i) == h.end()) h[x.i] = cnt++;
        if (h.find(x.j) == h.end()) h[x.j] = cnt++;
    }
    UnionSet u(2 * n);
    for (int i = 0; i < n; i++) {
        if (arr[i].e == 0) continue;
        u.merge(h[arr[i].i], h[arr[i].j]);
    }
    int flag = 1;
    for (int i = 0; i < n && flag; i++) {
        if (arr[i].e == 1) continue;
        if (u.get(h[arr[i].i]) == u.get(h[arr[i].j])) {
            flag = 0;
        }  
    }
    printf("%s\n", flag ? "YES" : "NO");
    return ;
}

int main() {
    int t;
    scanf("%d", &t);
    while (t--) {
        solve();
    }
    return 0;
}

三、小结

用并查集解决连通性问题是一个很好的方法,如果文章有帮助,就给个免费的赞吧!小编和大家一起进步!
在这里插入图片描述

  • 32
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值