java隨筆——HashMap與紅黑樹


前言:

hashmap是一種很常用的數據結構,其使用方便快捷,接下來筆者將給大家深入解析這個數據結構,讓大家能在用的時候知其然,也知其所以然。

一.Map

首先,從最基本的講起,我們先來認識一下map是個什么東西。在我們寫程序的時候經常會遇到數據檢索等操作,對於幾百個數據的小程序而言,數據的存儲方式或是檢索策略沒有太大影響,但對於大數據,效率就會差很遠。我們來討論一下這個問題。

1.線性檢索:

線性檢索是最為直白的方法,把所有數據都遍歷一遍,然后找到你所需要的數據。其對應的數據結構就是數組,鏈表等線性結構,這種方式對於大數據而言效率極低,其時間復雜度為O(n)。

2.二分搜索:

二分搜索算是對線性搜索的一個改進,比如說對於【1,2,3,4,5,6,7,8】,我要搜索一個數(假設是2),我先將這個數與4(這個數一般選中位數比較好)比較,小於4則在4的左邊【1,2,3】中查找,再與2比較,相等,就成功找到了,這種檢索方式好處在於可以省去很多不必要的檢索,每次只用查找集合中一半的元素。其時間復雜度為O(logn)。但其也有限制,他的數排列本身就需要是有序的。

3.Hash表中的查找:

好了,重點來了,Hash表閃亮登場,這是一種時間復雜度為O(1)的檢索,就是說不管你數據有多少只需要查一次就可以找到目標數據。是不是很神奇??好吧其實很弱智。大家請看下圖。

大家可以看到這個數組中的值就等於其下標,比如說我要存11,我就把它存在a[11]里面,這樣我要找某個數字的時候就直接對應其下標就可以了。這其實是一種犧牲空間換時間的方法,這樣會對內存占用比較大,但檢索速度極快,只需要搜索一次就能查到目標數據。

4.Hash表的改變

看了上面的Hash表你肯定想問,如果我只存一個數10000,那我不是要存在a[10000],這樣其他空間不是白白浪廢了嗎,好吧,不存在的。Hash表已經有了其應對方法,那就是Hash函數。Hash表的本質在於可以通過value本身的特征定位到查找集合的元素下標,從而快速查找。一般的Hash函數為:要存入的數 mod(求余) Hash數組長度。比如說對於上面那個長度為9的數組,12的位置為12 mod 9=3,即存在a3,通過這種方式就可以安放比較大的數據了。

5.Hash沖突解決策略

看了上面的講解,機智的你們肯定已經發現了一個問題,通過求余數得到的地址可能是一樣的。這種我們稱為Hash沖突,如果數據量比較大而Hash桶比較小,這種沖突就很嚴重。我們采取如下方式解決沖突問題。

我們可以看到12和0的位置沖突了,然后我們把該數組的每一個元素變成了一個鏈表頭,沖突的元素放在了鏈表中,這樣在找到對應的鏈表頭之后會順着鏈表找下去,至於為什么采用鏈表,是為了節省空間,鏈表在內存中並不是連續存儲,所以我們可以更充分地使用內存。

Java之HashMap

上面講了那么多,那跟我們今天的主題HashMap有什么關系呢??好了盆友們不要方,進入正題。我們知道HashMap中的值都是key,value對吧,其實這里的存儲與上面的很像,key會被映射成數據所在的地址,而value就在以這個地址為頭的鏈表中,這種數據結構在獲取的時候就很快。但這里存在的問題就是如果hash桶較小,數據量較大,就會導致鏈表非常的長。比如說上面的長為11的空間我要放1000個數,無論Hash函數如何精妙,后面跟的鏈表都會非常的長,這樣Hash表的優勢就不復存在了,反而傾向於線性檢索。好了,紅黑樹閃亮登場。

紅黑樹

在jdk1.8版本后,java對HashMap做了改進,在鏈表長度大於8的時候,將后面的數據存在紅黑樹中,以加快檢索速度,我們接下來講一下紅黑樹。

avl樹

要了解紅黑樹,先要知道avl樹,要知道avl樹,首先要知道二叉樹,其實很簡單,二叉樹就是每個父節點下面有零個一個或兩個子節點,大致如下圖。

