数据结构:集合

概述

  • 在数据结构中,集合(Set) 是一种无序、不重复 的元素集合,核心特性是"去重"和"快速判断元素是否存在",底层通常基于哈希表(散列表)红黑树实现(哈希表实现的集合称为"哈希集合",红黑树实现的称为"有序集合")。

  • 集合与我们之前讲的字典(Dictionary) 关系密切:集合本质是"只存键、不存值"的字典,利用字典"键唯一"的特性实现去重,同时继承了哈希表的高效查找能力。

  • 资料:https://pan.quark.cn/s/43d906ddfa1b

一、集合的核心特点

  1. 元素唯一:集合中不会有重复元素,重复插入的元素会被自动忽略;
  2. 无序性:元素的存储顺序与插入顺序无关(哈希集合),有序集合(如 TreeSet)会按元素大小排序;
  3. 高效操作:判断元素是否存在、插入、删除的平均时间复杂度为 O(1)(哈希集合);
  4. 元素不可变:集合中的元素必须是不可变类型(如整数、字符串、元组),不能存储列表、字典等可变类型(否则无法保证哈希值稳定)。

二、集合的基本操作

集合的操作围绕"元素的增删查去重"展开,核心操作如下:

操作 描述 时间复杂度(哈希集合)
add(element) 向集合中添加元素,重复元素不生效 O(1)(平均)
remove(element) 删除集合中的元素,元素不存在抛异常 O(1)(平均)
discard(element) 删除集合中的元素,元素不存在不报错 O(1)(平均)
contains(element) 判断元素是否在集合中 O(1)(平均)
size() 返回集合中元素的个数 O(1)
clear() 清空集合所有元素 O(n)
union(set2) 求两个集合的并集(A ∪ B) O(n + m)
intersection(set2) 求两个集合的交集(A ∩ B) O(min(n, m))
difference(set2) 求两个集合的差集(A - B) O(n)
is_subset(set2) 判断当前集合是否是 set2 的子集 O(n)

三、集合的底层实现

集合的底层实现与字典高度一致(以哈希集合为例):

  1. 底层使用哈希表(桶数组) 存储元素;
  2. 插入元素时,先通过哈希函数计算元素的索引,再存入对应桶中;
  3. 利用哈希表的冲突解决策略(如链地址法)处理哈希冲突;
  4. 由于集合"只存元素不存值",本质是将元素作为"键"存入哈希表,值字段可忽略(如 Python 的 set 底层就是无值的 dict)。

对比字典与集合的底层逻辑:

数据结构 哈希表存储内容 核心特性
字典 键值对(Key-Value) 键唯一、通过键查值
集合 仅键(Key) 元素唯一、快速判重

四、集合的实现(Python示例)

Python 中的 set 是哈希集合的经典实现,直接使用即可。以下是自定义简易哈希集合,帮助理解底层逻辑:

python 复制代码
class HashSet:
    def __init__(self, capacity=8):
        self.capacity = capacity  # 桶数组大小
        self.buckets = [None] * self.capacity  # 桶数组(每个桶存储链表)
        self.size = 0  # 元素个数
        self.load_factor = 0.75  # 负载因子阈值

    class Node:
        """集合节点(链表节点)"""
        def __init__(self, value):
            self.value = value
            self.next = None

    def _hash(self, value):
        """哈希函数:计算元素的桶索引"""
        return hash(value) % self.capacity

    def _resize(self):
        """扩容:桶数组翻倍,重新哈希所有元素"""
        old_buckets = self.buckets
        self.capacity *= 2
        self.buckets = [None] * self.capacity
        self.size = 0

        # 重新插入旧桶中的所有元素
        for bucket in old_buckets:
            current = bucket
            while current:
                self.add(current.value)
                current = current.next

    def add(self, value):
        """添加元素(去重)"""
        # 检查元素是否已存在
        if self.contains(value):
            return False  # 重复元素,添加失败

        # 检查是否需要扩容
        if self.size / self.capacity >= self.load_factor:
            self._resize()

        index = self._hash(value)
        # 插入链表头部
        new_node = self.Node(value)
        new_node.next = self.buckets[index]
        self.buckets[index] = new_node
        self.size += 1
        return True

    def remove(self, value):
        """删除元素(元素不存在抛异常)"""
        if not self.contains(value):
            raise KeyError(f"元素 {value} 不在集合中")
        self._remove(value)
        return True

    def discard(self, value):
        """删除元素(元素不存在不报错)"""
        if self.contains(value):
            self._remove(value)
            return True
        return False

    def _remove(self, value):
        """内部删除逻辑"""
        index = self._hash(value)
        current = self.buckets[index]
        prev = None

        while current:
            if current.value == value:
                # 调整链表指针
                if prev:
                    prev.next = current.next
                else:
                    self.buckets[index] = current.next
                self.size -= 1
                return
            prev = current
            current = current.next

    def contains(self, value):
        """判断元素是否在集合中"""
        index = self._hash(value)
        current = self.buckets[index]

        while current:
            if current.value == value:
                return True
            current = current.next

        return False

    def size(self):
        """返回元素个数"""
        return self.size

    def clear(self):
        """清空集合"""
        self.buckets = [None] * self.capacity
        self.size = 0

    def union(self, other_set):
        """返回两个集合的并集"""
        result = HashSet()
        # 添加当前集合的所有元素
        for bucket in self.buckets:
            current = bucket
            while current:
                result.add(current.value)
                current = current.next
        # 添加另一个集合的所有元素(自动去重)
        for bucket in other_set.buckets:
            current = bucket
            while current:
                result.add(current.value)
                current = current.next
        return result

    def intersection(self, other_set):
        """返回两个集合的交集"""
        result = HashSet()
        # 遍历较小的集合,提高效率
        if self.size < other_set.size:
            target_set = self
            check_set = other_set
        else:
            target_set = other_set
            check_set = self

        for bucket in target_set.buckets:
            current = bucket
            while current:
                if check_set.contains(current.value):
                    result.add(current.value)
                current = current.next
        return result

    def difference(self, other_set):
        """返回当前集合相对于 other_set 的差集(A - B)"""
        result = HashSet()
        for bucket in self.buckets:
            current = bucket
            while current:
                if not other_set.contains(current.value):
                    result.add(current.value)
                current = current.next
        return result

    def is_subset(self, other_set):
        """判断当前集合是否是 other_set 的子集"""
        for bucket in self.buckets:
            current = bucket
            while current:
                if not other_set.contains(current.value):
                    return False
                current = current.next
        return True

    def __str__(self):
        """打印集合"""
        elements = []
        for bucket in self.buckets:
            current = bucket
            while current:
                elements.append(str(current.value))
                current = current.next
        return "{" + ", ".join(elements) + "}"

