数据结构—散列表(哈希表)

一、数组和链表的弊端

1、对于所有可能的关键字集合U,如果用数组来存储U中的关键字,则需要分配的存储单元至少等于U中关键字的个数。这种方法的弊端很明显,实际中U可能很大,计算机的内存毕竟有限,用数组去存储这么大一个集合不切实际

2、对于链表来说也是同样的道理,对每个关键字需要分配一个节点来存储,这样空间上就会受到限制

3、还有,实际出现的关键字集合K有可能远远小于U,这样用数组链表来存储整个全集U,就会有大部分元素(节点)没有被使用到,那么这些空间就是严重的浪费

4、最后,数组和链表在查找插入删除操作上各有不足。

               比如,数组的查找效率非常高,时间复杂度为O(1),而它的插入和删除操作就非常麻烦

               链表刚好相反,插入删除方便,查找则需要O(N)的时间复杂度

 

二、散列表的思想

        把全集U的每一个关键字通过一个散列函数(Hash Function)h映射到表T{0, 1, ... , m-1}的某个下标,表T{0, 1, ... , m-1}可以简单的理解为有m个元素的数组               h:U—>{0, 1, ... , m-1} 

                                            

我们用散列表的一个关键目的就是为了节省存储空间,所以一般情况下m要远远小于|U|。

这里就有一个问题,两个不同的关键字通过h计算可能会得到同一个哈希值(函数值),例如上图的k2 != k5,但h(k2) == h(k5),我们称这种情形为冲突或碰撞(collision)。

我们选择散列函数h的时候,要尽可能地减少冲突次数。要做到这一点,就需要散列函数h尽可能的"随机",即函数值分布尽可能均匀,不要集中在某个区间。

同时也要明白,把一个大集合U硬塞到一个小数组T{0, 1, ... , m-1},是不可能避免冲突的。

那么,解决冲突主要有两种方法,一是链接法,二是开放地址法,这里着重介绍第一种。

 

三、通过链接法解决冲突

在链接法中,我们把映射到同一下标的关键字存储在一个链表中,数组中保存指向该链表的指针(有可能为空)。

                   

装载因子α=N / m,N为所有关键字的个数,m为数组的元素个数。α表示的是每个链表中平均存储的元素个数。

显然,链接法的最坏情形是所有关键字都映射到一个下标上,这样所有关键字都被存储到一个链表中,其性能退化为和链表,还得加上散列函数h的开销。

所以,链接法的性能依赖于散列函数的选择,我们要选择尽量使关键字均匀分布在数组的各个元素上的散列函数。

 

四、散列函数

1、除法散列法

     通过取k除以m的余数,将关键字k映射到含有m个元素的数组的一个下标上。

                    h(k) = k % m

     一个不太接近2的整数幂的素数,通常是m的一个较好的选择

2、乘法散列法

     构造散列函数的乘法散列法包含两个步骤:

     ①用关键字k乘上常数A(0<A<1),并提取kA的小数部分

     ②用m乘以这个小数部分,再向下取整

                    h(k) = floor(kA % 1)

        A = (√5 -1) / 2 ≈ 0.618是个比较理想的值

开放地址法是

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章