我们经常说的哈希到底是值什么呢?其实就是杂凑的意思 。那么为什么要杂凑呢?这就要了解一下 hash 出现的原因了 。
为什么会出现哈希表?
我们知道在查找数据时,理想的状况是希望一次就能找到我们查找的数据,这就要求所查数据和它的存储位置(可以理解为数组下标)之间有一种关系,可以跟据查询数据直接得到位置,这就是一个映射关系啊,也就是我们常说的函数,那么这个函数我们就称为 hash 函数,按这个思想建立的表就是哈希表 。
备注:我们的 hash 函数的定义(构建)是多种多样的,随意的,只要能够满足我们所希望的那样就行,也就是所查数据和数组下标一一对应,但是我们知道,数组的长度(哈希表的长度)毕竟有限,所以在数据量变大的情况下,必然会出现多个数据对应一个数组索引的情况,也就是发生了冲突 。
综上所述,我们可以这样描述哈希表,很据设定的哈希函数 H(key) 和处理冲突的方法将一组关键字映射到一个有限的地址集(区间)上,并以关键字在地址集中的“像”作为记录在表中的存储位置,这种表称为哈希表,这一映射过程称为哈希造表或散列,所得存储位置称为哈希地址或散列地址 。
使用哈希查找有两个步骤:
使用哈希函数将被查找的键转换为数组的索引 。在理想的情况下,不同的键会被转换为不同的索引值,但是在有些情况下我们需要处理多个键被哈希到同一个索引值的情况 。所以哈希查找的第二个步骤就是处理冲突
处理哈希碰撞冲突 。本文后面会介绍 。
哈希表是一个在时间和空间上做出权衡的经典例子 。如果没有内存限制,那么可以直接将键作为数组的索引 。那么所有的查找时间复杂度为O(1);如果没有时间限制,那么我们可以使用无序数组并进行顺序查找,这样只需要很少的内存 。哈希表使用了适度的时间和空间来在这两个极端之间找到了平衡 。只需要调整哈希函数算法即可在时间和空间上做出取舍 。
在Hash表中,记录在表中的位置和其关键字之间存在着一种确定的关系 。这样我们就能预先知道所查关键字在表中的位置,从而直接通过下标找到记录 。
1) 哈希 ( Hash ) 函数是一个映象,即: 将关键字的集合映射到某个地址集合上,它的设置很灵活,只要这个地址集合的大小不超出允许范围即可;
2) 由于哈希函数是一个压缩映象,因此,在一般情况下,很容易产生“冲突”现象,即: key1!=key2,而 f (key1) = f(key2) 。
3) 只能尽量减少冲突而不能完全避免冲突,这是因为通常关键字集合比较大,其元素包括所有可能的关键字, 而地址集合的元素仅为哈希表中的地址值
在构造这种特殊的“查找表” 时,除了需要选择一个“好”(尽可能少产生冲突)的哈希函数之外;还需要找到一 种“处理冲突” 的方法 。
所以重点就落在了如何构建一个比较好的 hash 函数,可以尽可能的为不同的键求出不同的 hash 值来 。
常用的构造hash函数的方法有很多。。。
我这里只说一种,除留余数法:取关键字被某个不大于哈希表长m 的数p 除后所得余数为哈希地址 。
H(key) = key mod p (p<= m) 一般 p 取最接近表长的素数
处理冲突的方法:
1 开放地址法
H = ( H(key) + d ) mod m
d 有三种取法:
(1)d = 1 , 2 , 3 , 4 , 5 …. 称线性探测再散列
(2)d = 1^2 , -1^2 , 2^2 , -2^2 …. 称二次探测再散列
(3)d = 伪随机数序列 称伪随机探测再散列
简单来说,就是这个位置有值了,那我就去下一个位置,如果还有,那就再下一个 。
2 再哈希法
若是冲突出现,则换一个哈希函数,重新计算哈希值,依次类推 。
3 链地址法
将所有的位置一样的记录存储在同一张线性表中。设立一个指针型向量 China ChinaHash[m] m 是表长 。其每个分量的初始值都是空指针 。凡哈希地址为 i 的记录都插入到头指针为 ChinaHash[i] 的链表中 。
插入位置可以是表头,表位,中间,以保证是有序链表 。
4 建立一个公共的溢出区
假设哈希函数的值域为 [0,m-1],则设向量 HashTable[0,m-1] 为基本表,再建一长溢出表 OverTable[0,v] 一旦发生冲突,都填入溢出表 。