# 使用示例
s1 = HashSet()
s1.add(1)
s1.add(2)
s1.add(3)
s1.add(2)  # 重复元素,不生效
print(s1)  # 输出: {1, 2, 3}

s2 = HashSet()
s2.add(3)
s2.add(4)
s2.add(5)

# 并集
print(s1.union(s2))  # 输出: {1, 2, 3, 4, 5}
# 交集
print(s1.intersection(s2))  # 输出: {3}
# 差集
print(s1.difference(s2))  # 输出: {1, 2}
# 子集判断
print(s1.is_subset(s2))  # 输出: False

五、集合的优缺点

优点
  1. 自动去重:无需手动处理重复元素,简化开发;
  2. 高效判重:平均 O(1) 时间复杂度判断元素是否存在,远快于数组、链表;
  3. 集合运算便捷:原生支持并集、交集、差集等数学运算,适合数据筛选;
  4. 实现简单:底层基于哈希表,逻辑与字典高度复用,易理解。
缺点
  1. 无序性 :哈希集合无法保证元素顺序,如需有序需使用 TreeSet(Java)或 sorted() 排序;
  2. 元素不可变:限制了元素类型,无法存储可变对象;
  3. 内存开销大:哈希表的桶数组和链表指针会占用额外空间,空间利用率低于数组;
  4. 最坏性能差:哈希冲突严重时,操作时间复杂度退化为 O(n)(依赖良好的哈希函数和扩容策略)。

六、集合的应用场景

集合的"去重"和"快速判重"特性使其在以下场景中高频使用:

  1. 数据去重:如筛选列表中的唯一元素、处理用户输入去重;
  2. 元素存在性判断:如判断用户是否在黑名单中、关键词是否在敏感词库中;
  3. 集合运算:如筛选两个数据集的共同元素(交集)、合并数据集并去重(并集)、找出两个数据集的差异(差集);
  4. 缓存判重:如爬虫去重(避免重复爬取同一 URL)、任务调度去重(避免重复执行同一任务);
  5. 数学建模:如集合论相关的算法实现、图论中的节点集合操作。

七、集合 vs 字典 vs 数组

数据结构 核心特性 核心优势 适用场景
集合 无序、不重复、快速判重 自动去重、集合运算便捷 去重、判重、数据筛选
字典 键值对映射、键唯一 通过键快速查值 键值对存储、索引查找
数组 有序、可重复、索引访问 随机访问快、空间利用率高 有序数据存储、遍历

八、总结

集合是一种高效的去重数据结构,底层基于哈希表实现,核心优势是"自动去重"和"快速判重"。它与字典同源,本质是"无值字典",继承了哈希表的 O(1) 平均时间复杂度操作。

实际开发中,直接使用编程语言内置集合(如 Python 的 set、Java 的 HashSet、C++ 的 unordered_set)即可,这些实现已优化了哈希函数、冲突解决和扩容策略,性能稳定且支持丰富的集合运算,无需重复造轮子。

相关推荐
阿沁QWQ1 小时前
list模拟实现
数据结构·list
dragoooon343 小时前
[优选算法专题九.链表 ——NO.53~54合并 K 个升序链表、 K 个一组翻转链表]
数据结构·算法·链表
松涛和鸣3 小时前
22、双向链表作业实现与GDB调试实战
c语言·开发语言·网络·数据结构·链表·排序算法
云里雾里!10 小时前
力扣 209. 长度最小的子数组:滑动窗口解法完整解析
数据结构·算法·leetcode
憨憨崽&11 小时前
进击大厂:程序员必须修炼的算法“内功”与思维体系
开发语言·数据结构·算法·链表·贪心算法·线性回归·动态规划
chem411112 小时前
C 语言 函数指针和函数指针数组
c语言·数据结构·算法
liu****12 小时前
八.函数递归
c语言·开发语言·数据结构·c++·算法
客梦12 小时前
数据结构-树结构
数据结构·笔记
老约家的可汗13 小时前
数据结构之栈和队列
数据结构