我們在向二叉樹中存放數據的時候將比父節點大的數放在右節點,將比父節點小的數放在左節點,這樣之后我們在查找某個數的時候只需要將其與父節點比較,大則進入右邊並遞歸調用,小則進入左邊遞歸。但其存在不足,如果運氣很不好我的數據本身就是有序的,比如【1,2,3,4,5,6,7】,這樣就會導致樹的不平衡,二叉樹就會退化成為鏈表。所以我們推出了avl樹。

avl樹即平衡樹,他對二叉樹做了改進,在我們每插入一個節點的時候,必須保證每個節點對應的左子樹和右子樹的樹高度差不超過1。如果超過了就對其進行調平衡,具體的調平衡操作就不在這里講了,無非就是四個操作——左旋,左旋再右旋,右旋再左旋。最終可以是二叉樹左右兩邊的樹高相近,這樣我們在查找的時候就可以按照二分查找來檢索,也不會出現退化成鏈表的情況。

二三樹

網上有很多講解紅黑樹的文章,有各種各樣的講解方式,但博主喜歡把紅黑樹與二三樹放在一起。先來看一下什么是二三樹。

注:該圖來自百度

其實很好理解,二三樹與普通二叉樹的不同點在於他有二節點和三節點。二節點下面有兩個子節點,二節點里面可以容納一個值,而三節點下面有三個子節點,三節點里面可以容納兩個值。下面來說一下二三數的構建。

注:圖片依然來自百度,博主畫圖比較垃圾。
其實二三樹的構建很簡單,如圖所示,圖中M結點就是一個二節點,M左邊的EJ節點是一個三節點。依然是大的數據放右邊,小的數據放左邊。此時我們向該樹重如果該數可以直接放入二節點中,就直接進去,但如果正好需要放在三節點中,就像圖中一樣,Z正好要放在SX中。那么我們需要將該節點分裂成兩個節點,並將中間的數提到父節點中去,就像圖中將X放在了R旁邊。當然如果將子節點提到父節點的時候導致了父節點里的數超過了兩個,就繼續向上提,直到滿足了為止。

紅黑樹

紅黑樹和二三樹很相像,基本上就是二三樹的一個變形。
紅黑樹比較傳統的定義是需要滿足以下五個特征:
(1)每個節點或者是黑色,或者是紅色。
(2)根節點是黑色。
(3)每個葉子節點(NIL)是黑色。 [注意:這里葉子節點,是指為空(NIL或NULL)的葉子節點!]
(4)如果一個節點是紅色的,則它的子節點必須是黑色的。
(5)從一個節點到該節點的子孫節點的所有路徑上包含相同數目的黑節點。
其特點在於給數的每一個節點加上了顏色屬性,在插入的過程中通過顏色變換和節點旋轉調平衡。其實博主不是很喜歡上面的定義,還有一種視角就是將它與二三樹比較。

當然上面這張圖也是搜來的。
紅黑樹還可以描述成:
⑴紅鏈接均為左鏈接。
⑵沒有任何一個結點同時和兩條紅鏈接相連。
⑶該樹是完美黑色平衡的,即任意空鏈接到根結點的路徑上的黑鏈接數量相同。
這里節點之間的連接分為紅連接和黑連接,取代了紅節點和黑節點的定義(本質是一樣的),將之前的黑高度相等定義為了黑連接數相等。更為直觀。
而如圖所示,其實紅黑樹的每一步操作都對應了二三樹的操作,如果是二節點就是黑連接,三節點的話里面的兩個數之間就是紅連接。

紅黑樹的優勢

紅黑樹相比avl樹,在檢索的時候效率其實差不多,都是通過平衡來二分查找。但對於插入刪除等操作效率提高很多。紅黑樹不像avl樹一樣追求絕對的平衡,他允許局部很少的不完全平衡,這樣對於效率影響不大,但省去了很多沒有必要的調平衡操作,avl樹調平衡有時候代價較大,所以效率不如紅黑樹,在現在很多地方都是底層都是紅黑樹的天下啦~

總結

HashMap在里面就是鏈表加上紅黑樹的一種結構,這樣利用了鏈表對內存的使用率以及紅黑樹的高效檢索,是一種很happy的數據結構。

文末小福利

筆者以前用C++手寫過avl樹的實現,大二數據結構課程設計有點迷的朋友可以參考。

下期預告


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM