哈希表的本质就是 K-V 结构,V 可以是链表、数组、数组链表等形式,需要灵活掌握这些数据结构,在代码实现,才能真正的掌握。
1、哈希表
理想情况是不希望经过任何比较,一次存取,便可以直接定位,这是需要一种映射关系,我们把这个映射关系就叫为哈希函数。
哈希表在对大数据的处理查找上,有明显优势。
若数据有序,--->我们一般会想到二分查找;但是哈希查找更加犀利;
针对哈希查找,我们主要学习:(1)
、如何构造哈希函数;(2)、怎么解决冲突问题。
2、哈希函数
这个哈希函数的构造方法比较多:(1)、直接定址法,(2)、数字分析法,(3)、平方取中法,(4)、折叠法,
(5)、除留余数法,(6)、随机数法;
简单说一下,直接定址法,用的少,且需要空间过大;
以上的哈希函数,最常用的是除留余数法;什么是除留余数法?
在哈希表中,总空间大小为m,选一个 p,p 满足两个条件:
(1)、p <= m(越接近 m 越好);
(2)、p 必须是质数;则哈希函数为:
hash(key) = kep % p; (p<=m;)
p 为什么一定要选质数呢?
经过数学的理论推导,p 为质数时,可以保证模之后概率均等,均匀分布;p 要不是质数,则不满足随机、均匀这些条件,就不能保证每个数字均等存储。
3、解决冲突
(1)、线性探测法
线性探测法存储,此时存放的位置如下图,因为在下标为 1 处已经存放数字了,此时其后的数据也要存放到这个位置,产生冲突;按照线性探测法的解决方案:有冲突一直往后探测,直到有空间可以放数据时,就存储在那个位置。
模型图:
(2)、二次探测法
二次探测法存储,此时存放的位置如下图,因为在下标为 1 处已经存放数字了,此时其后的数据也要存放到这个位置,产生冲突;按照二次探测法的解决方案:有冲突先向左探测,若没有空间,再向右探测,来回左右探测,直到有空间可以放数据时,就存储在那个位置。
模型图:
以上的两种解决冲突的方案不是很好,缺点:定位准确度不高,查找起来效率较低。
(3)、双散列法
需要两个哈希函数,第一个元素直接就是记哈希表的下标,第二个记到达该数字的移位量,从而达到直接定位。
模型图:
(4)、链地址法
哈希表中存放的是指针(指向结点的指针),在冲突处,通过指针连接起来。
模型图:
关于链表的连接问题:
头插----->效率比较高;
尾插----->效率相对来说比较低,(因为的一个一个的查找最后一个结点)。
解决尾插效率低的方案:以空间换时间,就是在哈希表中存放的是一个结构体类型,成员如下:
1
4、除留余数法+开链法(重点实现)
此次全部代码均用 C 语言实现,与 C++ 不牵扯。
我把函数声明和定义放到了一个 .h 文件中,只是为了方便实现,本应该分开存放;
除留余数法的重点是:hash() 函数;开链法的重点是:链表的操作。
哈希表上应有的操作函数(其实主要是链表的操作)
1
(1)、数据前插
1
(2)、数据展示
1
(3)、数据后插
1
(4)、查找函数
1
(5)、删除函数
1
5、完整代码
(1)、完整代码
1
(2)、测试代码
1
(3)、测试结果
推荐阅读:
从零开始学习数据结构-->入门篇
从零开始学习数据结构-->链表
从零开始学习数据结构-->线性表
从零开始学习数据结构-->栈
从零开始学习数据结构-->队列
从零开始学习数据结构-->矩阵+串
认真的人 自带光芒