java隨筆——HashMap與紅黑樹

前言:

hashmap是一種很經常使用的數據結構,其使用方便快捷,接下來筆者將給你們深刻解析這個數據結構,讓你們能在用的時候知其然,也知其因此然。java

一.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)的檢索,就是說無論你數據有多少隻須要查一次就能夠找到目標數據。是否是很神奇??好吧其實很弱智。你們請看下圖。app

你們能夠看到這個數組中的值就等於其下標,好比說我要存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的位置衝突了,而後咱們把該數組的每個元素變成了一個鏈表頭,衝突的元素放在了鏈表中,這樣在找到對應的鏈表頭以後會順着鏈表找下去,至於爲何採用鏈表,是爲了節省空間,鏈表在內存中並非連續存儲,因此咱們能夠更充分地使用內存。blog

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樹的實現,大二數據結構課程設計有點迷的朋友能夠參考。

下期預告

相關文章
相關標籤/搜索