平衡二叉樹(AVL樹),原來如此!!!
一、認識平衡二叉樹
前幾天,我們將搜尋二叉樹(也稱為二叉排序樹)講解了,重點講了搜尋二叉樹的插入和刪除操作,由特別是刪除操作,是比較難的知識點。現在我們將繼續在搜尋二叉樹的基礎之上,學習一顆新的數,那就是大名鼎鼎的平衡二叉樹(AVL樹)。在學習平衡二叉樹前,同學們需掌握了搜尋二叉樹的基本操作之後,再來看平衡二叉樹的知識,就會簡單一點哦!!!
前期文章:二叉樹的概念以及搜尋二叉樹。
本期文章:GitHub原始碼連結。
我們前面講了搜尋二叉樹的定義:一個節點的左子節點的關鍵字值小於這個節點,右子節點的關鍵字值大於或等於這個父節點。簡單點說就是左邊的小,右邊的大。現在我們試著將這個陣列插入到搜尋二叉樹,看看是什麼樣子?
int[] array = {1,2,3,4,5,6,7,8}; //升序
1
當我們試著去插入後,會發現,這顆搜尋二叉樹有一點怪怪,如圖:
根據上面的圖,我們可以看出,當我們去對一個本身已經有序的陣列,去插入到搜尋二叉樹中,結果卻跟“連結串列”長得更相似。也就是說,假設我們需要查詢8,這顆搜尋二叉樹的時間複雜度就跟連結串列一樣,是O(N)了。所以怎麼辦??? 就出現了今天的主題:平衡二叉樹。
要想使時間複雜度降下來,我們就得調整這棵樹的“樣子”,使之查詢元素的時間複雜度,跟這棵樹的深度直接掛鉤,直接變為O(logN)。所以我們得從新增元素的時候著手。平衡二叉樹程式碼的整體框架,跟搜尋二叉樹差不多,如下:
class TreeNode {
public int val;
public int height; //新加的成員變數:節點高度
public TreeNode left;
public TreeNode right;
public TreeNode(int val) {
this.val = val;
this.height = 1; //新節點的初始高度為1
}
}
public class AVL{
private TreeNode root; //根結點
public void add(int val) { //插入新的節點
}
public void remove(int val) { //刪除對應的節點
}
public boolean contains(int val) { //查詢是否有該值
}
}
二、插入操作
首先,平衡二叉樹與搜尋二叉樹的差別在於:平衡二叉樹,它自己可以自動調節整棵樹的平衡,也叫自平衡機制。 所以在平衡二叉樹裡,有一個概念叫做平衡因子,意思就是: 對於每一個子樹而言,它的左子樹的高度 減去 右子樹的高度 ,差值的絕對值不能超過1;換句話說就是:兩邊高度相減的範圍必須在[-1,1],這個區間呢,才能稱為平衡。這也是我們為什麼在TreeNode類裡面加入了height這個變數的原因。
插入操作,我們只需掌握4種不同情況導致的不平衡。分別是 LL型、RR型、LR型和RL型。
LL型
就如上面這幅圖所示,當我們插入5節點後,計算平衡因子,我們就會發現,在12結點處的平衡因子超過了1,所以我們需要對12這個節點進行調整。正是因為2節點的插入,從而導致了12節點的不平衡,而5節點在12節點的左子樹(8節點)的左子樹(5節點)。所以這種情況就叫LL型。我們稍微將上面的圖再“裝飾”一下,將它們各自的子節點都顯示出來,如下圖:(注:T1~T4,在實際的情況中可能沒有,此時是為了讓大家更好理解如何去進行旋轉操作,才加上的)
LL型動圖
對於LL型,我們需要進行右旋轉操作,同學們根據動圖,自行在紙上畫一下是如何進行連線的,就能更好的理解其中的關係。程式碼圖如下:
最後,我們還要再說一下,旋轉之後,只有兩個節點的高度是需要更改的,就是root和tmp這兩個節點的高度,等於它左右子樹的高度,再加上自己本身的高度值1,就是旋轉之後,這個節點的新高度了。切記:必須先計算root的高度之後,才能計算tmp的高度,因為root是tmp的右子樹,tmp的高度是依賴於root的高度值。
RR型
講完了LL型,RR型,也就簡單了許多,RR型和LL型是差不多的。只是二者互為映象而已。我們就直接看動圖吧!
RR型動圖
計算高度的節點還是root和tmp這兩個節點,還是先計算root的高度,再計算tmp的高度。程式碼圖如下:
LR型
講完了LL型和RR型,接來了的LR和RL型,就非常簡單,因為LR和RL,根本不需要重新再寫新的方法,我們只需要旋轉兩次,就是LR或者RL的操作,多的不說,我們以圖為切入點,展開來講;
上圖就是LR型的情況,當我們嘗試著上面的LL型和RR型,發現是解決不了問題的。我們只有想辦法讓LR型轉化成LL型,問題就迎刃而解了。問題在於怎麼轉化? 來,我們看下圖:
我們可以發現,我們只需將8節點先向左旋轉一下,就能得到LL型的狀態。得到LL型後,問題就回到了LL型上,那我們再整體向右旋轉一次,就能達到平衡的效果。我們以動圖來演示一下:
LR型動圖
RL型
相應的RL型,跟LR型也是差不多,就是映象而已。LR是先左旋轉再右旋轉,而RL是先有旋轉再左旋轉。我們還是以圖來展開說明吧!
旋轉過程如下:
整體的程式碼演示,我先以圖片的形式,將框架分出來,看著更為直觀一點,更容易理解一點。
public void add(int val) {
root = add(root, val);
}
private TreeNode add(TreeNode node, int val) {
if (node == null) {
return new TreeNode(val);
}
if (val < node.val) {
node.left = add(node.left, val);
} else { //大於等於的情況,還是需要新建節點
node.right = add(node.right, val);
}
//計算當前節點的高度
node.height = Math.max(getHeight(node.left), getHeight(node.right)) + 1; //取左右兩邊的最大值,再加1
//計算平衡因子
int balanceFactor = getBalanceFactor(node);
if (Math.abs(balanceFactor) > 1) {
//LL型,做右旋轉處理
if (balanceFactor > 1 && getBalanceFactor(node.left) >= 0) {
return R_Rotate(node); //直接將新的根結點返回即可
}
//RR型,做左旋轉處理
if (balanceFactor < -1 && getBalanceFactor(node.right) <= 0) {
return L_Rotate(node); //新的根節點,直接返回
}
//LR型,先對左子樹進行左旋轉,然後再對根節點進行右旋轉
if (balanceFactor > 1 && getBalanceFactor(node.left) < 0) {
node.left = L_Rotate(node.left); //先進行左旋轉,變成LL型
return R_Rotate(node); //再進行右旋轉
}
//RL型,先對右子樹進行右旋轉,然後再對根節點進行左旋轉
node.right = R_Rotate(node.right);
return L_Rotate(node);
}
return node;
}
//計算平衡因子
private int getBalanceFactor(TreeNode node) {
if (node == null) {
return 0;
}
return getHeight(node.left) - getHeight(node.right);
}
//計算節點的高度
private int getHeight(TreeNode node) {
if (node == null) {
return 0;
}
return node.height;
}
這樣的話,平衡二叉樹的插入操作,我們就講完了。接下來,我們來看看刪除操作。
三、刪除操作
刪除和插入是一樣的,我們只是在搜尋二叉樹的刪除操作上,做一些改動,就能實現平衡二叉樹的刪除操作。
分析:整棵樹原本是已經平衡了,是因為我們需要刪除一個節點,從而導致整棵樹產生不平衡。所以們只需要從刪除的節點處,向上一直遍歷,一直向上回溯,然後計算回溯到的節點,重新計算高度,重新計算平衡因子即可。操作完全就是add方法的一樣,直接拷下來即可。下圖是搜尋二叉樹的刪除操作:
平衡二叉樹的刪除,簡直就是一模一樣。我們可以發現,在上面圖中刪除之後,就是直接返回了node節點,。
而平衡二叉樹刪除,就是不要先返回node節點,先對node節點進行計算height,並且計算平衡因子,如果平衡因子超過1了,就調整即可。如果平衡因子沒超過1,此時 返回node節點就行。
平衡二叉樹刪除操作程式碼大致框架如下:
public void remove(int val) {
root = remove(root, val); //方法過載
}
private TreeNode remove(TreeNode node, int val) {
if (node == null) {
return null;
}
if (val < node.val) { //小於
node.left = remove(node.left, val);
} else if (val > node.val) { //大於
node.right = remove(node.right, val);
} else if (node.left != null && node.right != null) { //相等的情況,並且有左右兩個孩子
TreeNode minNode = getMinNode(node.right); //返回的是,node的右子樹的最小節點
minNode.right = remove(node.right, minNode.val); //以這個最小節點作為新的node返回,並刪除右子樹上的minNode
minNode.left = node.left;
node = minNode; //這裡先將minNode儲存到node裡面
} else { //相等的情況,只有一個孩子節點,或者是沒有節點情況
node = node.left != null? node.left : node.right;
}
//對node節點進行判斷,並計算height和平衡因子。
if (node == null) {
return null; //上面的else中,有可能node沒有孩子節點,可能會產生null
}
//計算當前節點的高度
node.height = Math.max(getHeight(node.left), getHeight(node.right)) + 1; //取左右兩邊的最大值,再加1
//計算平衡因子
int balanceFactor = getBalanceFactor(node);
if (Math.abs(balanceFactor) > 1) {
//LL型,做右旋轉處理
if (balanceFactor > 1 && getBalanceFactor(node.left) >= 0) {
return R_Rotate(node); //直接將新的根結點返回即可
}
//RR型,做左旋轉處理
if (balanceFactor < -1 && getBalanceFactor(node.right) <= 0) {
return L_Rotate(node); //新的根節點,直接返回
}
//LR型,先對左子樹進行左旋轉,然後再對根節點進行右旋轉
if (balanceFactor > 1 && getBalanceFactor(node.left) < 0) {
node.left = L_Rotate(node.left); //先進行左旋轉,變成LL型
return R_Rotate(node); //再進行右旋轉
}
//RL型,先對右子樹進行右旋轉,然後再對根節點進行左旋轉
node.right = R_Rotate(node.right);
return L_Rotate(node);
}
return node;
}
//返回這棵樹最小的節點
private TreeNode getMinNode(TreeNode node) {
TreeNode pre = null;
while (node != null) {
pre = node;
node = node.left; //向左子樹查詢
}
return pre;
}
對於平衡二叉樹,我們只需深刻理解到LL型和RR型的旋轉操作,那基本上平衡二叉樹就還算掌握的不錯。LR型和RL型,就是前面兩種的衍生而已。不管是add方法還是remove方法;都只需在搜尋二叉樹的基礎之上進行稍微的修改即可。最後大家可以自己再新增一下方法,例如:isBST、isBalanceTree等等。
好啦,本期更新就到此結束啦!!!同學們好好的拿出紙筆去畫一畫那4種旋轉過程,那麼恭喜你,平衡二叉樹就掌握的不錯啦!!!
下期見!!!
————————————————
版權宣告:本文為CSDN博主「飛人01_01」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處連結及本宣告。
原文連結:https://blog.csdn.net/x0919/article/details/119862758
。
1
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/30239065/viewspace-2788619/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 平衡二叉樹(AVL)二叉樹
- 手擼二叉樹——AVL平衡二叉樹二叉樹
- 十三、Mysql之平衡二叉樹(AVL樹)MySql二叉樹
- 自動平衡二叉樹的構建-AVL樹二叉樹
- 平衡二叉樹(AVL樹)和 二叉排序樹轉化為平衡二叉樹 及C語言實現二叉樹排序C語言
- 手寫AVL平衡二叉搜尋樹
- Java 樹結構實際應用 四(平衡二叉樹/AVL樹)Java二叉樹
- 演算法與資料結構——AVL樹(平衡二叉搜尋樹)演算法資料結構
- 平衡二叉樹,B樹,B+樹二叉樹
- 平衡二叉樹二叉樹
- 排序二叉樹和平衡二叉樹排序二叉樹
- 資料結構中的樹(二叉樹、二叉搜尋樹、AVL樹)資料結構二叉樹
- Java集合原始碼分析之基礎(五):平衡二叉樹(AVL Tree)Java原始碼二叉樹
- 詳解什麼是平衡二叉樹(AVL)(修訂補充版)二叉樹
- 資料結構與演算法-二叉查詢樹平衡(AVL)資料結構演算法
- 資料結構高階--AVL(平衡二叉樹)(圖解+實現)資料結構二叉樹圖解
- 5分鐘瞭解二叉樹之AVL樹二叉樹
- 平衡樹和二叉樹的區別二叉樹
- 平衡二叉查詢樹:紅黑樹
- 看動畫學演算法之:平衡二叉搜尋樹AVL Tree動畫演算法
- 滿二叉樹、完全二叉樹、平衡二叉樹、二叉搜尋樹(二叉查詢樹)和最優二叉樹二叉樹
- 110. 平衡二叉樹二叉樹
- Java實現紅黑樹(平衡二叉樹)Java二叉樹
- Python 樹表查詢_千樹萬樹梨花開,忽如一夜春風來(二叉排序樹、平衡二叉樹)Python排序二叉樹
- 漫話:什麼是平衡(AVL)樹?這應該是把AVL樹講的最好的文章了
- BST(二叉搜尋樹)、AVL樹、紅黑樹、2-3樹、B樹、B+樹、LSM樹、Radix樹比較
- 二叉堆、BST 與平衡樹
- JZ-039-平衡二叉樹二叉樹
- LeetCode-110-平衡二叉樹LeetCode二叉樹
- 程式碼隨想錄——二叉樹-12.平衡二叉樹二叉樹
- 資料結構之樹結構概述(含滿二叉樹、完全二叉樹、平衡二叉樹、二叉搜尋樹、紅黑樹、B-樹、B+樹、B*樹)資料結構二叉樹
- 二叉樹的深度、寬度遍歷及平衡樹二叉樹
- 資料結構之MySQL獨愛B+樹(二叉樹、AVL樹、紅黑樹、B樹對比)資料結構MySql二叉樹
- 資料結構和演算法-二叉樹,AVL,紅黑樹資料結構演算法二叉樹
- 資料結構-平衡二叉樹資料結構二叉樹
- 每日一練(28):平衡二叉樹二叉樹
- python實現非平衡二叉樹Python二叉樹
- 二叉平衡樹 python 列表 遞迴Python遞迴