1. 程式人生 > >[譯]C語言實現一個簡易的Hash table(7)

[譯]C語言實現一個簡易的Hash table(7)

分享圖片 每次 就會 code 指針 分布 放置 .cn git

技術分享圖片

上一章我們講了如何根據需要動態設置hash表的大小,在第四章中,我們使用了雙重哈希來解決hash表的碰撞,其實解決方法有很多,這一章我們來介紹下其他方法。

本章將介紹兩種解決hash表碰撞的方法:

  1. 拉鏈法
  2. 開放地址法

拉鏈法

使用拉鏈法,每一個bucket都會包含一個鏈接表,當發生碰撞時,就會將該記錄插入在該位置的鏈接表後面,步驟如下:

  • 插入時:通過hash函數獲取到要插入的位置,如果該位置是空的,就直接插入,如果該位置不是空的,就插入在鏈接表的後面

  • 搜索時:通過hash函數獲取到key對應的位置,遍歷鏈接表,判斷key是不是搜索的key,如果是,則返回value,否則返回NULL

  • 刪除時:通過hash函數

    獲取到key對應的位置,遍歷鏈接表,找到需要刪除的key,如果找到,則將該key對應的記錄從鏈接表中刪除,如果鏈接表中只有一條記錄,則將該位置置為NULL

拉鏈法的優點是實現起來簡單,但是空間利用率低。每個記錄必須存儲指向鏈接表中下一個記錄的指針,如果沒有記錄,則指向NULL,這種方法會浪費一些空間來存儲額外的指針。

開放地址法

開放地址法能解決拉鏈法空間利用率低的問題,發生碰撞時,碰撞的記錄將放置在hash表中的其他bucket中,存放的位置是根據預先確定的規則選擇的,以便在搜索記錄時可以重復該規則,有如下幾種規則:

線性探查

當發生碰撞時,就會遞增索引,將記錄插入在下一個可用的索引中,方法如下:

  • 插入時:通過hash函數找到插入的位置的索引,如果這個位置是空的,直接插入,如果不為空,就遞增索引,直到找到索引指向的位置是空的為止,然後執行插入

  • 搜索時:通過hash函數找到搜索的記錄的索引,每次遞增索引,並比較索引指向的值是否是要搜索的值,如果索引指向的是空,則返回NULL

  • 刪除時:通過hash函數找到刪除的記錄的索引,每次遞增索引,直到找到要刪除的那個key後執行刪除

線性探測提供了良好的緩存性能,但是存在碰撞後遍歷次數多的問題。將發生碰撞key放入下一個可用的bucket中可能導致後面插入記錄也要往後插,就需要多次叠代。

二次探查

二次探查法和先行探查類似,不同的是,發生碰撞後,我們會將記錄插入在如下的序列中:i, i + 1, i + 4, i + 9, i + 16, ...

i代表通過hash函數獲取到的索引,具體步驟如下:

  • 插入時:通過hash函數找到插入的索引,通過遍歷上面的序列直到找到一個空的或已被刪除的索引位置,執行插入

  • 搜索時:通過hash函數找到key的索引,遍歷上面的序列,將序列上的key與搜索的key對比,如果相等,則返回value,否則返回NULL

  • 刪除時:因為我們無法判斷要刪除的項是不是碰撞鏈上的,所以我們不能直接刪除該條記錄,只能把它標記為已刪除

二次探查法減少發生碰撞後遍歷的次數,並且仍然提供了不錯的緩存性能。

雙重hash

雙重hash旨在解決碰撞後遍歷次數多的問題。使用兩次hash函數為插入的記錄選擇新的索引,這個索引會均勻的分布在整個表中,該方法雖然解決了上述問題,但也失去了緩存特性,雙重hash是實際項目中常見的沖突管理方法,也是我們在本教程中實現的方法。

上一章:設置hash表大小


原文地址:https://github.com/jamesroutley/write-a-hash-table/tree/master/07-appendix

[譯]C語言實現一個簡易的Hash table(7)