数据结构 - 并查集(find-union/disjoint-set)

概念

在这里插入图片描述

三个主要操作

在这里插入图片描述

  1. 没有出现闭环的情况
    在这里插入图片描述

  2. 没有出现闭环的情况
    在这里插入图片描述

  3. 代码实现

// 一定要结合图看,简单直接
#include <stdio.h>
#include "stdlib.h"

/// 最大定点数量
#define VERTICES 6

/// 初始化
void initialize(int parent[], int rank[]) {
    int i;
    for (i = 0; i < VERTICES; i++) {
        parent[i] = -1;
        rank[i] = 0;
    }
}

/// find,朴素查找元素的根,非递归
int find_root(int x, int parent[]) {
    int x_root = x;
    while (parent[x_root] != -1) {
        x_root = parent[x_root];
    }
    return x_root;
}

#if 0
/// 1 - Union successfully, 0 - union failed
///  缺点:如果union过程是1->2, 2->3, 3->4.....  存在的问题:时间复杂度O(n),会造成很长的链,树高度越小越好,可优化
/// 1 - Union successfully, 0 - union failed
int union_vertices(int x, int y) {
    int x_root = find_root(x, parent);
    int y_root = find_root(y, parent);
    if (x_root == y_root) {
        return 0;
    } else {
        parent[x_root] = y_root;
        return 1;
    }
}
#endif


#if 1
/// 对上面union_vertices函数的优化:新增rank数组,如果rank[x] < rank[y],则parent[x_root] = y_root; 如果rank[y] < rank[x],则parent[y_root] = x_root; 如果相等,则parent[x_root] = y_root, rank[y]++;
int union_vertices(int x, int y, int parent[], rank[]) {
    int x_root = find_root(x, parent);
    int y_root = find_root(y, parent);
    if (x_root == y_root) {
        return 0;
    } else {
        if (rank[x_root] > rank[y_root]) {
            parent[y_root] = x_root;
        } else if(rank[x_root] < rank[y_root]) {
            parent[x_root] = y_root;
        } else {
            parent[x_root] = y_root;
            rank[y_root]++;
        }
        
//        parent[x_root] = y_root;
        return 1;
    }
}
#endif

int main(int argc, const char * argv[]) {
    printf("Hello, 并查集!\n");
    
    int parent[VERTICES] = {0};
    int rank[VERTICES] = {0};
    
    int edges[6][2] = {
        {0, 1}, {1, 2}, {1, 3}, {3, 4}, {2, 4}, {2, 5}
    };
    
    initialize(parent, rank);
    
    int i;
    for (i = 0; i < sizeof(edges)/sizeof(edges[0]); i++) {
        int x = edges[i][0];
        int y = edges[i][1];
        if (union_vertices(x, y, parent, rank) == 0) {
            printf("Circle detected!\n");
            exit(1);
        }
    }
    
    printf("No cycles found!\n");
    return 0;
}

在这里插入图片描述

补充: 路径压缩实现

  • 朴素查找算法

使用并查集查找时,如果查找次数很多,那么使用朴素版的查找方式肯定要超时。比如,有一百万个元素,每次都从第一百万个开始找,这样一次运算就是106,如果程序要求查找个一千万次,这样下来就是1013,肯定要出问题的。
  
这是朴素查找的代码,适合数据量不大的情况:

void find(int x) {
	int r = x;
	while (parent[r] != r) {
		r = parent[r];
	}
	return r;
}
  • 递归路径压缩

下面是采用路径压缩的方法查找元素:

//查找x元素所在的集合,回溯时压缩路径
int find(int x) {
	if (x != parent[x]) {
		//从x结点搜索到祖先结点所经过的结点都指向该祖先结点
		parent[x] = find(parent[x]);
	}
	return parent[x];
}
  • 非递归路径压缩

上面是一采用递归的方式压缩路径, 但是,递归压缩路径可能会造成溢出栈,下面我们说一下非递归方式进行的路径压缩:

int find(int x) {
    int t, k, r;
    r = x;

    //查找跟节点
    while (parent[r] != r) {
        r = parent[r];
    }

    //非递归路径压缩操作
    k = x;
    while (k != r) {
        t = parent[k];
        parent[k] = r;
        k = t;
    }
    return r;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值