并查集——集合的高效实现

目录

1.什么是集合?

2.什么是并查集?

3.并查集的表示

3.1双亲表示法 

3.2以数组形式存储 

4.并查集的定义 

4.1类型名称:并查集(Union-Find)

数据对象集

操作集

4.2按秩合并

4.3路径压缩 

5.并查集的初始化

6.并查集的并运算

7.并查集的查找

 完整代码示例


1.什么是集合?

在数据结构中,集合(Set)是一种不允许重复元素的数据结构。
 
集合中的元素是无序的,且每个元素在集合中仅出现一次。集合主要用于快速判断某个元素是否在集合中存在,以及对不同集合进行并集、交集、差集等操作。
 
集合通常提供以下基本操作:
 
1. 插入元素:将一个新元素添加到集合中,如果该元素已存在则不进行任何操作。
2. 删除元素:从集合中移除指定的元素。
3. 查找元素:判断一个元素是否在集合中。
4. 集合运算:如求两个集合的并集(包含两个集合中的所有元素)、交集(包含两个集合共有的元素)、差集(属于一个集合但不属于另一个集合的元素)等。
 
集合可以通过多种方式实现,比如使用数组、链表、哈希表等数据结构。 

2.什么是并查集?

并查集(Union-Find)是一种特殊的数据结构,它可以用于处理一些不相交集合的合并及查询问题。
 
集合是一个更广泛的概念,是一组无序且不重复元素的组合。
 
并查集是对集合的一种具体应用和操作方式。在并查集中,每个元素都属于一个特定的集合。
 
并查集主要支持两个重要操作:“合并”(Union)和“查找”(Find)。
 
“合并”操作将两个集合合并为一个集合。
 
“查找”操作用于确定一个元素所属的集合。
 
通过高效地实现这两个操作,并查集能够在解决一些图论问题、动态连通性问题等方面发挥重要作用。
 
总的来说,并查集是一种基于集合概念的特殊数据结构,专注于高效地处理集合的合并和元素所属集合的查询。

并查集通常通过两种技术来优化操作效率:

  1. 路径压缩(Path Compression):在执行查找操作时,将访问路径上的所有节点直接连接到根节点,从而加速后续的查找操作。
  2. 按秩合并(Union by Rank/Size):在合并操作时,总是将较小的树合并到较大的树上,从而避免树的不平衡。

3.并查集的表示

3.1双亲表示法 

双亲表示法: 孩子指向双亲。 

用树结构表示集合,树的每个结点代表一个集合元素 

3.2以数组形式存储 

其实处理这个问题主要是往上追溯其父节点,而并不是一味的向下追寻。

所以如同上一章,我们使用数组形式的树。

4.并查集的定义 

4.1类型名称:并查集(Union-Find)

数据对象集
  • 一个有0个或多个元素的有穷集合。
  • 每个元素属于某一个集合,集合中的元素通过树结构连接,树的根节点为集合的代表元素。
操作集
  1. Union-Find CreatUnionFind(int size):生成一个包含size个元素的并查集,每个元素初始化为其自身的父节点。
  2. int Find(UnionFind uf, int x):查找元素x所在集合的代表元素,采用路径压缩优化。
  3. void Union(UnionFind uf, int x, int y):将元素xy所在的集合合并,采用按秩合并优化。
  4. int IsConnected(UnionFind uf, int x, int y):判断元素xy是否在同一个集合中。

4.2按秩合并

        随着集合元素的存入,树型的组织形式势必会让这棵树越来越高,这就导致了查找效率的降低,所以我们再进行集合的并运算时,将子树的元素个数作为其秩(优先级),秩小的子树在合并时需要挂载在秩大的子树下。 

 

 

        并且为了防止结构体中的空间浪费,我们可以将秩用根节点的负数表示的绝对值来代替。

 在合并时直接通过比较两子树的秩来决定怎样合并,可以显著提升并查集的效率。 

4.3路径压缩 

        可以在查询操作时将根节点下面挂载的结点直接指向根结点,这样省区了一层一层的指向,有利于我们的查找效率提高。并且由于是树型结构,且根结点下的结点本身就同属于一个集合,同时指向一个根节点省去了很多事情。

5.并查集的初始化

// 创建并查集,初始化为用户给定的数组
SetType* CreatUnionFind(ElementType* elements, int size) {
    SetType* uf = (SetType*)malloc(size * sizeof(SetType));
    int i; 
    for (i = 0; i < size; i++) {
        uf[i].Data = elements[i];
        uf[i].Parent = -1; // 初始化每个节点的父节点为自己,秩为1
    }
    return uf;
}

