1.考考你
這是咱們分享散列表的第三篇,在第一篇中咱們分享了散列表的基礎原理和HashMap應用,在第二篇中咱們分享了散列函數。你都還記得嗎?那麼這一篇一塊兒來分享:散列衝突、以及散列衝突的解決方法。java
#考考你: 1.你還記得什麼是散列衝突嗎? 2.你知道什麼是開放尋址法嗎? 3.你知道什麼是拉鍊法嗎?
2.案例
2.1.散列衝突
咱們說散列表數據結構,是數組的一種擴展,利用了數組支持按照下標隨機訪問的特性。那麼這裏就有一個講究:如何把散列表的key,與數組的下標創建起對應關係。這裏咱們須要一個散列函數,即:hash(key)。以下圖示:算法
那要如何設計散列函數呢?有三個基本原則:數組
#設計散列函數基本原則: 1.經過散列函數,計算出的散列值,一定是非負整數 2.若是key1 = key2,那麼一定hash(key1) = hash(key2) 3.若是key1 != key2,那麼儘可能hasy(key1) != hash(key2)
第一條與第二條,咱們在上一篇散列函數中有詳細解釋,也很好理解,這裏咱們就只看第三條。關鍵在這裏:若是key1 != key2,那麼儘可能hasy(key1) != hash(key2)。數據結構
你有沒有發現,我用了兩個字:儘可能。其實在心裏咱們是多麼渴望:若是key不一樣,那麼計算出的散列值就必定不一樣。可是很難辦到,或者就說辦不到。即使是業界知名的MD五、SHA哈希算法,也存在散列衝突。函數
所以,咱們默認接受了散列衝突的存在。固然你也不一樣擔憂,針對散列衝突有相應的解決辦法:開放尋址法、拉鍊法。spa
2.2.開放尋址法
什麼是開放尋址法呢?設計
咱們先看一個圖,而後再經過文字描述,相信你就能夠明白了。code
看圖:blog
說話:源碼
#開放尋址法思想: 1.假設有一個散列表,散列表容量是:7 2.如上圖示,填充了藍色的塊,表示已經存儲了數據 3.未填充顏色的塊,表示空閒 4.如今有一個元素:a,須要存儲到散列中 5.a元素經過散列函數hash(key),計算出散列值對應數組下標:5的位置 6.數組下標5的位置,已經填充了藍色塊(已經存有數據) 7.那麼咱們說:這就是散列衝突(不一樣的key,散列函數計算出了相同的散列值) 8.既然下標5的位置已經存儲了其餘數據,那a元素該如何處理呢? 9.咱們只須要向前看,好比說看6的位置 10.6的位置也不巧,也存儲了數據 11.並且6是數組的最後一個下標位置,如何繼續向前呢? 12.咱們能夠從頭再來,從0號位置開始看,0的位置也不巧,也存儲了數據 13.那1的位置呢?恰好1的位置沒有填充顏色,是空閒 14.因而將元素a,存儲在下標1的位置 15.這就是整個開放尋址法的思想,很簡單有沒有? 16.可是你可能會發現開放尋址法有一個弊端: 16.1.若是散列表中,空閒位置比較少,那麼散列衝突的機率就會很大 16.2.這個時候開放尋址法的效率就會很低 16.3.所以開放尋址法,只適合於散列表規模小,散列元素少的場景
2.3.拉鍊法
什麼是拉鍊法呢?
咱們仍是看一個圖,而後再經過文字描述,相信你就能夠明白了。
看圖:
說話:
#拉鍊法思想: 1.假設有一個散列表,散列表容量是:5 2.如上圖示,填充了藍色的塊,表示已經存儲了數據 3.未填充顏色的塊,表示空閒 4.在散列表的右邊,對應每個藍色塊,都有一個鏈表 5.0的位置鏈表有兩個節點,表示0的位置存在散列衝突 6.2的位置鏈表有三個節點,表示2的位置存在散列衝突 7.這就是整個拉鍊法的思想,也很簡單有沒有? 8.咱們來具體描述一下: 8.1.拉鍊法的核心思想是,散列表對應數組的每個下標位置,稱爲:slot槽 8.2.每個slot槽位,對應一條鏈表,用於解決散列衝突 8.3.當不一樣的元素key,經過散列函數計算出了相同的散列值,存在散列衝突 8.4.好比0 槽位發生了散列衝突,那麼只需在0槽位的鏈表上增長一個節點便可 8.5.這也就是拉鍊法名稱的由來(鏈表==>拉鍊法),對吧 9.最後結論: 拉鍊法適合於存儲大規模散列元素的散列表場景。好比java中的HashMap,應用的就是拉鍊法處理散列衝突。固然在jdk8中,還結合了紅黑樹,你應該去看一下HashMap的源碼了!
3.討論分享
#考考你答案: 1.你還記得什麼是散列衝突嗎? 【參考2.1節】散列衝突 2.你知道什麼是開放尋址法嗎? 【參考2.2節】開放尋址法 3.你知道什麼是拉鍊法嗎? 【參考2.3節】拉鍊法