数据结构:关于AVL树的平衡旋转详解
前言
? 本文是基于你已經有一定的二叉排序樹知識。如果你還是小白,可以參考我之前的博客:《數據結構:二叉搜索樹(BST)的基本操作》。所以,在本文中不會再出現關于BST樹的基本知識。
?
版權說明
著作權歸作者所有。
商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
作者:Q-WHai
發表日期: 2015年12月28日
鏈接:https://qwhai.blog.csdn.net/article/details/50393548
來源:CSDN
更多內容:分類 >> 算法與數學
?
概述
? AVL樹又叫做平衡二叉樹。前言部分我也有說到,AVL樹的前提是二叉排序樹(BST或叫做二叉搜索樹)。由于在生成BST樹的過程中可能會出現線型樹結構,比如插入的順序是:1, 2, 3, 4, 5, 6, 7..., n。在BST樹中,比較理想的狀況是每個子樹的左子樹和右子樹的高度相等,此時搜索的時間復雜度是log(N)??墒?#xff0c;一旦這棵樹演化成了線型樹的時候,這個理想的情況就不存在了,此時搜索的時間復雜度是O(N),在數據量很大的情況下,我們并不愿意看到這樣的結果。
? 現在我們要做的事就是讓BST在創建的過程中不要向線型樹發展。方法就是讓其在添加新節點的時候,不斷調整樹的平衡狀態。
? 定義:它是一棵空樹或它的左右兩個子樹的高度差的絕對值不超過1,并且左右兩個子樹都是一棵平衡二叉樹。
?
AVL樹實現
1.類圖
? 首先我們來看一下本文編寫代碼的類圖(見圖-1)。
? 因為考慮到圖片內容的篇幅問題,類圖中有一些地方的表述以eg.或getters(setters)省略掉了,詳細內容請以代碼為準。
? 知道策略模式或是看過我之前的博客《Java設計模式——策略模式》的都知道,這里我使用了策略模式來編寫代碼,4種不同的旋轉方式明顯的一個策略模式。這樣一來,代碼更加簡捷,邏輯也更加清晰了。
圖-1 AVL樹程序類圖
?
2.節點失衡
? 我們對于節點平衡有這樣的定義:它是一棵空樹或它的左右兩個子樹的高度差的絕對值不超過1,并且左右兩個子樹都是一棵平衡二叉樹。而這里提到的高度差,就是我們下面會引入的平衡因子:BF。
? 因為AVL樹說到底還是一個二叉樹,只有兩個子節點。而且節點失衡的發生,是因為有一個新節點的插入,這個新插入的節點導致了某些節點左右子節點高度的不一致。所以我們可以枚舉出以下4種情況的失衡狀態。
(1)在一個節點的左子樹的左子樹上插入一個新節點。即LL。在這種情況下,我們可以通過將節點右旋使其平衡。如圖-2所示;
圖-2 LL單右旋操作
(2)在一個節點的右子樹的右子樹上插入一個新節點。即RR。在這種情況下,我們可以通過將節點左旋使其平衡。如圖-3所示;
圖-3 RR單左旋操作
(3)在一個節點的左子樹的右子樹上插入一個新節點。即LR。在這種情況下,我們不能直接通過將節點左旋或右來使其平衡了。這里需要兩步來完成,先讓樹中高度較低的進行一次左旋,這個時候就變成了LL了。再進行一次單右旋操作即可。如圖-4所示;
圖-4 LR先左旋再右旋操作
(4)在一個節點的右子樹的左子樹上插入一個新節點。即RL。在這種情況下,我們不能直接通過將節點左旋或右來使其平衡了。這里需要兩步來完成,先讓樹中高度較低的進行一次右旋,這個時候就變成了RR了。再進行一次單左旋操作即可。如圖-5所示;
圖-5 RL先右旋再左旋操作
3.旋轉調節平衡
? 從上面對節點失衡的說明,以及圖解。我想你已經對旋轉的操作有了一個大概地認識了吧。從圖中我們也可以看出,LL型和RR型、LR型和RL型是兩個行為很相似地操作。其實他們互為對稱。所以在下面的講解中,我只針對LL型和LR型兩種操作進行詳細講解,另外兩種是類似的。
? 這里還有一點,可能讀者會有一些不是太明白。就是我們在旋轉的過程中,比如左旋,到底是怎么旋轉法,以哪個節點為中心點旋轉?
? 上面我們說到,失衡節點的平衡因子的絕對值是大于1的。AVL樹失衡類型的判斷都基于這個失衡節點的。也就是說LL型需要調整的是失衡節點的左子樹的左子樹,LR型需要調整的是失衡節點左子樹的右子樹。是不是有一點繞,看看上面的旋轉操作圖就應該知道了。那么,對于單旋轉操作也就很簡單了,就是以中間節點為中心旋轉。而雙旋轉中,因為不是一次旋轉,不能在一開始就確定旋轉中心點,我們在第一次旋轉的過程中,只是旋轉后面的兩個節點,并保證節點值的大小關系,如圖-4所示。后面的問題就轉化成了單旋轉操作了。
LL型:
? 針對圖-2所示的LL型失衡情況,可以看到節點15上是沒有右孩子的,這個時候轉成平衡之后,不用考慮節點15的右孩子與旋轉平衡調節之后的節點20是否有沖突。
? 關鍵代碼如下:
?
public class AdjustLL implements Adjustable {@Overridepublic void adjust(AVLTree tree, Node imbalanceNode) {Node parentNode = imbalanceNode.getParent();Node leftNode = imbalanceNode.getLeft(); // 新添加的節點resetImbalanceNode(imbalanceNode, leftNode);resetLeftNode(tree, leftNode, imbalanceNode, parentNode);resetParentNode(parentNode, leftNode);}/** 重置失衡節點* * @param imbalanceNode* 失衡節點*/private void resetImbalanceNode(Node imbalanceNode, Node leftNode) {... ...}/** 重置失衡節點的左節點* * @param leftNode* 左節點* @param imbalanceNode* 失衡節點* @param parentNode* 父節點*/private void resetLeftNode(AVLTree tree, Node leftNode, Node imbalanceNode, Node parentNode) {... ...}/** 重置父節點* * @param parentNode* 父節點* @param leftNode* 左節點*/private void resetParentNode(Node parentNode, Node leftNode) {... ...} }?
RR型:
? 類似LL型,詳細代碼請參見下面的GitHub源碼。
RL型:
? 對于RL型的就比較麻煩一些了,比如我們現在插入的序列是這樣的:{20, 15, 30, 40, 25, 23}。那么在調整平衡之前,我們的樹結構是這樣的(如圖-6所示):
圖-6 RL型失衡
? 這時我們就不能直接進行一次左旋或是右旋就可以搞定的了。雖然我們不能一步直接達到我們的要求,但是分兩步就可以了呀。操作圖示參見圖-7.
圖-7 RL型失衡的旋轉過程
? 第一次右旋的關鍵代碼:
?
/** 第一次右旋* (此處方法調用的順序不要修改)* * @param tree* AVL樹* @param imbalanceNode* 失衡節點* @param rightChildNode* 失衡節點的右孩子* @param rightLeftNode* 失衡節點右孩子的左孩子*/private void firstRightAdjust(AVLTree tree, Node imbalanceNode, Node rightChildNode, Node rightLeftNode) {firstRightChildAdjust(rightChildNode, rightLeftNode);firstRightLeftAdjust(imbalanceNode, rightChildNode, rightLeftNode);firstImbalanceAdjust(imbalanceNode, rightLeftNode);}/** 調整失衡節點* * @param imbalanceNode* 失衡節點* @param rightLeftNode* 失衡節點右孩子的左孩子*/private void firstImbalanceAdjust(Node imbalanceNode, Node rightLeftNode) {imbalanceNode.setRight(rightLeftNode);imbalanceNode.resetHeight();imbalanceNode.resetBF();}/** 調整失衡節點的右孩子* * @param rightChildNode* 失衡節點的右孩子* @param rightLeftNode* 失衡節點右孩子的左孩子*/private void firstRightChildAdjust(Node rightChildNode, Node rightLeftNode) {rightChildNode.setParent(rightLeftNode);rightChildNode.setLeft(null);rightChildNode.resetHeight();rightChildNode.resetBF();}/** 調整失衡節點右孩子的左孩子* * @param imbalanceNode* 失衡節點* @param rightChildNode* 失衡節點的右孩子* @param rightLeftNode* 失衡節點右孩子的左孩子*/private void firstRightLeftAdjust(Node imbalanceNode, Node rightChildNode, Node rightLeftNode) {rightLeftNode.setRight(rightChildNode);rightLeftNode.setParent(imbalanceNode);rightLeftNode.resetHeight();rightLeftNode.resetBF();}? 第二次左旋的關鍵代碼:
?
?
/** 第二次左旋* * @param tree* AVL樹* @param imbalanceNode* 失衡節點* @param rightChildNode* 失衡節點的右孩子* @param rightLeftNode* 失衡節點右孩子的左孩子*/private void secondLeftAdjust(AVLTree tree, Node imbalanceNode, Node rightChildNode, Node rightLeftNode) {Node parentNode = imbalanceNode.getParent();secondRightChildAdjust(rightChildNode);secondImbalanceAdjust(imbalanceNode, rightLeftNode);secondRightLeftAdjust(tree, imbalanceNode, rightLeftNode, parentNode);}/** 調整失衡節點* * @param imbalanceNode* 失衡節點* @param rightLeftNode* 失衡節點右孩子的左孩子*/private void secondImbalanceAdjust(Node imbalanceNode, Node rightLeftNode) {if (rightLeftNode.getLeft() != null) {imbalanceNode.setRight(rightLeftNode.getLeft());}imbalanceNode.setParent(rightLeftNode);imbalanceNode.resetHeight();imbalanceNode.resetBF();}/** 調整失衡節點的右孩子* * @param rightChildNode* 失衡節點的右孩子*/private void secondRightChildAdjust(Node rightChildNode) {rightChildNode.resetHeight();rightChildNode.resetBF();}/** 調整失衡節點右孩子的左孩子* * @param tree* AVL樹* @param imbalanceNode* 失衡節點* @param rightLeftNode* 失衡節點右孩子的左孩子* @param parentNode* 失衡節點的父節點*/private void secondRightLeftAdjust(AVLTree tree, Node imbalanceNode, Node rightLeftNode, Node parentNode) {rightLeftNode.setParent(parentNode);if (parentNode == null) {tree.resetRoot(rightLeftNode);}rightLeftNode.setLeft(imbalanceNode);rightLeftNode.resetHeight();rightLeftNode.resetBF();}?
LR型:
? 類似RL型,詳細代碼請參見下面的GitHub源碼。
?
Ref
- 《數據結構(C語言版)--清華大學出版社》
- 《大話數據結構--清華大學出版社》
?
GitHub源碼下載
https://github.com/qwhai/simple-tree
總結
以上是生活随笔為你收集整理的数据结构:关于AVL树的平衡旋转详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 算法:关于生成抽样随机数的这些算法
- 下一篇: 基于正态分布的图片高斯模糊算法