前面兩篇文章介紹了hashmap的源碼和理論,今天把剩餘的部分成黑樹講一下。理解好紅黑樹,對咱們後續對hashmap或者其餘數據結構的理解都是頗有好處的。比方說爲何後面jdk要把hashmap中的單鏈表更新成紅黑樹?算法
父節點和子節點,這個我就很少說了。應該都知道。數組
若是某幾個子節點的父節點都是一個節點,那他們就是兄弟節點。數據結構
若是某個節點沒有父節點,那他就是根節點。iphone
若是某個節點沒有子節點,拿他就是葉子節點函數
看下面這個二叉樹:性能
A節點的高度:就是節點A到最遠端的葉子節點的邊數。 比方說這裏A節點的高度就是33d
E節點的深度:就是根節點A到節點E的邊數,這裏明顯就是2了,因此E節點的深度就是2指針
E 節點的層數:就是E節點的深度+1. 因此這裏E節點的層數就是3.cdn
再看這張圖:對象
圖1 就叫作滿二叉樹:
條件1:葉子節點所有在最下面
條件2:除了葉子節點之外,每一個節點都有左右2個子節點。
滿二叉樹很好理解對吧,很少說了。咱們繼續看圖2,
圖2 就叫作 徹底二叉樹:
條件1:葉子節點只能在最下面2層。
條件2:最後一層的葉子節點都靠左排列。
條件3:除了最後一層,其餘層的子節點個數必須達到最大。(也能夠理解成:把最後一層去掉之後,必須是一個滿二叉樹)
恩,好像徹底二叉樹的斷定就有點複雜了,不要緊,咱們看幾個圖練習一下
再看幾張圖:
圖1:不是徹底二叉樹吧,條件1就不符合。由於第二層就有一個葉子節點了
圖2:不符合條件2,圖2 裏最後一層葉子節點都向右邊了
圖3:不符合條件3 ,由於咱們把最後一層去掉之後發現並非一個滿二叉樹。
首先要明確一個概念,二叉樹除了用鏈表實現之外,還能夠用數組來實現。
鏈表實現的二叉樹很少說了,網上太多了,其實數據結構就是一個data字段,而後配一個left指針和right指針。
那若是用數組來作,怎麼存儲一個二叉樹呢?看下面這張圖:
很好理解吧,數字就表明數組的下標。好比說 跟節點A 就放在[1]位置,節點F 就放在[6]位置,節點I就放在[9]位置,等等
因此數學概括法之後,咱們能夠抽象出以下的規則:
若是一個二叉樹用數組來存儲,那麼節點對應的下表規則以下:
規則1:下標爲2*i的 必然是左子節點。
規則2:下標爲2*i+1 的,必然是右子節點。
規則3:下標爲i/2的節點必然是 節點i的 父親節點。
因此咱們只要把一個二叉樹的根節點放到數組的任意一個位置中,就必然能夠把整個二叉樹利用上述的規則串聯起來,整個就是二叉樹的數組存儲法。
而用數組存儲二叉樹的好處就是能夠節省left和right 這2個指針,能夠節約內存空間
再看一張圖:
你看這個二叉樹,咱們的出來的數組下標是 1,2,3,4,7,8,9,13.
浪費了 5,6,10,11,12. 浪費了這5個數組空間空閒在那裏。
因此徹底二叉樹的優勢就是不會浪費任何數組空間,因此對於徹底二叉樹來講,數組存儲是最合適的方法,比方說堆就是用數組來 作二叉樹的存儲結構的
前序遍歷:先打印節點自己,再打印左子樹,再打印右子樹
中序遍歷:左子樹-節點自己-右子樹
後序遍歷:左子樹-右子樹-節點自己
那這三種遍歷的方法我就很少說了,網上一搜一大堆。
有了這些前置的概念 咱們就能夠繼續深挖。
二叉搜索樹:對於任意一個節點來講,他的左節點的值都要小於這個節點,右節點的值都要大於這個節點。
那麼,咱們來對一個二叉搜索樹進行他的 查找,插入操做。 爲何要學這個,由於對於大部分數據結構來講,衡量他的
性能主要就是 看查找數據和插入數據的效率。比方說數組和鏈表 就是查找和插入效率截然相反的兩種數據結構。
廢話很少說,咱們首先來看一下二叉搜索樹的 查找操做要怎麼完成:
其實也很簡單,對於二叉搜索樹來講, 左節點的值《父節點《右節點。
因此想查找一個值,咱們只要從二叉搜索樹的頂部也就是根節點來遍歷便可:
若是這個值比節點大,那麼就去右子樹繼續遞歸查找,若是這個值比節點的值小,那麼就去左子樹查。直到找到爲止
這個遞歸函數我就不寫了,你們知道這個算法思路就能夠,本身多練習一下。
二叉搜索樹的插入操做就稍微複雜一點:
1.若是要插入的值,比節點的值要大,而且這個節點的右子樹爲空,那麼就直接放在這個節點的右節點位置上(注意哦,這個就是遞歸的結束條件)
2.若是不爲空,那麼就繼續 步驟1的操做,直到咱們找到對應的節點位置爲止。
能夠看出來,遞歸是實現二叉樹相關算法的核心思想。
此外最重要的一點,咱們再回顧一下這個二叉搜索樹的特性你會發現:若是咱們用中序遍歷的方法 print這個 二叉搜索樹,那麼 獲得的結果必定是一個有序的。時間複雜度爲o(n)
因此,二叉搜索樹又叫二叉排序樹。
這個問題問的好,這裏給出2個解決方案,你們有興趣能夠實現一下
方案1:
正常狀況下,咱們一個二叉樹若是用鏈表來實現,那麼基礎數據結構確定是 data字段和left以及right指針對吧,那麼
咱們能夠人爲的增長一個字段,叫 more指針。這個指針是幹嗎的呢?這個指針就是把重複的數據串起來。
比方說下圖的例子:
你們能夠看一下,這個二叉搜索樹的 11的那個節點 有一個鏈表,存儲的都是值爲11的節點。這個處理方式是否是有點像hashmap中處理哈希衝突的方法?
在實際生產中,咱們存儲的數據結構不多是一個個簡單的int值,而是一個個對象,那麼一個個對象裏面確定有不少字段, 好比 商品這個對象,能夠有不少字段,價格,商品名稱,商品圖片等。 若是用商品價格來做爲二叉搜索樹的key值, 那麼就確定會出現這種狀況,由於相同價格的商品能夠是不一樣的商品,好比iphone和mate20 都賣7000元,但顯然他們是不 同樣的東西
方案2:
在插入數據的時候,若是發現插入的值和節點的值相同,那麼就把這個值插入到這個節點的右節點的位置。(也就是說 插入一個大於節點的值和插入一個等於節點的值 方法是同樣的)
那麼針對這種狀況,咱們的查找方法就要修改一下了,不能說是找到和節點相同的值就退出遞歸了,而是找到相同節點的值之後 還要繼續找他的右子樹,看看有沒有值和查找的值相同,一直到葉子節點爲止(注意退出遞歸的條件)
實際上,咱們看一下二叉搜索樹的算法能夠得出來一個結果,他的查找效率是O(logn)的,徹底不如哈希表查找的效率o(1). 且,二叉搜索樹極端狀況下很容易退化成單鏈表,那麼單鏈表的查找效率咱們都知道是O(N)的。這個就更慢了。
但,即使如此,二叉搜索樹還有一些哈希表不具有的優勢:
二叉搜索樹用中序遍歷能夠很容易對 數據進行排序,這個是哈希表作不到的。
哈希表遇到哈希衝突的時候須要擴容,這個擴容操做至關耗時,性能有時候不穩定,雖說二叉搜索樹的性能也不穩定, 可是 咱們有更特殊的 平衡二叉搜索樹能夠把時間複雜度穩定在O(logn)
二叉搜索樹比較簡單,數據結構一目瞭然,遞歸遞歸遞歸就好了,可是哈希表大家懂的,實現起來超級複雜。
因此能夠知道,hashmap和二叉搜索樹各有各的優勢,具體怎麼用,仍是要看實際狀況,可是你們要知道這兩種數據結構的 優劣在哪裏以及爲何?
前面咱們說到了,普通的二叉搜索樹極端狀況下會退化成單鏈表,好比說:
你看這個二叉搜索樹運氣就很是差,全在左邊,一點都不平衡,看起來跟個單鏈表同樣,一看就是很慢的數據結構。
因此 所謂的平衡二叉搜索樹就是要想辦法保證在插入的過程當中,讓這個二叉搜索樹變的平衡起來,所謂的平衡起來 就是不能全在左邊或者全在右邊,最好是左右兩邊都有,且左右兩邊子樹的高度越接近就越好就越平衡。由於只有這樣 咱們的時間複雜度纔會穩定在 O(logn)
平衡二叉搜索樹的實現方法有不少,其中最出名的就是紅黑樹了,對於咱們普通寫業務代碼不是專業寫算法的人來講 紅黑樹幾乎就能夠表明平衡二叉搜索樹,二者無限接近。
可是咱們要注意的是,紅黑樹是近似平衡的 平衡二叉搜索樹,並非嚴格的平衡二叉樹,有興趣的同窗能夠查一查AVL樹 這個纔是嚴格平衡二叉樹,不過這個東西由於太嚴格致使每次插入刪除的效率過低,因此生產環境用的人不多。
關於紅黑樹的具體實現,我這裏就很少說了,由於紅黑樹的實現比較複雜,我估計除非是寫算法的人,不然絕大多數人 一生都不會寫一次紅黑樹,若是實在要用,實際上跳錶寫起來比紅黑樹簡單多了。。也好理解。
因此這裏有興趣的同窗能夠自行搜索紅黑樹的相關資料,開開眼界。沒有興趣的同窗就只要知道紅黑樹是一種平衡二叉查找樹,它是爲了解決普通二叉查找樹在數據插入的時候容易退化成單鏈表致使效率大幅下降的數據結構。他的高度會無限接近於log2n,因此 他的查找效率也就能夠穩定在O(logn)了。這也就是爲何hashmap在高版本jdk中的實現 用紅黑樹代替單鏈表來解決哈希碰撞的緣由。