并查集

本文介绍了并查集的基本概念及其在解决节点连通性问题中的应用。详细解释了并查集的实现方法,并提供了五种不同的优化策略,包括使用父节点表示法、按秩合并、路径压缩等技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

并查集

并查集能很好地解决网络中两个节点是否连接的问题,但并查集不会给出节点之间的路径。正因为如此,并查集在判断节点的连通性时效率很高。其时间复杂度为O(1)。

并查集实现

思路:
  • 相连的节点具有相同的id值
  • 要实现p,q两个节点的合并,就需要将所有与p节点id值相同的节点的id改为q的id。
function unionFind(n){
    this.id = [];
    for (var i = 0; i < n; i++) {
        //初始化时,每个节点的id值都不相同
        this.id[i] = i;
    }
    this.find = function(p){
        if (p >= 0 && p < this.id.length) {
            return this.id[p];
        }
    }

    this.isConnected = function(p,q){
        return this.find(p) == this.find(q);
    }

    this.unionElements = function(p,q){
        var pId = this.id[p];
        var qId = this.id[q];
        if (pId == qId) {
            return;
        }
        for (var i = 0; i < this.id.length; i++) {
            if (this.id[i] == pId) {
                this.id[i] = qId;
            }
        }
    }
}

并查集优化(1)

思路:
  • 为每个元素指定父节点, 初始化时每个元素的父节点都指向自身
  • 两个元素相连可以看做是一个元素是另一个元素的父节点
function unionFind2(n){
    this.parent = [];
    for (var i = 0; i < n; i++) {
        //初始化时父节点等于自身
        this.parent[i] = i;
    }

    this.find = function(p){
        if (p < 0 || p >= this.parent.length) {
            return -1;
        }
        //获得元素p的根节点
        while(parent[p] != p ){
            p = parent[p];
        }
        return p;
    }

    this.isConnected = function(p,q){
        //两个元素根节点相等即代表相连
        return this.find(p) == this.find(q);
    }

    this.unionElements = function(p,q){
        int pParent = find[p];
        int qParent = find[q];
        if (pParent == qParent) {
            return;
        }
        //将一个元素的父节点设置为另一个元素,则代表两个元素相连
        this.parent[pParent] = qParent;
    }
}

并查集优化(2)

思路:
  • 如果节点之间相连所形成的树层数太高的话,查到元素的根节点所耗费的时间会增加
  • 增加一个数组,用于记录以每个节点为根节点的树中子节点的数量
  • 每次合并元素之前,先判断一下待合并的元素所在的根节点哪个节点数量多,将数量少的根节点的父节点设置为数量多的根节点,并维护子节点数量数组
function unionFind2(n){
    this.parent = [];
    this.size = [];
    for (var i = 0; i < n; i++) {
        this.parent[i] = i;
        //初始化时,每个节点的根节点都是自身,因此,size数量都为1
        this.size[i] = 1;
    }

    this.find = function(p){
        if (p < 0 || p >= this.parent.length) {
            return -1;
        }
        while(parent[p] != p ){
            p = parent[p];
        }
        return p;
    }

    this.isConnected = function(p,q){
        return this.find(p) == this.find(q);
    }

    this.unionElements = function(p,q){
        int pParent = find[p];
        int qParent = find[q];
        if (pParent == qParent) {
            return;
        }
        //合并时判断哪个分支数量多,将数量少的父节点设置为数量多的
        if (this.size[pParent] > this.size[qParent]) {
            this.parent[qParent] = pParent;
            this.size[pParent] += this.size[qParent];
        }else{
            this.parent[pParent] = qParent;
            this.size[qParent] += this.size[pParent];
        } 
    }
}

并查集优化(3)

思路:
  • 用节点数量判断应该怎样连接两个分支是不准确的,因为节点数量不能代表一个分支的节点层数
  • 用节点层数来判断怎样合并两个分支更加合理
function unionFind2(n){
    this.parent = [];
    //rank表示以每个节点为根节点的分支树的高度
    this.rank = [];
    for (var i = 0; i < n; i++) {
        this.parent[i] = i;
        //初始化时每个分支的高度都为1
        this.rank[i] = 1;
    }

    this.find = function(p){
        if (p < 0 || p >= this.parent.length) {
            return -1;
        }
        while(parent[p] != p ){
            p = parent[p];
        }
        return p;
    }

    this.isConnected = function(p,q){
        return this.find(p) == this.find(q);
    }

    this.unionElements = function(p,q){
        int pParent = find[p];
        int qParent = find[q];
        if (pParent == qParent) {
            return;
        }
        //将层数少的分支的父节点设置为层数大的分支的父节点,此时,分支层数不会发生变化
        if (this.rank[pParent] > this.rank[qParent]) {
            this.parent[qParent] = pParent;
        }else if(this.rank[pParent] < this.rank[qParent]){
            this.parent[pParent] = qParent;
        } else{
            //当两个分支层数相同时,连接在一起之后,其中一个分支的层数将+1
            this.parent[pParent] = qParent;
            this.rank[qParent] += 1;
        }
    }
}

并查集优化(4)

思路:
  • 每一个分支的层数越小,查询的效率越高
  • 在find寻找某一元素的根节点的过程中,可以同时对分支的路径进行压缩,减少分支层数
function unionFind2(n){
    this.parent = [];
    this.rank = [];
    for (var i = 0; i < n; i++) {
        this.parent[i] = i;
        this.rank[i] = 1;
    }

    this.find = function(p){
        if (p < 0 || p >= this.parent.length) {
            return -1;
        }
        while(parent[p] != p ){
            parent[p] = parent[parent[p]];//路径压缩
            p = parent[p];
        }
        return p;
    }

    this.isConnected = function(p,q){
        return this.find(p) == this.find(q);
    }

    this.unionElements = function(p,q){
        int pParent = find[p];
        int qParent = find[q];
        if (pParent == qParent) {
            return;
        }
        if (this.rank[pParent] > this.rank[qParent]) {
            this.parent[qParent] = pParent;
        }else if(this.rank[pParent] < this.rank[qParent]){
            this.parent[pParent] = qParent;
        } else{
            this.parent[pParent] = qParent;
            this.rank[qParent] += 1;
        }
    }
}

并查集优化(5)

思路:
  • 路径压缩是减少分支层数的有效方法,最理想的路径压缩效果是:在每个分支中,所有子节点的父节点都为根节点,即分支的层数为2。
function unionFind2(n){
    this.parent = [];
    this.rank = [];
    for (var i = 0; i < n; i++) {
        this.parent[i] = i;
        this.rank[i] = 1;
    }

    this.find = function(p){
        if (p < 0 || p >= this.parent.length) {
            return -1;
        }
        //第二种路径压缩方法,通过递归的方式将所有节点的父节点都设置为根节点
        if (this.parent[p] != p ) {
            this.parent[p] = find(this.parent[p]);
        }
        return this.parent[p];
    }

    this.isConnected = function(p,q){
        return this.find(p) == this.find(q);
    }

    this.unionElements = function(p,q){
        int pParent = find[p];
        int qParent = find[q];
        if (pParent == qParent) {
            return;
        }
        if (this.rank[pParent] > this.rank[qParent]) {
            this.parent[qParent] = pParent;
        }else if(this.rank[pParent] < this.rank[qParent]){
            this.parent[pParent] = qParent;
        } else{
            this.parent[pParent] = qParent;
            this.rank[qParent] += 1;
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值