算法與數據結構(十二) 散列(哈希)表的建立與查找(Swift版)

散列表又稱爲哈希表(Hash Table), 是爲了方便查找而生的數據結構。關於散列的表的解釋,我想引用維基百科上的解釋,以下所示:git

散列表Hash table,也叫哈希表是根據(Key)而直接訪問在內存存儲位置的數據結構。也就是說,它經過計算一個關於鍵值的函數,將所需查詢的數據映射到表中一個位置來訪問記錄,這加快了查找速度。這個映射函數稱作散列函數,存放記錄的數組稱作散列表github

散列表的建立就是將Value經過散列函數和處理散列key值衝突的函數來生成一個key, 這個key就是Value的查找映射,咱們就能夠經過key來訪問Value的值。本篇博客咱們就來好好的聊一下散列表的實現,固然主要仍是構建散列函數還有解決衝突的函數,下方咱們先給出散列函數爲「除留取餘法」和處理衝突的線性探測發的原理圖,而後再給出面向對象的實現,最後在給出相應的代碼實現。編程

 

1、散列表建立原理數組

本部分咱們將以一系列的示意圖來看一下如何來建立一個哈希表,咱們就將下方截圖中的數列中的數據來存儲到哈希表中。在下方的實例中,咱們採用除留取餘法來建立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中便可,下方是完整的步驟。

 

  

 

二、散列表的查找spa

散列表的查找與散列表元素的插入是很是類似的,也是經過哈希函數以及處理衝突的方法來完成的。咱們以在建立好的查找表中查找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。3d

上述這種查找方式,與咱們以前聊的順序查找、二分查找等等效率要高的多,不過散列函數和處理衝突的函數的選擇在提升查找效率方面是相當重要的。查找順序以下:xml

  

 

2、散列表的具體代碼實現

聊完原理,接下來就到了咱們代碼實現的時刻了。下方咱們會使用面嚮對象語言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。

 

3、測試用例

接下來又到了咱們測試的時刻了,上方咱們依然採用「面向接口」編程的思想來實現的,因此咱們的測試用例可使用一個。將不一樣類型的HashTable的對象便可,下方就是咱們的測試用例。

  

 

下方是對除留取餘法+線性探測的哈希表進行的的測試結果。上面是使用該方法建立哈希表的詳細步驟,而後將建立好的hashTable進行了輸出,最後給出了查找的結果。以下所示:

  

上方是構建哈希表的整個過程,下方則是將建立好的HashTable進行輸出,而且給出35的查詢結果:

   

今天的博客就先到這,更詳細的代碼實現請移步github分享連接,以下所示。

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

相關文章
相關標籤/搜索