散列表——Hash Table

本文探讨了散列表作为一种高效数据结构的优势,特别是在读取和插入操作方面,相较于数组和链表,散列表提供了更快的查找速度。通过实例解释了散列表的工作原理,包括散列函数的作用和冲突解决方案,并讨论了装填因子的概念及其在散列表扩容中的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前文对数组和链表进行了分析,介绍了各自在插入、遍历、删除方面的优势和劣势,时间复杂度如下表示。

 数组链表
读取O(1)O(n)
插入O(n)O(1)
删除O(n)O(1)

既然数组和链表都存在一定的不足,那么有没有什么方法能在快速读取的同时,可以快速的将数据插入呢?答案就是散列表!


场景

假设你开了一家超市,但还没有采购收银机器,于是在顾客结账的时候只能到价格列表上一个个查找。如果这个列表有序且商品数量不多,你只需要进行O(nlogn)次查找即可。可一旦顾客买了十几甚至几十种商品时,这就比较费劲了。一旦慢下来,后面那么长的队伍还在等着你呢,那么有没有什么办法能更快的做查询呢?一个好办法就是找一个熟记价格的营业员,对n件商品只需O(n)时间即可。那么问题又来了,怎么找到这么优秀的雇员呢?!接下来就要看散列表出场了!


定义&原理

定义:对于给定输入,能稳定的给出相应的输出(不能每次都不一样)。

那散列表又是如何存储的呢?为了达到理想的O(1)查找时间,能满足这个要求的非数组莫属了。但是数组是按照index进行索引,那又如何将输入的数字或字符串(如“book”、“mouse”等)转换为索引呢,即f(input)=index,且一个input对应一个唯一的index。这就是散列函数所要干的事情,有关的见SHA算法。

冲突

但是对于再优秀的散列函数,也可能存在有缺陷的地方,即存在多个给定输入,产生同一个输出的情况。之前MD5、SHA算法都已先后被攻破(新闻)。

缺陷归缺陷,当出现冲突的时候,是不是又可以用上其他方法来解决呢?常见的冲突解决方法有开放定址法,链地址法,建立公共溢出区等。实际的哈希表实现中,使用最多的是链地址法,如下。

在这种情况下的时间复杂度如下(差了可不是一点半点):

 平均情况最坏情况
读取O(1)O(n)
插入O(1)O(n)
删除O(1)O(n)

 

装填因子

因哈希表的存储介质是数组,而一个固定了大小的数组不可能存储无限多的数据,因此需要考虑什么时候对数组进行扩容。装填因子干的就是这个事情,告诉散列表什么时候预先进行扩容。


实际应用

DNS解析:域名->IP转换(上亿个网页的域名解析,不用散列表,有其他更高效的方法吗?)

数据缓存——对于要频繁访问的数据,从磁盘载入到内存的成本高昂,何尝不直接就放在内存里呢?


总结

散列表和数组、链表一样,都有其优劣之处。各取其长,来解决更大的问题。

### Python 中散列表的实现与使用 Python 的核心数据结构之一——字典(`dict`),其底层基于散列表Hash Table)实现。以下是关于 Python 散列表的实现原理及其使用的详细介绍。 #### 1. 散列表的基本概念 散列表一种通过哈希函数将键映射到特定索引位置的数据结构[^2]。理想情况下,每个键都能被均匀分布到不同的桶中,从而使得查找、插入删除操作的时间复杂度接近 O(1)[^3]。然而,在实际应用中,由于可能存在多个键映射到相同的桶(即发生冲突),因此需要设计合理的解决策略。 #### 2. Python 字典的内部实现 在 CPython 实现中,字典由一系列 `PyDictEntry` 结构体组成,每个条目包含三个字段:缓存的键哈希值 (`me_hash`)、指向键的对象指针 (`me_key`) 指向值的对象指针 (`me_value`)[^1]。具体如下: ```c typedef struct { long me_hash; /* 缓存的键哈希值 */ PyObject *me_key; /* 键对象 */ PyObject *me_value; /* 值对象 */ } PyDictEntry; ``` CPython 使用开放寻址法 (Open Addressing) 处理冲突,这意味着如果某个桶已经被占用,则会寻找下一个可用的位置。为了提高性能,CPython 还引入了一些优化措施,例如动态调整表大小以及预分配额外空间以减少重新分配频率[^1]。 #### 3. 散列表的操作方法 尽管开发者通常无需关心底层细节即可高效地使用字典,但理解其实现有助于更好地利用该数据结构。下面列举了几种常见的字典操作及其特点: - **创建字典** 可以通过花括号 `{}` 或者 `dict()` 构造器来初始化一个新字典。 ```python my_dict = {'name': 'Alice', 'age': 25} another_dict = dict(name='Bob', age=30) ``` - **访问元素** 利用方括号语法获取指定键对应的值;若不存在则抛出 KeyError 异常。也可以配合 get 方法设置默认返回值。 ```python value = my_dict['name'] # 返回 Alice default_val = my_dict.get('height', 0) # 若无 height 键,默认返回 0 ``` - **修改与新增** 同样采用赋值语句完成更新已有项或者添加新的 key-value 对象。 ```python my_dict['age'] += 1 # 将年龄加一 my_dict['city'] = 'New York' # 新增城市信息 ``` - **删除操作** 支持 del 关键字直接移除某一项,pop 方法允许同时取出并销毁目标实体,clear 函数清空整个容器内容。 ```python del my_dict['age'] removed_item = my_dict.pop('city') my_dict.clear() ``` #### 4. 时间复杂度分析 理论上讲,得益于优秀的哈希算法设计加上恰当的扩容机制保障了绝大多数场景下单步查询效率维持恒定级别 O(1)[^3]。不过一旦遇到极端情况比如大量重复模式输入导致频繁重组等情况时,最差情形下仍需线性扫描全量项目才能定位所需资源,此时退化至 O(n) 层面[^3]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值