6.并查集的并运算

// 合并元素x和y所在的集合,按秩合并
void Union(SetType* uf, int x, int y) {
    int rootX = Find(uf, x);
    int rootY = Find(uf, y);
    if (rootX != rootY) {
        if (uf[rootX].Parent < uf[rootY].Parent) { // rootX树更高
            uf[rootX].Parent += uf[rootY].Parent; // 秩相加
            uf[rootY].Parent = rootX; // rootY挂在rootX下
        } else { // rootY树更高或一样高
            uf[rootY].Parent += uf[rootX].Parent; // 秩相加
            uf[rootX].Parent = rootY; // rootX挂在rootY下
        }
    }
}

7.并查集的查找

// 查找元素x的根节点,路径压缩
int Find(SetType* uf, int x) {
    if (uf[x].Parent < 0) {
        return x; // x是根节点
    } else {
        uf[x].Parent = Find(uf, uf[x].Parent); // 路径压缩
        return uf[x].Parent;
    }
}

 完整代码示例

#include <stdio.h>
#include <stdlib.h>

typedef int ElementType;

typedef struct {
    ElementType Data;
    int Parent; // 负数表示根节点且其绝对值为秩
} SetType;

// 创建并查集,初始化为用户给定的数组
SetType* CreatUnionFind(ElementType* elements, int size) {
    SetType* uf = (SetType*)malloc(size * sizeof(SetType));
    int i; 
    for (i = 0; i < size; i++) {
        uf[i].Data = elements[i];
        uf[i].Parent = -1; // 初始化每个节点的父节点为自己,秩为1
    }
    return uf;
}

// 查找元素x的根节点,路径压缩
int Find(SetType* uf, int x) {
    if (uf[x].Parent < 0) {
        return x; // x是根节点
    } else {
        uf[x].Parent = Find(uf, uf[x].Parent); // 路径压缩
        return uf[x].Parent;
    }
}

// 合并元素x和y所在的集合,按秩合并
void Union(SetType* uf, int x, int y) {
    int rootX = Find(uf, x);
    int rootY = Find(uf, y);
    if (rootX != rootY) {
        if (uf[rootX].Parent < uf[rootY].Parent) { // rootX树更高
            uf[rootX].Parent += uf[rootY].Parent; // 秩相加
            uf[rootY].Parent = rootX; // rootY挂在rootX下
        } else { // rootY树更高或一样高
            uf[rootY].Parent += uf[rootX].Parent; // 秩相加
            uf[rootX].Parent = rootY; // rootX挂在rootY下
        }
    }
}

// 判断元素x和y是否在同一集合中
int IsConnected(SetType* uf, int x, int y) {
    return Find(uf, x) == Find(uf, y);
}

// 释放并查集内存
void DestroyUnionFind(SetType* uf) {
    free(uf);
}

// 显示并查集的父节点数组
void PrintUnionFind(SetType* uf, int size) {
    printf("Element: ");
    int i;
    for (i = 0; i < size; i++) {
        printf("%d ", uf[i].Data);
    }
    printf("\nParent:  ");
    int j;
    for (j = 0; j < size; j++) {
        printf("%d ", uf[j].Parent);
    }
    printf("\n");
}

int main() {
    int elements[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    int size = sizeof(elements) / sizeof(elements[0]);
    SetType* uf = CreatUnionFind(elements, size);

    // 显示初始状态
    printf("Initial state:\n");
    PrintUnionFind(uf, size);

    // 合并集合
    Union(uf, 1, 2);
    Union(uf, 2, 3);
    Union(uf, 4, 5);
    Union(uf, 6, 7);
    Union(uf, 7, 8);

    // 显示合并后的状态
    printf("\nAfter union operations:\n");
    PrintUnionFind(uf, size);

    // 检查集合
    printf("\nCheck connections:\n");
    printf("1 and 3 connected: %d\n", IsConnected(uf, 1, 3)); // 输出: 1
    printf("1 and 4 connected: %d\n", IsConnected(uf, 1, 4)); // 输出: 0
    printf("6 and 8 connected: %d\n", IsConnected(uf, 6, 8)); // 输出: 1
    printf("5 and 9 connected: %d\n", IsConnected(uf, 5, 9)); // 输出: 0

    // 释放内存
    DestroyUnionFind(uf);
    return 0;
}

 

  • 19
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值