算法与数据结构(十二) 散列(哈希)表的创建与查找(Swift版)

散列表又称为哈希表(Hash Table), 是为了方便查找而生的数据结构。关于散列的表的解释,我想引用维基百科上的解释,如下所示:

散列表Hash table,也叫哈希表是根据(Key)而直接访问在内存存储位置的数据结构。也就是说,它通过计算一个关于键值的函数,将所需查询的数据映射到表中一个位置来访问记录,这加快了查找速度。这个映射函数称做散列函数,存放记录的数组称做散列表

散列表的创建就是将Value通过散列函数和处理散列key值冲突的函数来生成一个key, 这个key就是Value的查找映射,我们就可以通过key来访问Value的值。本篇博客我们就来好好的聊一下散列表的实现,当然主要还是构建散列函数还有解决冲突的函数,下方我们先给出散列函数为“除留取余法”和处理冲突的线性探测发的原理图,然后再给出面向对象的实现,最后在给出相应的代码实现。

 

一、散列表创建原理

本部分我们将以一系列的示意图来看一下如何来创建一个哈希表,我们就将下方截图中的数列中的数据来存储到哈希表中。在下方的实例中,我们采用除留取余法来创建value的映射key, 如果产生冲突,就采用线性探测法来处理key的冲突。下方就是我们要构建哈希表的数据以及所需的散列函数和处理冲突的函数。

  

1.散列表的构建

接下来我们就要将上述元素插入到我们的散列表中,下方是对每个步骤的描述:

  • 将62插入到散列表中,通过取余求出的key为7。散列表中7的位置没有存入东西,所以62的key为7。同理依次插入88,58.
  • 然后计算47的key值,通过除留取余法,得到47%11 = 3, 发现3已经存储了58,也就是说与58的key冲突了,于是乎进行一轮冲突的解决key = key + 1 = 4。4的位置没有存入值,所以讲47存入4的位置。
  • 按上述两个步骤,将剩下的值插入到HashTable中即可,下方是完整的步骤。

 

  

 

2、散列表的查找

散列表的查找与散列表元素的插入是非常相似的,也是通过哈希函数以及处理冲突的方法来完成的。我们以在创建好的查找表中查找93为例,首先通过创建哈希表时使用的哈希函数来计算93对应的key, key = 93 % 11 = 5。然看key = 5所映射的数据,我们发现HashTable[5] = 37, 而不是93。那么说明93在插入的时候遇到了冲突,然后通过冲突的处理后才插入到HashTable中的。所以需要通过冲突处理函数找到93正确的key。进行一轮冲突处理,即为key = key + 1 = 5 + 1 = 6。我们发现hashTable[6] = 93, 于是乎我们找到93的下标是6。

上述这种查找方式,与我们之前聊的顺序查找、二分查找等等效率要高的多,不过散列函数和处理冲突的函数的选择在提高查找效率方面是至关重要的。查找顺序如下:

  

 

二、散列表的具体代码实现

聊完原理,接下来就到了我们代码实现的时刻了。下方我们会使用面向对象语言Swift来实现我们的HashTable。因为散列表由于散列函数与处理冲突函数的不同可以分为多种类型,但是每种类型之前的区别除了散列函数和冲突函数不同之外,其他的还是完全一致的,因为我们使用的是面向对象语言,所以我们可以将相同的放在父类中实现,而不同的则由子类提供。下方代码的实现,主要也是这个思路。

 

1.抽象HashTable的父类

下方这个HashTable的类,就是我们抽象的散列表的父类。该类所扮演的角色类似于接口的角色,定义了对外的调用方式,并且给出了散列表共用方法的实现。其实下方这个类与C++中的虚基类极为相似。我们采用Swift中的字典来充当我们的HashTable, 字典的Value就是我们要插入的值,而字典的key就是通过插入的值Value生成的并处理完冲突的key。

下方代码中的hashTable字典中存储的就是我们的散列表。计算属性count中存储的就是散列表的大小。而list数组中存储的就是要插入到散列表中的数据。每个方法所表达的功能请看下方截图中的注释,如下所示。

HashTable方法中,有两个方法需要注意一下。一个是hashFunction()方法,另一个就是conflictMethod()方法。这两个方法需要在散列表的子类中进行重写的,hashFunction()方法用来提供散列函数,而conflictMethod()则用来提供处理key值冲突的方法。因为散列函数有许多种,而处理冲突的方法也有许多种,所以我们可以将其放到具体的子类中去实现。不同类型的散列表中这两个方法给出具体的散列函数和处理冲突的方法。

  

 

2.除留取余法与线性探测

接下来我们要给出散列函数为“除留取余法”以及使用线性探测的方式来处理冲突的散列表。该散列表我们命名为HashTableWithMod, 当然该类是继承自上面的散列表父类HashTable的,并且重写了hashFunction()和conflictMethod()方法。在相应的方法中给出了相应的解决方案。

  

 

3.直接定址法与随机数探测法

与上面的HashTableWithMod类类似,我们还可以继承自HashTable类给出哈希函数为直接定址法,以及使用随机数探测法来处理冲突的散列表。当然也需要将hashFunction()和conflictMethod()这两个方法进行重写了。具体代码如下所示:

  

 当然,上面只给出了部分哈希函数的实现和处理冲突的方式,其余的在本篇博客中就不做过多赘述了,请自行Google。

 

三、测试用例

接下来又到了我们测试的时刻了,上方我们依然采用“面向接口”编程的思想来实现的,所以我们的测试用例可以使用一个。将不同类型的HashTable的对象即可,下方就是我们的测试用例。

  

 

下方是对除留取余法+线性探测的哈希表进行的的测试结果。上面是使用该方法创建哈希表的详细步骤,然后将创建好的hashTable进行了输出,最后给出了查找的结果。如下所示:

  

上方是构建哈希表的整个过程,下方则是将创建好的HashTable进行输出,并且给出35的查询结果:

   

今天的博客就先到这,更详细的代码实现请移步github分享链接,如下所示。

github分享链接: https://github.com/lizelu/DataStruct-Swift/tree/master/HashTableSearch

posted @ 2016-12-01 10:18  青玉伏案  阅读(5658)  评论(4编辑  收藏  举报