java avl_Java底层实现AVL 平衡二叉树
1、為什么要有AVL平衡二叉樹
我們之前寫過文章叫做BST二分搜索樹,這種數(shù)據(jù)結(jié)構(gòu)有一個(gè)缺點(diǎn)就是會(huì)退化為鏈表形式,到這導(dǎo)致了我們的樹結(jié)構(gòu)發(fā)揮不出來它應(yīng)有的優(yōu)勢。
從上圖可以發(fā)現(xiàn)如果按照順序進(jìn)行添加操作,那么二分搜索樹就會(huì)退化為鏈表形式,樹結(jié)構(gòu)也就失去了它的意義。
AVL(Adelson-Velsky-Landis Tree)是以創(chuàng)造者的名字命名的。這種樹結(jié)構(gòu)就是一種平衡二叉樹。它是最早的認(rèn)為可以自平衡的一種樹結(jié)構(gòu)。
2、什么是AVL平衡二叉樹
我們之前寫過的滿二叉樹、完全二叉樹、線段樹、最大堆等等都是一種平衡樹的例子(葉子節(jié)點(diǎn)的高度差不會(huì)大于 1 )。其實(shí)上面的平衡二叉樹都是比較理想的例子。但是在AVL樹中維護(hù)的平衡二叉樹有所不同。對(duì)于任意一個(gè)節(jié)點(diǎn),左子樹和右子樹的高度差不能超過 1
上面的規(guī)則相對(duì)來說更加寬松一些。比如下面這張圖:
這個(gè)結(jié)構(gòu)并不滿足我們之前的平衡二叉樹的規(guī)則,根節(jié)點(diǎn)的左右子樹高度差不大于 1 。卻滿足我們上面的規(guī)則(對(duì)于任意節(jié)點(diǎn))。
對(duì)于時(shí)間復(fù)雜度方面,平衡二叉樹的高度和節(jié)點(diǎn)數(shù)量之間也是O(log (N) )。
3、AVL樹的基本實(shí)現(xiàn)
3.1、實(shí)現(xiàn)的方法
首先,我們需要標(biāo)注每個(gè)節(jié)點(diǎn)的高度信息和平衡因子,平衡因子的計(jì)算就是左子樹的高度減去右子樹的高度的絕對(duì)值。這樣我們就可以依靠平衡因子來維護(hù)的AVL樹結(jié)構(gòu)。
3.2、構(gòu)造函數(shù)
我們首先需要構(gòu)造一個(gè)節(jié)點(diǎn)信息作為內(nèi)部類,主要包含我們要存儲(chǔ)的內(nèi)容,左右子樹的指針,還有高度信息。對(duì)于平衡因子我們可以使用左右子樹的差來進(jìn)行計(jì)算。 節(jié)點(diǎn)信息:
private class Node{ //內(nèi)部類 public K key;
public V value;
public Node left, right;
public int height;
public Node(K key, V value){
this.key = key;
this.value = value;
left = null;
right = null;
height = 1; //新節(jié)點(diǎn)高度為 1 }
構(gòu)造函數(shù):
private Node root;
private int size;
public AVLTree(){
root = null;
size = 0;
}
3.3、基本成員函數(shù)
首先我們需要獲取樹結(jié)構(gòu)的大小信息getSize()方法和判斷是否為空isEmpty()方法。
程序?qū)崿F(xiàn):
public int getSize() {
return size;
}
public boolean isEmpty() {
return size == 0;
}
因?yàn)槲覀冃枰叨刃畔砼袛鄻浣Y(jié)構(gòu),所以引入getHeight()方法。
程序?qū)崿F(xiàn):
private int getHeight(Node node) {
if (node == null)
return 0;
return node.height;
}
為了方便我們后續(xù)的操作,我們引入getNode()方法,通過索引key值來獲得節(jié)點(diǎn)。
程序?qū)崿F(xiàn):
private Node getNode(K key) {
return getNode(root, key);
}
private Node getNode(Node node, K key) {
if (node == null)
return null;
if (node.key.compareTo(key) < 0)
return getNode(node.left, key);
else if (node.key.compareTo(key) > 0)
return getNode(node.right, key);
else
return node;
}
在添加操作的時(shí)候,我們需要根據(jù)高度信息來獲得平衡因子,進(jìn)而判斷樹結(jié)構(gòu)是否滿足平衡樹的性質(zhì)。大于0:偏左,小于0:偏右
程序?qū)崿F(xiàn):
private int getBalanceFactor(Node node){
if (node == null)
return 0;
return getHeight(node.left) - getHeight(node.right);
}
4、左旋轉(zhuǎn)和右旋轉(zhuǎn)
我們知道原來平衡的樹變成不平衡會(huì)是在添加元素的時(shí)候,所以在添加元素的時(shí)候我們需要維護(hù)平衡性。下面就分四種情況分別討論:
4.1、LL 右旋轉(zhuǎn)
有一種添加元素后的情況是這樣的。
可以認(rèn)為添加的元素在節(jié)點(diǎn)的左邊(L)的右邊(L)。 在添加元素2,后會(huì)導(dǎo)致樹結(jié)構(gòu)的平衡性破壞,對(duì)于這種情況的判斷就是當(dāng)前節(jié)點(diǎn)的平衡因子大于1(向左偏斜),并且左節(jié)點(diǎn)的平衡因子大于0(向左偏斜),這樣就保證了這種情況的出現(xiàn),向左偏斜。if (balanceFactor > 1 && getBalanceFactor(node.left) >= 0)
那么我們就需要對(duì)上面的結(jié)構(gòu)進(jìn)行維護(hù)。進(jìn)行右旋轉(zhuǎn)操作。
為了不失一般性,我們引入更復(fù)雜的情況,如下圖:
按照平衡二叉樹的排序規(guī)則,對(duì)元素進(jìn)行右旋轉(zhuǎn)(類似于 y 繞 x 右旋轉(zhuǎn)),相當(dāng)于降低樹的高度。旋轉(zhuǎn)后仍然滿足二叉樹的排序規(guī)則。我們可以發(fā)現(xiàn)相對(duì)位置發(fā)生改變的就是節(jié)點(diǎn) y 和 x 的右子樹 T3 。最后更新高度信息,高度發(fā)生變化的只有節(jié)點(diǎn) x , y 。 程序?qū)崿F(xiàn):
private Node rightRotate(Node y) {
Node x = y.left; //獲得旋轉(zhuǎn)中心 Node T3 = x.right;
x.right = y; //進(jìn)行旋轉(zhuǎn)操作 y.left = T3;
y.height = Math.max(getHeight(y.left), getHeight(y.right)) + 1; //重新更新高度信息 x.height = Math.max(getHeight(x.left), getHeight(x.right)) + 1;
return x;
}
4.2、RR 左旋轉(zhuǎn)
我們有了上面的右旋轉(zhuǎn)的概念,那么左旋轉(zhuǎn)就變得簡單多了,左旋轉(zhuǎn)的發(fā)生的前提就是節(jié)點(diǎn)的平衡因子小于-1(偏右),右子樹的平衡因子小于0(偏右),也就是類似于下面的結(jié)構(gòu)。 可以認(rèn)為添加的元素在節(jié)點(diǎn)的右邊(R)的右邊(R)。if (balanceFactor < -1 && getBalanceFactor(node.right) <= 0)
下面就是對(duì)結(jié)構(gòu)進(jìn)行左旋轉(zhuǎn)操作,維護(hù)平衡性。
以x為中心,進(jìn)行左旋轉(zhuǎn)操作(類似于 y 繞 x 左旋轉(zhuǎn)),需要轉(zhuǎn)移的分別是 x 的左子樹 T2 和節(jié)點(diǎn) y 。 程序?qū)崿F(xiàn):
private Node leftRotate(Node y) {
Node x = y.right;
Node T2 = x.left;
x.left = y; //需要轉(zhuǎn)移的元素 y.right = T2;
y.height = Math.max(getHeight(y.left), getHeight(y.right)) + 1; // 更新高度信息 x.height = Math.max(getHeight(x.left), getHeight(x.right)) + 1;
return x;
}
4.3、LR 左右旋轉(zhuǎn)
這種情況就是添加元素在節(jié)點(diǎn)的左邊的右邊,左右旋轉(zhuǎn)指的是先進(jìn)行左旋轉(zhuǎn),后進(jìn)行右旋轉(zhuǎn)。類似于下面這種情況。
具體細(xì)節(jié)的旋轉(zhuǎn)如下:
先進(jìn)行左旋轉(zhuǎn)操作,結(jié)構(gòu)就會(huì)變成我們之前LL的形式,然后對(duì)其進(jìn)行右旋轉(zhuǎn)操作即可。
4.4、RL 右左旋轉(zhuǎn)
這種情況就是添加元素在節(jié)點(diǎn)的右邊的左邊,右左旋轉(zhuǎn)指的是先進(jìn)行右旋轉(zhuǎn),后進(jìn)行左旋轉(zhuǎn)。類似于下面這種情況。
具體細(xì)節(jié)的旋轉(zhuǎn)如下: 先進(jìn)行左旋轉(zhuǎn)操作,結(jié)構(gòu)就會(huì)變成我們之前LL的形式,然后對(duì)其進(jìn)行右旋轉(zhuǎn)操作即可。
4.5、四種情況總結(jié)
上面的四種情況完全包含了添加元素所需要的情況。
// LLif (balanceFactor > 1 && getBalanceFactor(node.left) >= 0)
return rightRotate(node);
// RRif (balanceFactor < -1 && getBalanceFactor(node.right) <= 0)
return leftRotate(node);
// LRif (balanceFactor > 1 && getBalanceFactor(node.left) < 0){
node.left = leftRotate(node.left);
return rightRotate(node);
}
// RLif (balanceFactor < -1 && getBalanceFactor(node.right) > 0){
node.right = rightRotate(node.right);
return leftRotate(node);
}
5、增刪改查操作的實(shí)現(xiàn)
5.1、添加操縱
步驟:遞歸到底的時(shí)候添加元素,否則就更新元素
更新每個(gè)節(jié)點(diǎn)的高度信息 + 對(duì)結(jié)構(gòu)進(jìn)行平衡處理
private Node add(Node node, K key, V value) {
/** BST 的源代碼片段 ····*/
node.height = 1 + Math.max(getHeight(node.left), getHeight(node.right));
int balanceFactor = getBalanceFactor(node);
// LL if (balanceFactor > 1 && getBalanceFactor(node.left) >= 0)
return rightRotate(node);
// RR if (balanceFactor < -1 && getBalanceFactor(node.right) <= 0)
return leftRotate(node);
// LR if (balanceFactor > 1 && getBalanceFactor(node.left) < 0){
node.left = leftRotate(node.left);
return rightRotate(node);
}
// RL if (balanceFactor < -1 && getBalanceFactor(node.right) > 0){
node.right = rightRotate(node.right);
return leftRotate(node);
}
return node;
}
5.2、刪除操作
這里的刪除操作,在原本BST二分搜索樹的基礎(chǔ)上進(jìn)行改變,增加刪除元素后對(duì)節(jié)點(diǎn)進(jìn)行平衡后的處理,同添加操作基本相同,這里只對(duì)增加的程序片段進(jìn)行展示。
private Node remove(Node node, K key) {
/** BST 的源代碼片段 ····*/
if (retNode == null)
return null;
retNode.height = 1 + Math.max(getHeight(retNode.left), getHeight(retNode.right)); //更新高度值 int balanceFactor = getBalanceFactor(retNode);
// LL if (balanceFactor > 1 && getBalanceFactor(retNode.left) >= 0)
return rightRotate(retNode);
// RR if (balanceFactor < -1 && getBalanceFactor(retNode.right) <= 0)
return leftRotate(retNode);
// LR if (balanceFactor > 1 && getBalanceFactor(retNode.left) < 0){
retNode.left = leftRotate(retNode.left);
return rightRotate(retNode);
}
// RL if (balanceFactor < -1 && getBalanceFactor(retNode.right) > 0){
retNode.right = rightRotate(retNode.right);
return leftRotate(retNode);
}
return retNode;
}
5.3、查詢操作
查詢操作主要包含查看元素是否在樹結(jié)構(gòu)中,另一個(gè)就是通過key值查找對(duì)應(yīng)的 value 值。兩者均是借助getNode方法進(jìn)行實(shí)現(xiàn)。 程序?qū)崿F(xiàn):
public boolean contains(K key) {
return getNode(key) != null;
}
public V get(K key) {
Node node = getNode(key);
return node == null ? null : node.value;
}
5.4、更改操作
更改操作也是借助getNode方法進(jìn)行更改。
public void set(K key, V newValue) {
Node node = getNode(key);
if (node != null)
node.value = newValue;
else
throw new IllegalArgumentException(key + "doesn't exist");
}
最后
更多精彩內(nèi)容,大家可以轉(zhuǎn)到我的主頁:源碼地址在主頁內(nèi)哦~~首頁 | 曲怪曲怪?quguai.cn
總結(jié)
以上是生活随笔為你收集整理的java avl_Java底层实现AVL 平衡二叉树的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java 注解继承注解_Java注解合并
- 下一篇: java 宽字节_宽字节注入