哈希表
哈希表是一个很重要的数据结构,是一个使用散列函数储存数据的数组。
首先,要区分一个概念,哈希表是一个数组,而日常听人解释说:根据一个键值(key)找到一个值(value),这个东西是映射函数,也叫散列函数,哈希表就是使用这个函数将两个值并存起来的一个数据结构,本质其实是一个数组,不是两个数组。
存储值的步骤如下:
- 获得值x,根据散列函数f(x)计算出一个值k。
- 然后将x存在地址k处(如果是数组的话为a[k]=x)。.
散列函数可以是根据数据的不同,场合的不同进行改变,按应用场合来制定,可以是比较简单的分类如:fx=x mod 13,也可以是超级复杂的。
举个例子,这里有一个散列函数:fx=x mod 13,然后存一个序列: 15 13 14 37。
经过计算:
哈希表就会是以下情况:
在编写程序实现功能时,一直会有一个矛盾,就是时间和空间上的矛盾,大多数情况下是两者取其一。因此大佬们在编写程序时会对这两个方面进行权衡,因此在设计存储数据的数据结构时,可以使用值(value)作为地址直接进行存储,在无视空间大小的前提下,以达到极端的节省时间;也可以无序的存储,顺序查找,在无视时间的前提下,以达到较极端的节省空间。而哈希表在这两者之间做出了权衡,它会根据散列函数来调整时间和空间。
这个说法就很微妙,理论上来说哈希表在查找元素时的时间复杂度应该是O(1),应该是不会存在降低的可能,但是这个情况是一种理想情况,与物理的理想模型类似,通常哈希函数不可能达到O(1)的复杂度,但是哈希函数在字符串哈希的线性的搜索中确实是非常快的,并且在某种程度上是稳定的,这里只是简单提一下,不深入探讨。
但是上面的例子没有考虑到重复的情况。例如上述情况下我们存入的序列是:15 13 14 37 26。这个就很有意思了,因为
两个应该是会存到同一个地址里的,这明显会出现冲突,这种情况就是哈希冲突。
哈希冲突
一般而言,再好的散列函数都不能避免上述的重复情况,因此为了防止潜在的危险,必须要解决哈希冲突。
1.开放地址法
也称再散列法,意思是对地址进行再次散列,基本操作为:假设地址k出现冲突,就以k为基础,产生另一个地址k1,k2,k3……直到没有冲突为止,此时将值(value)存在地址kn中。通项公式:
一般而言有三种再散列方式:
- 线性探测再散列:
上述公式的di=1,2,3……m-1,意思是发现冲突会顺序查看表的下一个单元,直到找到空单元或者查完表。
例如,在序列15 13 14 37 26发生冲突时,26是与13冲突,因此进行计算,当di=1时,与14冲突;当di=2时,与15冲突;当di=3时,出现空位,存入地址3位置。
如果出现了16等应该存在3位置的值(value)时,同样处理。
- 二次探测再散列:
同理:di=12,-12,22……,寻找一个合法的空位并存入。
- 伪随机探测再散列:di=伪随机的数列。
2.再哈希法
我们不难看出哈希冲突与散列函数有关,因为散列函数设置的不当导致了两个值(value)产生了同一个键(key),假设这个散列函数是
在当前数据就不会出现哈希冲突。但是一旦加入10,即15 13 14 37 26 10,这也出现了哈希冲突,但是只加入10去掉26,即15 13 14 37 10,使用散列函数
就不会出现哈希冲突。这个就很有说法了,那我们是不是还要绞尽脑汁想出一个哈希函数来保证哈希表能存下15 13 14 37 26 10呢?
答案是没有必要,我们可以同时使用这两个散列函数来消除哈希冲突,即一旦第一个散列函数出现冲突时,使用第二个散列函数来重新计算一下,就会大大降低哈希冲突的几率。
严谨一点讲就是:创建多个哈希函数
当fikey出现冲突时,再计算fi+1key……直到冲突不产生为止,然后选取地址储存。这种方法不容易产生聚集(多个数据堆在相邻的地址),但是增加了存取时计算的时间。
3.链地址法
也叫拉链法,这个方法解决哈希冲突的原理类似于分类,即:将产生冲突的数据存在一个单链表里,图示是比较好理解的。
例如之前的数据,13 14 15 37 26
然后查找时就遍历单链表知道找到为止,但是这个方法必须建立在良好的散列函数的基础上,即散列的值尽可能的均匀分布,不然就可能出现聚集的情况,并大大增加存取时间。
4.建立一个公共溢出区
大致意思是建立一个溢出区,将发生冲突的放入溢出区,这里不会细讲。
哈希表的基础和哈希冲突的心得暂时到这里,关于哈希的其他知识可能会在之后补全。