之前看hashmap原始碼的時候遇到一些問題,jdk1.8中對hashmap做了一些優化,其中就包括當鍊表節點大於8個時會將鍊錶轉換成紅黑樹以提高查詢效率。我對紅黑樹了解的比較少,而且之前學的二叉樹相關的知識也忘了許多,所以決定近期去複習一下。
先回顧一下二叉排序樹的基本知識,在二叉排序樹中,每個節點的左邊的節點都比該節點小,右邊的節點都比該節點大。它可以比較快的完成查詢操作,但是卻存在乙個弊端。那就是當插入時如果插入的節點的順序是基本有序的,那麼形成的二叉搜尋樹就是非常不平衡的,最極端的情況會直接退化成為鍊錶,這樣會大大降低查詢效率。
平衡二叉樹就是為了解決這個問題而出現的,它可以保證無論以什麼樣的順序插入節點,最後生成的樹的左右子樹高度之差的絕對值(平衡因子)不會超過1。因為每次向樹中插入乙個節點之後都會計算平衡因子(左右子樹高度差),平衡因子大於1則視為不平衡,需要通過左旋或者右旋調整平衡。
插入時大概可以分為以下幾種情況(不需要調整平衡的情況就不說了):
一:只需進行一次旋轉操作就可以調整平衡,可能是左旋也可能是右旋,這裡只說左旋的情況,右旋對稱過去就可以了。
/**
* 左旋
* * 第一種情況
* * 1
* \ 2
* 2 ---> / \
* \ 1 3
* 3
* * 第二種情況
* * 2
* / \ 4
* 1 4 / \
* / \ ---> 2 5
* 3 5 / \ \
* \ 1 3 6
* 6
* ** */
如圖中第一種情況所示,3 插入之後導致該樹不平衡。要調整平衡只需要從新插入的 節點3 開始向上查詢,找到最小不平衡子樹(從下往上第乙個高度差絕對值大於1的子樹),對最小不平衡子樹進行旋轉即可。向上查詢發現,節點1 的平衡因子是 -2 所以節點一就是最小不平衡子樹的樹根,並且 節點1 的右子節點(也就是 節點2 )的平衡因子是 -1,同為負數。所以只需要進行一次左旋即可。
什麼是左旋?怎麼左旋?
根據上面的例子,左旋大概可以理解為對 節點1 進行逆時針旋轉,讓 節點1 的右子節點做父節點,節點1 做左子節點。也就是:右子變新父,原父變左子。但也存在特殊情況,在情況一中,節點2 變成新的父節點之後,它的左右子節點都不為空,那麼如果 節點2 原來就有左子節點該怎麼辦?請看情況二。
在第二種情況中,節點6 的插入導致了該樹的不平衡,通過分析(節點2平衡因子為-2,節點4平衡因子為-1)可以得出需要對 節點2 進行左旋。這裡有乙個很尷尬的問題:節點4 原本就存在乙個左子節點(節點3),如果把節點4變成新的父節點之後,節點3該放哪?我第一次看到這就很懵逼,當時看的是乙個**,盯著看了好多遍,眼都要花了,氣的想把多出來的節點拔下來,好像扯遠了。。。。。。回到正題,通過觀察可以發現,節點3 是最小不平衡子樹的右子樹中的最小的乙個節點。它比原父節點要大,比新的父節點要小,那麼就可以放到原父節點的右邊,因為原父節點的右子節點不是變成了新父節點了嘛,所以就剛好空出來了。總結一下也就是,右子的左子變左子的右子,好像有點繞口。。。左旋的就是這樣,右旋跟左旋是對稱的所以沒必要講。
二:需要先左旋再右旋或者先右旋在左旋才能調整平衡。
這種情況的基本形態是這樣的:
/**
* * 先右旋再左旋
* * 1 1
* \ \ 2
* 3 ---> 2 ---> / \
* / \ 1 3
* 2 3
*
*
*
*/
按照上面的方法,從節點2開始向上找最小不平衡子樹,通過計算得出,節點1 的平衡因子是 -2,節點3 的平衡因子是 1 。一正一負,直接左旋是不行的,不信你可以試試。這裡就需要先對 節點3 進行右旋,在對節點1進行左旋,跟只旋轉一次也沒多大區別,就不講了。
怎麼判斷是進行左旋還是右旋?
這的看是怎麼計算平衡因子的,我是用左子樹高度減右子樹高度的方法。所以平衡因子為負數,那麼右邊比較高,就需要進行左旋,為正數就是左邊比較高,需要進行右旋。
**實現
/**
* 計算插入位置並插入節點
* @param e 節點中的資料
* @return 返回被插入的節點,用來從下往上檢查平衡性
*/private node addnode(e e)
while(node != null) else
}else if(result > 0) else
// 插入節點已存在時不做插入操作,但是 modcount 會自增
}else
}return node;}
/** * 從node節點開始 向上查詢失去平衡的節點,得到最小不平衡子樹的樹根,並調整平衡
* @param node 開始查詢的節點(新插入的節點)
*/private void checkandadjustbalance(node node)
}// 調整平衡
if(balancefactor == 2 || balancefactor == -2)
// 右旋
rightrotate(node);
}else
// 左旋
leftrotate(node);
}}}
/** * 計算該樹的平衡因子,平衡因子為 左子樹深度 減 右子樹深度
* @param root 需要計算平衡因子的樹的樹根
* @return 平衡因子
*/private int calculatebalancefactor(node root)
/**
* 左旋
* @param n 最小不平衡子樹的樹根
*/private void leftrotate(node n) else
}else
// 將原父的父節點指標指向新父
n.parent = n.right;
// 將新父的原左子,變成原父的右子
n.right = n.parent.left;
if(n.right != null)
// 新父的左子節點指標指向原父
n.parent.left = n;}
/** * 右旋
* @param n
*/private void rightrotate(node n) else
}else
// 將原父的父節點指標指向新父
n.parent = n.left;
// 將新父的原右子,變成原父的左子
n.left = n.parent.right;
if(n.left != null)
// 新父的左子節點指標指向原父
n.parent.right = n;
}
平衡二叉樹的刪除跟二叉排序樹差不多,基本可以分為以下三種情況:
1.被刪除節點存在兩個子節點。
2.被刪除節點存在乙個子節點。 3.被刪除節點不存在子節點。
對於第三種情況,可以直接刪除,只需要把相關的指標「斷開」就好。
對於前兩種,我們可以把被刪除節點跟乙個葉子節點互換,然後直接刪除。互換刪除的同時還要保證樹是平衡的,所以不能隨便選擇葉子結點互換。對於第一種情況,可以與左子樹中的最大節點互換,也可以與右子樹中的最小的節點互換。要注意的是,被交換的節點不一定是葉子節點,可能存在0-1個子節點,當存在子節點時就當做第二種情況處理。
第二種情況就直接跟它的子節點互換就好,因為是平衡二叉樹,平衡因子不能大於一,所以當該節點存在乙個子節點的時候,那麼該節點一定沒有孫子節點,也就是說它的那乙個子節點一定是葉子結點,可以直接互換。
刪除之後別忘了重新驗證平衡,因為少了乙個節點導致此節點所在的子樹的高度減一,可能會出現不平衡的情況。說多了也沒用,還是直接上**吧。
**實現
/**
* 刪除node節點,要分別處理 node節點有 0,1,2 個子節點的情況
* @param node
*/private void removenode(node node)
node childnode = node.left != null ? node.left : node.right;
// 當被刪除節點存在乙個子節點時
if(childnode != null)
if(node == node.parent.left) else
checkandadjustbalance(node);
}
資料結構 平衡二叉樹
1 空樹是平衡二叉樹。2 如果一棵樹不為空,並且其中所有的子樹都滿足各自的左子樹與右子樹的高度差都不超過 1。下面介紹乙個簡單應用,平衡二叉樹的相關操作以後補充。給定一顆二叉樹的頭結點 head,判斷一棵樹是否是平衡二叉樹。1.1 左子樹是否為平衡二叉樹 1.2 記錄左子樹的深度,最深到達哪一層,記...
平衡二叉樹 資料結構實驗之查詢二 平衡二叉樹
剛開始接觸平衡二叉樹,沒有什麼太多要分析的。部落格裡有很多大佬們都寫的很好。平衡二叉樹就是每個節點的子樹的高度差不超過1的二叉樹。可以快速搜尋數值的一種演算法,最糟的情況就是一直找到底,但也是log n 的。還是快很多。include include include define max a b a...
資料結構 二叉樹 2 平衡二叉樹 3
前面分析了平衡二叉樹是怎麼調整平衡的,這裡就來解決另乙個問題,平衡二叉樹理論為什麼能成立?難道就不會有怎麼調都不會平衡的情況嗎?一起 一下吧。我在很早的時候就注意到了乙個問題。上圖中是一顆不平衡的二叉樹向平衡調整的一部分過程,這個過程中我發現,子樹a,b,c,d的左右關係始終都是不變的,就是從左到右...