原理
Python 中的集合(set)是一个无序的不重复元素序列。它们可以用来进行成员测试、去除重复项以及数学上的集合操作,如并集、交集、差集和对称差分。
Python 中的集合(set)是基于哈希表实现的。了解集合的原理,首先需要理解哈希表(Hash Table)的概念。
哈希表
哈希表是一种使用哈希函数组织数据,以支持快速插入和搜索的数据结构。它通过将键值对映射到表中一个位置来访问记录,以加快查找速度。这个映射函数称为哈希函数,存放记录的数组称为哈希表或散列表。
集合工作原理
当你向集合添加一个元素时,Python 会首先计算该元素的哈希值(hash value),这个值决定了该元素在内部数据结构中的存储位置。如果两个不同元素具有相同的哈希值,则会发生冲突;Python 内部通过开放寻址法或链地址法等方式来解决这种冲突。
- 无序性:由于集合中元素位置是根据其哈希值确定,并不依赖于插入顺序,因此 Python 中集合是无序的。
- 唯一性:在任何时候,每个元素都有唯一确定的哈希值(即使存在冲突),所以集合中不会有重复元素。
- 高效性:由于基于哈希实现,大多数情况下(假设没有太多冲突),集合操作如添加、检查成员资格、删除等操作可以接近 O(1) 的时间复杂度完成。
动态调整大小
随着向集合添加更多元素,其内部结构可能变得过满(即装载因子过高)。为了保持操作效率,在达到某个阈值时,Python 会自动增加存储空间,并重新计算所有已存储对象的位置。这个过程称为“重新散列”或“扩容”,虽然它可能导致单次操作时间较长但确保整体上操作效率。
示例说明
考虑以下简单示例:
my_set = set()
print(my_set)
# 添加几个不同类型对象
my_set.add(42)
my_set.add("hello")
my_set.add((1, 2, 3))
print(my_set)
- 当我们逐一添加
42
、"hello"
和(1, 2, 3)
到my_set
集合时:- 对每一个对象调用
hash()
函数获取其 hash 值。 - 使用 hash 值找到在内部数据结构中应该存储该对象信息的位置并保存。
- 对每一个对象调用
- 尽管
(1, 2, 3)
是可迭代类型且包含多项内容,在作为整体被添加进 set 的过程也仅视作单独一个条目进行处理和计算其 hash 值。(注意:只有不可变类型才能被加入到 set 中)
总之,在 Python 中使用集合进行去重和成员测试非常有效率且方便。但要记住背后实现机制——基于高效但复杂度较高且动态调整大小能力强大的“散列表”。
操作
创建集合
创建一个基本的集合可以使用大括号 {}
或者 set()
函数:
# 使用大括号
my_set = {1, 2, 3}
# 使用 set() 函数
my_set = set([1, 2, 3])
注意:空大括号 {}
不是用来创建空集合的,而是用来创建空字典。要创建一个空集合,你必须使用 set()
而不是 {}
。
修改
-
添加元素
-
add(elem)
:向集合中添加单个元素elem
。my_set.add(4)
-
-
更新元素
-
update(*others)
:将其他集合中的元素添加到当前集合中。参数可以是列表、元组、字典等任何可迭代对象。my_set.update([4,5], {6,7})
-
-
删除元素
-
remove(elem)
:从集合中移除元素elem
。如果该元素不存在,则抛出 KeyError。my_set.remove(1)
-
discard(elem)
:从集合中移除元素elem
。如果该元素不存在,不会发生错误。my_set.discard(2)
-
pop()
:随机移除一个元素并返回它。由于 set 是无序的,因此无法确定哪个是“下一个”被删除的项。elem = my_set.pop()
-
clear()
:清空整个集合内容。my_set.clear()
-
查询
-
成员测试:
使用关键字 in 来检查某个值是否存在于 set 中。if 1 in my_set: print("Found!")
-
长度计算:
使用内置函数 len() 来获取 set 的大小(即其中有多少个项)。size = len(my_set)
数学运算
-
并集 (
union
或者操作符 |):
合并两个或多个 sets 的所有唯一项。 -
交叉 (
intersection
或者操作符 &):
找出两个或多个 sets 共有的项。 -
差异 (
difference
或者操作符-
):
找出存在于第一个 set 中但不在其他 sets 中的所有项。 -
对称差异 (
symmetric_difference
或者操作符 ^):
找出只存在于其中一个 set 中而不同时存在于两个 sets 中的所有项。
每种数学运算都提供了相应地就地执行版本方法,如:
a.intersection_update(b) # 将 a 更新为 a 和 b 的交叉结果.
这些方法会直接修改原始数据而不返回新值。
代码示例:
# 定义两个示例 set
a = {1, 2, 3}
b = {3, 4, 5}
# 并运算
print(a | b) # 输出 {1, 2, 3, 4, 5}
# 取交运算
print(a & b) # 输出 {3}
# 差运算 (a 中有而 b 中没有)
print(a - b) # 输出 {1, 2}
# 对称差分 (在 a 或 b 中但不同时在 a 和 b 中)
print(a ^ b) # 输出 {1, 2 ,4 ,5}