哈希表
首先什麼是 哈希表,哈希表(英文名字爲Hash table,國內也有一些算法書籍翻譯爲散列表,你們看到這兩個名稱知道都是指hash table就能夠了)。java
❝哈希表是根據關鍵碼的值而直接進行訪問的數據結構。程序員
❞
這麼這官方的解釋可能有點懵,其實直白來說其實數組就是一張哈希表。web
哈希表中關鍵碼就是數組的索引下表,而後經過下表直接訪問數組中的元素,以下圖所示:面試
那麼哈希表能解決什麼問題呢,「通常哈希表都是用來快速判斷一個元素是否出現集合裏。」算法
例如要查詢一個名字是否在這所學校裏。數組
要枚舉的話時間複雜度是O(n),但若是使用哈希表的話, 只須要O(1) 就能夠作到。微信
咱們只須要初始化把這所學校裏學生的名字都存在哈希表裏,在查詢的時候經過索引直接就能夠知道這位同窗在不在這所學校裏了。markdown
將學生姓名映射到哈希表上就涉及到了「hash function ,也就是哈希函數」。數據結構
哈希函數
哈希函數,把學生的姓名直接映射爲哈希表上的索引,而後就能夠經過查詢索引下表快速知道這位同窗是否在這所學校裏了。less
哈希函數以下圖所示,經過hashCode把名字轉化爲數值,通常hashcode是經過特定編碼方式,能夠將其餘數據格式轉化爲不一樣的數值,這樣就把學生名字映射爲哈希表上的索引數字了。
若是hashCode獲得的數值大於 哈希表的大小了,也就是大於tableSize了,怎麼辦呢?
此時爲了保證映射出來的索引數值都落在哈希表上,咱們會在再次對數值作一個取模的操做,就要咱們就保證了學生姓名必定能夠映射到哈希表上了。
此時問題又來了,哈希表咱們剛剛說過,就是一個數組。
若是學生的數量大於哈希表的大小怎麼辦,此時就算哈希函數計算的再均勻,也避免不了會有幾位學生的名字同時映射到哈希表 同一個索引下表的位置。
接下來「哈希碰撞」登場
哈希碰撞
如圖所示,小李和小王都映射到了索引下表 1的位置,「這一現象叫作哈希碰撞」。
通常哈希碰撞有兩種解決方法, 拉鍊法和線性探測法。
拉鍊法
剛剛小李和小王在索引1的位置發生了衝突,發生衝突的元素都被存儲在鏈表中。這樣咱們就能夠經過索引找到小李和小王了
(數據規模是dataSize, 哈希表的大小爲tableSize)
其實拉鍊法就是要選擇適當的哈希表的大小,這樣既不會由於數組空值而浪費大量內存,也不會由於鏈表太長而在查找上浪費太多時間。
線性探測法
使用線性探測法,必定要保證tableSize大於dataSize。咱們須要依靠哈希表中的空位來解決碰撞問題。
例如衝突的位置,放了小李,那麼就向下找一個空位放置小王的信息。因此要求tableSize必定要大於dataSize ,要否則哈希表上就沒有空置的位置來存放 衝突的數據了。如圖所示:
其實關於哈希碰撞還有很是多的細節,感興趣的同窗能夠再好好研究一下,這裏我就再也不贅述了。
常見的三種哈希結構
當咱們想使用哈希法來解決問題的時候,咱們通常會選擇以下三種數據結構。
-
數組 -
set (集合) -
map(映射)
這裏數組就沒啥可說的了,咱們來看一下set和map,在C++語言中,實現在C++中,set 和 map 分別提供瞭如下三種數據結構,其底層實現以及優劣以下表所示:
std::unordered_set底層實現爲哈希表,std::set 和std::multiset 的底層實現是紅黑樹,紅黑樹是一種平衡二叉搜索樹,因此key值是有序的,但key不能夠修改,改動key值會致使整棵樹的錯亂,因此只能刪除和增長。
std::unordered_map 底層實現爲哈希表,std::map 和std::multimap 的底層實現是紅黑樹。同理,std::map 和std::multimap 的key也是有序的(這個問題也常常做爲面試題,考察對語言容器底層的理解)。
當咱們要使用集合來解決哈希問題的時候,優先使用unordered_set,由於它的查詢和增刪效率是最優的,若是須要集合是有序的,那麼就用set,若是要求不只有序還要有重複數據的話,那麼就用multiset。
那麼再來看一下map ,在map 是一個key value 的數據結構,map中,對key是有限制,對value沒有限制的,由於key的存儲方式使用紅黑樹實現的。
其餘語言例如:java裏的 HashMap ,TreeMap 都是同樣的原理。能夠靈活貫通。
雖然set、multiset 的底層實現是紅黑樹,不是哈希表,可是set、multiset 依然使用哈希函數來作映射,只不過底層的符號表使用了紅黑樹來存儲數據,因此使用這些數據結構來解決映射問題的方法,咱們依然稱之爲哈希法。map也是同樣的道理。
這裏在說一下,一些C++的經典書籍上 例如STL源碼剖析,說到了hash_set hash_map,這個與unordered_set,unordered_map又有什麼關係呢?
實際上功能都是同樣同樣的, 可是unordered_set在C++11的時候被引入標準庫了,而hash_set並無,因此建議仍是使用unordered_set比較好,這就比如一個是官方認證的,hash_set,hash_map 是C++11標準以前民間高手自發造的輪子。
總結
總結一下,當咱們遇到了要快速判斷一個元素是否出現集合裏的時候,就要考慮哈希法。
可是哈希法也是犧牲了空間換取了時間,由於咱們要使用額外的數組,set或者是map來存放數據,才能實現快速的查找。
都看到這了,還有sei!sei沒讀懂單獨找我!
預告下篇講解一波哈希表面試題的解題套路,咱們下期見!
我的微信,歡迎來撩!
本文分享自微信公衆號 - 代碼隨想錄(code_thinking)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。