AVL Tree
前言
?
希望讀者
了解二叉搜索樹
了解左旋右旋基本操作
https://blog.csdn.net/hebtu666/article/details/84992363
直觀感受直接到文章底部,有正確的調整策略動畫,自行操作。
二叉搜索樹
?
二叉查找樹(Binary Search Tree),(又:二叉搜索樹,二叉排序樹)它或者是一棵空樹,或者是具有下列性質的二叉樹: 若它的左子樹不空,則左子樹上所有結點的值均小于它的根結點的值; 若它的右子樹不空,則右子樹上所有結點的值均大于它的根結點的值; 它的左、右子樹也分別為二叉排序樹。
具體介紹和實現:https://blog.csdn.net/hebtu666/article/details/81741034
我們知道,對于一般的二叉搜索樹(Binary Search Tree),其期望高度(即為一棵平衡樹時)為log2n,其各操作的時間復雜度(O(log2n))同時也由此而決定。但是,在某些極端的情況下(如在插入的序列是有序的時),二叉搜索樹將退化成近似鏈或鏈,
此時,其操作的時間復雜度將退化成線性的,即O(n)。我們可以通過隨機化建立二叉搜索樹來盡量的避免這種情況,但是在進行了多次的操作之后,由于在刪除時,我們總是選擇將待刪除節點的后繼代替它本身,這樣就會造成總是右邊的節點數目減少,以至于樹向左偏沉。這同時也會造成樹的平衡性受到破壞,提高它的操作的時間復雜度。
?
AVL Tree
在計算機科學中,AVL樹是最先發明的自平衡二叉查找樹。在AVL樹中任何節點的兩個子樹的高度最大差別為1,所以它也被稱為高度平衡樹。增加和刪除可能需要通過一次或多次樹旋轉來重新平衡這個樹。AVL樹得名于它的發明者G. M. Adelson-Velsky和E. M. Landis,他們在1962年的論文《An algorithm for the organization of information》中發表了它。
這種結構是對平衡性要求最嚴苛的self-Balancing Binary Search Tree。
旋轉操作繼承自self-Balancing Binary Search Tree
public class AVLTree extends AbstractSelfBalancingBinarySearchTree旋轉
上面網址中已經介紹了二叉搜索樹的調整和自平衡二叉搜索樹的基本操作(左旋右旋),上篇文章我是這樣定義左旋的:
達到了? ?看似? ?更平衡的效果。
我們回憶一下:
看起來好像不是很平,對嗎?我們轉一下:
看起來平了很多。
但!是!
只是看起來而已。
我們知道。ABCD其實都是子樹,他們也有自己的深度,如果是這種情況:
我們簡化一下:
轉之后(A上來,3作為A的右孩子,A的右子樹作為新的3的左孩子):
沒錯,旋轉確實讓樹變平衡了,這是因為,不平衡是由A的左子樹造成的,A的左子樹深度更深。
我們這樣旋轉實際上是讓
A的左子樹相對于B提上去了兩層,深度相對于B,-2,
A的右子樹相對于B提上去了一層,深度相對于B,-1.
而如果是這樣的:
旋轉以后:
依舊是不平的。
那我們怎么解決這個問題呢?
先3的左子樹旋轉:
細節問題:不再講解
這樣,我們的最深處又成了左子樹的左子樹。然后再按原來旋轉就好了。
?
旋轉總結
?
那我們來總結一下旋轉策略:
單向右旋平衡處理LL:
由于在*a的左子樹根結點的左子樹上插入結點,*a的平衡因子由1增至2,致使以*a為根的子樹失去平衡,則需進行一次右旋轉操作;
單向左旋平衡處理RR:
由于在*a的右子樹根結點的右子樹上插入結點,*a的平衡因子由-1變為-2,致使以*a為根的子樹失去平衡,則需進行一次左旋轉操作;
雙向旋轉(先左后右)平衡處理LR:
由于在*a的左子樹根結點的右子樹上插入結點,*a的平衡因子由1增至2,致使以*a為根的子樹失去平衡,則需進行兩次旋轉(先左旋后右旋)操作。
雙向旋轉(先右后左)平衡處理RL:
由于在*a的右子樹根結點的左子樹上插入結點,*a的平衡因子由-1變為-2,致使以*a為根的子樹失去平衡,則需進行兩次旋轉(先右旋后左旋)操作。
?
深度的記錄
?
我們解決了調整問題,但是我們怎么發現樹不平衡呢?總不能沒插入刪除一次都遍歷一下求深度吧。
當然要記錄一下了。
我們需要知道左子樹深度和右子樹深度。這樣,我們可以添加兩個變量,記錄左右子樹的深度。
但其實不需要,只要記錄自己的深度即可。然后左右子樹深度就去左右孩子去尋找即可。
這樣就引出了一個問題:深度的修改、更新策略是什么呢?
單個節點的深度更新
本棵樹的深度=(左子樹深度,右子樹深度)+1
所以寫出節點node的深度更新方法:
private static final void updateHeight(AVLNode node) { //不存在孩子,為-1,最后+1,深度為0int leftHeight = (node.left == null) ? -1 : ((AVLNode) node.left).height;int rightHeight = (node.right == null) ? -1 : ((AVLNode) node.right).height;node.height = 1 + Math.max(leftHeight, rightHeight);}?
寫出旋轉代碼
配合上面的方法和文章頭部給出文章Abstract Self-Balancing Binary Search Tree的旋轉,我們可以AVL樹的四種旋轉:
private Node avlRotateLeft(Node node) {Node temp = super.rotateLeft(node);updateHeight((AVLNode)temp.left);updateHeight((AVLNode)temp);return temp;}private Node avlRotateRight(Node node) {Node temp = super.rotateRight(node);updateHeight((AVLNode)temp.right);updateHeight((AVLNode)temp);return temp;}protected Node doubleRotateRightLeft(Node node) {node.right = avlRotateRight(node.right);return avlRotateLeft(node);}protected Node doubleRotateLeftRight(Node node) {node.left = avlRotateLeft(node.left);return avlRotateRight(node);}請自行模擬哪些節點的深度記錄需要修改。
?
總寫調整方法
?
我們寫出了旋轉的操作和相應的深度更新。
現在我們把這些方法分情況總寫。
private void rebalance(AVLNode node) {while (node != null) {Node parent = node.parent;int leftHeight = (node.left == null) ? -1 : ((AVLNode) node.left).height;int rightHeight = (node.right == null) ? -1 : ((AVLNode) node.right).height;int nodeBalance = rightHeight - leftHeight;if (nodeBalance == 2) {if (((AVLNode)node.right.right).height+1 == rightHeight) {node = (AVLNode)avlRotateLeft(node);break;} else {node = (AVLNode)doubleRotateRightLeft(node);break;}} else if (nodeBalance == -2) {if (((AVLNode)node.left.left).height+1 == leftHeight) {node = (AVLNode)avlRotateRight(node);break;} else {node = (AVLNode)doubleRotateLeftRight(node);break;}} else {updateHeight(node);//平衡就一直往上更新高度}node = (AVLNode)parent;}}插入完工
?
我們的插入就完工了。
public Node insert(int element) {Node newNode = super.insert(element);//插入rebalance((AVLNode)newNode);//調整return newNode;}?
刪除
也是一樣的思路,自底向上,先一路修改高度后,進行rebalance調整。
public Node delete(int element) {Node deleteNode = super.search(element);if (deleteNode != null) {Node successorNode = super.delete(deleteNode);//結合上面網址二叉搜索樹實現的情況介紹if (successorNode != null) {// if replaced from getMinimum(deleteNode.right) // then come back there and update heightsAVLNode minimum = successorNode.right != null ? (AVLNode)getMinimum(successorNode.right) : (AVLNode)successorNode;recomputeHeight(minimum);rebalance((AVLNode)minimum);} else {recomputeHeight((AVLNode)deleteNode.parent);//先修改rebalance((AVLNode)deleteNode.parent);//再調整}return successorNode;}return null;}/*** Recomputes height information from the node and up for all of parents. It needs to be done after delete.*/private void recomputeHeight(AVLNode node) {while (node != null) {node.height = maxHeight((AVLNode)node.left, (AVLNode)node.right) + 1;node = (AVLNode)node.parent;}}/*** Returns higher height of 2 nodes. */private int maxHeight(AVLNode node1, AVLNode node2) {if (node1 != null && node2 != null) {return node1.height > node2.height ? node1.height : node2.height;} else if (node1 == null) {return node2 != null ? node2.height : -1;} else if (node2 == null) {return node1 != null ? node1.height : -1;}return -1;}請手動模擬哪里的高度需要改,哪里不需要改。
?
直觀表現程序
?
如果看的比較暈,或者直接從頭跳下來的同學,這個程序是正確的模擬了,維護AVL樹的策略和一些我沒寫的基本操作。大家可以自己操作,直觀感受一下。
https://www.cs.usfca.edu/~galles/visualization/AVLtree.html?utm_source=qq&utm_medium=social&utm_oi=826801573962338304
總結
- 上一篇: leetcode739 每日温度
- 下一篇: kubelet内存异常分析