我见过有人说python中的set
个对象有O(1)成员资格检查。它们如何在内部实施以实现这一目标?它使用什么样的数据结构?该实施还有哪些其他含义?
这里的每个答案都很有启发性,但我只能接受一个答案,所以我会用最接近我原来问题的答案。谢谢大家的信息!
答案 0 :(得分:114)
根据this thread:
事实上,CPython的集合实现为字典之类的东西 带有虚拟值(键是集合的成员),有些 利用这种缺乏价值的优化
所以基本上set
使用哈希表作为其底层数据结构。这解释了O(1)成员资格检查,因为在哈希表中查找项目平均是O(1)操作。
如果您如此倾向,您甚至可以浏览CPython source code for set,根据Achim Domma,{{3}}主要是dict
实施中的剪切和粘贴。
答案 1 :(得分:73)
当人们说集合有O(1)成员资格检查时,他们正在谈论平均案例。在最差情况下(当所有散列值冲突时),成员资格检查为O(n)。请参阅Python wiki on time complexity。
Wikipedia article表示未调整大小的哈希表的最佳情况时间复杂度为O(1 + k/n)
。此结果不直接适用于Python集,因为Python集使用调整大小的哈希表。
维基百科上的文章进一步说明,对于平均值情况,并假设一个简单的统一散列函数,时间复杂度为O(1/(1-k/n))
,其中k/n
可以是由常数c<1
限制。
Big-O仅指n→∞的渐近行为。 由于k / n可以由常数限制,c <1,独立于n ,
O(1/(1-k/n))
不大于O(1/(1-c))
,相当于O(constant)
= O(1)
。
因此,假设统一简单散列,在平均值上,Python集的成员资格检查为O(1)
。
答案 2 :(得分:13)
我认为这是一个常见错误,set
查找(或哈希表)不是O(1)。
from the Wikipedia
在最简单的模型中,哈希函数完全未指定,表格不会调整大小。为了最好地选择散列函数,具有开放寻址的大小为n的表没有冲突并且最多可容纳n个元素,单个比较用于成功查找,而具有链接和k键的大小为n的表具有最小最大值(0,kn)碰撞和 O(1 + k / n)比较用于查找。对于哈希函数的最差选择,每次插入都会导致冲突,哈希表会退化为线性搜索,每次插入时进行Ω(k)分摊比较,最多进行k次比较,以便成功查找。
答案 3 :(得分:13)
我们都可以轻松访问the source,其中set_lookkey()
之前的评论显示:
/* set object implementation
Written and maintained by Raymond D. Hettinger <python@rcn.com>
Derived from Lib/sets.py and Objects/dictobject.c.
The basic lookup function used by all operations.
This is based on Algorithm D from Knuth Vol. 3, Sec. 6.4.
The initial probe index is computed as hash mod the table size.
Subsequent probe indices are computed as explained in Objects/dictobject.c.
To improve cache locality, each probe inspects a series of consecutive
nearby entries before moving on to probes elsewhere in memory. This leaves
us with a hybrid of linear probing and open addressing. The linear probing
reduces the cost of hash collisions because consecutive memory accesses
tend to be much cheaper than scattered probes. After LINEAR_PROBES steps,
we then use open addressing with the upper bits from the hash value. This
helps break-up long chains of collisions.
All arithmetic on hash should ignore overflow.
Unlike the dictionary implementation, the lookkey function can return
NULL if the rich comparison returns an error.
*/
...
#ifndef LINEAR_PROBES
#define LINEAR_PROBES 9
#endif
/* This must be >= 1 */
#define PERTURB_SHIFT 5
static setentry *
set_lookkey(PySetObject *so, PyObject *key, Py_hash_t hash)
{
...
答案 4 :(得分:2)
为了更加强调set's
和dict's
之间的差异,这里是setobject.c
评论部分的摘录,其中阐明了集合与字母的主要区别。
来自github 的集合的用例与查找的字典有很大不同 密钥更有可能存在。相比之下,集合主要是 关于成员资格测试,其中不知道元素的存在 预先。因此,集合实现需要针对两者进行优化 发现和未发现的案例。
来源
答案 5 :(得分:1)
python中的设置在内部使用哈希表。让我们首先谈谈哈希表。 假设有一些要存储在哈希表中的元素,并且在哈希表中有31个位置可以存储。令元素为:2.83、8.23、9.38、10.23、25.58、0.42、5.37、28.10、32.14、7.31。当您要使用哈希表时,首先要确定哈希表中将这些元素存储在其中的索引。模数函数是确定这些索引的一种常用方法,因此,让我们说我们一次取一个元素,将其乘以100,然后对31取模。重要的是,对元素的每个此类运算都将产生一个唯一的数字,即除非允许链接,否则哈希表中的条目只能存储一个元素。这样,每个元素将被存储在由模运算获得的索引所控制的位置。现在,如果您要使用该哈希表在实质上存储元素的集合中搜索元素,则您将获得O(1)时间的元素,因为该元素的索引是使用恒定时间的模运算来计算的。 为了说明模运算,我还要写一些代码:
piles = [2.83, 8.23, 9.38, 10.23, 25.58, 0.42, 5.37, 28.10, 32.14, 7.31]
def hash_function(x):
return int(x*100 % 31)
[hash_function(pile) for pile in piles]
输出:[4、17、8、0、16、11、10、20、21、18]