AVL定義:
AVL的命名是由2個(gè)其發(fā)明者的名字組成的,G.M.Adelson-Velsky和E.M.Landis,兩個(gè)俄羅斯人。AVL既有平衡二叉樹的特性,又有二分搜索樹的特性。
平衡二叉樹:對(duì)于任意一個(gè)節(jié)點(diǎn),左子樹和右子樹的高度差不能超過1。
二分搜索樹:
1、二分搜索樹的每個(gè)節(jié)點(diǎn)的值,大于其左子樹的所有節(jié)點(diǎn)的值;小于其右子樹的所有節(jié)點(diǎn)的值;
2、每一顆子樹也是二分搜索樹。
AVL結(jié)構(gòu):
因?yàn)锳VL是二分搜索樹,所以可以直接使用其結(jié)構(gòu),這里泛型使用了映射(Map),是為了符合更多的數(shù)據(jù)類型。然后新增了變量height,來表示當(dāng)前節(jié)點(diǎn)的高度,其中葉子節(jié)點(diǎn)的高度為1。如下圖:
public class AVLTree<K extends Comparable<K>, V>{
private class Node{
//當(dāng)前節(jié)點(diǎn)的值
public K key;
public V value;
//左節(jié)點(diǎn)
public Node left;
//右節(jié)點(diǎn)
public Node right;
// 節(jié)點(diǎn)高度,葉子結(jié)點(diǎn)為1
public int height;
public Node(K key, V value){
this.key = key;
this.value = value;
left = null;
right = null;
height = 1;
}
}
private Node root;
private int size;
獲取節(jié)點(diǎn)的高度和平衡因子:
平衡因子:左孩子的高度減去右孩子的高度。
1、獲取當(dāng)前節(jié)點(diǎn)的高度:
// 獲取節(jié)點(diǎn)node的高度
private int getHeight(Node node){
if (node == null) return 0;
return node.height;
}
2、獲取當(dāng)前節(jié)點(diǎn)的平衡因子:
// 獲取節(jié)點(diǎn)node的平衡因子,平衡因子不在[-1,1]區(qū)間,說明不是平衡二叉樹
private int getBalanceFactory(Node node){
if (node == null) return 0;
return getHeight(node.left) - getHeight(node.right);
}
判斷該二叉樹是否是一顆二分搜索樹:
思路:因?yàn)槎炙阉鳂涞闹行虮闅v會(huì)使元素自然排序,于是我們可以利用這個(gè)特性。對(duì)當(dāng)前的二叉樹進(jìn)行中序遍歷,把值存入一個(gè)集合List中,然后遍歷這個(gè)List,看前一個(gè)元素是否小于后一個(gè)元素,如果有一個(gè)不滿足返回false,直到全部元素遍歷完才返回true。
// 判斷該二叉樹是否是一顆二分搜索樹
public boolean isBST(){
// 二分搜索樹的一個(gè)特性:中序遍歷的結(jié)果是自然排序的
ArrayList<K> keys = new ArrayList<>();
inOrder(root, keys);
for (int i = 1; i < keys.size(); i++) {
if (keys.get(i - 1).compareTo(keys.get(i)) > 0)
return false;
}
return true;
}
// 中序遍歷
private void inOrder(Node node, ArrayList<K> keys) {
if (node == null) return;
inOrder(node.left, keys);
keys.add(node.key);
inOrder(node.right, keys);
}
判斷該二叉樹是否是一顆平衡二叉樹:
思路:遍歷判斷當(dāng)前節(jié)點(diǎn)的平衡因子balanceFactory是否 > 1,遞歸的終止條件為:1、執(zhí)行到最后一個(gè)NULL元素,此時(shí)平衡因子為0,返回true;2、平衡因子>1,返回false。遞歸執(zhí)行,只有左右孩子同時(shí)滿足平衡二叉樹的特性時(shí)才為true。
// 判斷該二叉樹是否是一顆平衡二叉樹,看平衡因子
public boolean isBalanced(){
return isBalanced(root);
}
// 判斷node為根的二叉樹是否是一顆平衡二叉樹,遞歸算法
private boolean isBalanced(Node node) {
if (node == null) return true;
int balanceFactory = getBalanceFactory(node);
if (Math.abs(balanceFactory) > 1)
return false;
// 左右子樹遞歸都為true才行
return isBalanced(node.left) && isBalanced(node.right);
}
LL與AVL的右旋轉(zhuǎn):
在執(zhí)行了添加元素或者刪除元素之后,由于會(huì)對(duì)節(jié)點(diǎn)高度進(jìn)行修改,很可能破壞AVL的平衡性。于是需要左旋轉(zhuǎn)或者右旋轉(zhuǎn)操作來使AVL重新獲得平衡。
右旋轉(zhuǎn)的時(shí)機(jī):插入的元素在不平衡節(jié)點(diǎn)的左側(cè)的左側(cè)(LL)。
我們先看右旋轉(zhuǎn),如下圖:
首先從新增或者刪除的元素往上,找到第一個(gè)平衡因子=2的節(jié)點(diǎn),如上圖的 y,然后取出 x 的右子樹 T3,然后使 y 的左子樹 x 的右孩子指向 y,然后讓 y 的左子樹指向 T3。此時(shí) x 成為了新的AVL的根節(jié)點(diǎn),并且新的AVL即符合平衡二叉樹的特性又符合二分搜索樹的特性。
右旋轉(zhuǎn)代碼實(shí)現(xiàn):
// 對(duì)節(jié)點(diǎn)y進(jìn)行向右旋轉(zhuǎn)操作,返回旋轉(zhuǎn)后新的根節(jié)點(diǎn)x
// y x
// / \ / \
// x T4 向右旋轉(zhuǎn) (y) z y
// / \ - - - - - - - -> / \ / \
// z T3 T1 T2 T3 T4
// / \
// T1 T2
private Node rightRotate(Node y){
// 取出y 的左孩子x的右孩子T3
Node x = y.left;
Node T3 = x.right;
x.right = y;
y.left = T3;
// 更新height
y.height = Math.max(getHeight(y.left), getHeight(y.right)) + 1;
x.height = Math.max(getHeight(x.left), getHeight(x.right)) + 1;
return x;
}
RR與AVL的左旋轉(zhuǎn):
左旋轉(zhuǎn)和右旋轉(zhuǎn)剛好相反,于是實(shí)現(xiàn)機(jī)制是一樣的。
左旋轉(zhuǎn)的時(shí)機(jī): 插入的元素在不平衡節(jié)點(diǎn)的右側(cè)的右側(cè)(RR)。
左旋轉(zhuǎn)的代碼實(shí)現(xiàn):
// 對(duì)節(jié)點(diǎn)y進(jìn)行向左旋轉(zhuǎn)操作,返回旋轉(zhuǎn)后新的根節(jié)點(diǎn)x
// y x
// / \ / \
// T4 x 向左旋轉(zhuǎn) (y) y z
// / \ - - - - - - - -> / \ / \
// T3 z T4 T3 T1 T2
// / \
// T1 T2
private Node leftRotate(Node y){
// 取出y 的右孩子x的左孩子T3
Node x = y.right;
Node T3 = x.left;
x.left = y;
y.right = T3;
// 更新height
y.height = Math.max(getHeight(y.left), getHeight(y.right)) + 1;
x.height = Math.max(getHeight(x.left), getHeight(x.right)) + 1;
return x;
}
LR:
插入的元素在不平衡節(jié)點(diǎn)的左側(cè)的右側(cè)(LR)。
此時(shí)則需要讓不平衡節(jié)點(diǎn) y 的左孩子 x 進(jìn)行左旋轉(zhuǎn),然后讓不平衡節(jié)點(diǎn) y 的左子樹指向左旋轉(zhuǎn)之后的新的二叉樹的根節(jié)點(diǎn) z。如下圖:
此時(shí),已經(jīng)儼然成為了LL的情況,于是 y 節(jié)點(diǎn)右旋轉(zhuǎn)就解決了啦。
RL:
插入的元素在不平衡節(jié)點(diǎn)的右側(cè)的左側(cè)(RL)。
此時(shí)則需要讓不平衡節(jié)點(diǎn) y 的右孩子 x 進(jìn)行右旋轉(zhuǎn),然后讓不平衡節(jié)點(diǎn) y 的右子樹指向右旋轉(zhuǎn)之后的新的二叉樹的根節(jié)點(diǎn) z。如下圖:
此時(shí),已經(jīng)儼然成為了RR的情況,于是 y 節(jié)點(diǎn)左旋轉(zhuǎn)就解決了啦。
添加元素:
遞歸操作,遞歸的終止條件:當(dāng)前節(jié)點(diǎn)為NULL,表示到達(dá)最后一個(gè)根節(jié)點(diǎn)NULL,則將新節(jié)點(diǎn)放入該位置。然后比較所給的key與當(dāng)前node節(jié)點(diǎn)的key的大小,如果小于,則新增的節(jié)點(diǎn)放在左子樹,于是往左子樹遞歸;反之,往右子樹遞歸;當(dāng)二者相等時(shí),則更新value值。然后更新node的height,計(jì)算node的平衡因子,如果平衡因子的絕對(duì)值 <= 1,則表示符合AVL特性,直接返回node;否則,就是已經(jīng)破壞了AVL的平衡性,需要根據(jù)不同情況來進(jìn)行左右旋轉(zhuǎn)。
public void add(K key, V value) {
root = add(root, key, value);
}
// 向 node 為根的二分搜索樹中添加新的元素(key, value)
// 返回插入新節(jié)點(diǎn)后的二分搜索樹的根
private Node add(Node node, K key, V value) {
//遞歸終止條件
if (node == null) {
//表示到達(dá)最后一個(gè)根節(jié)點(diǎn)null,則將新節(jié)點(diǎn)放入該位置
size++;
return new Node(key, value);
}
if (key.compareTo(node.key) < 0){
//遞歸步驟,往往遞歸終止條件的參數(shù)是和遞歸步驟的參數(shù)對(duì)應(yīng)的
//將插入新節(jié)點(diǎn)后的二分搜索樹的根掛在當(dāng)前樹上
node.left = add(node.left, key, value);
}else if(key.compareTo(node.key) > 0){
node.right = add(node.right, key, value);
}else {
// 更新value值
node.value = value;
}
// 更新 height,左右孩子中大的height + 1
node.height = 1 + Math.max(getHeight(node.left), getHeight(node.right));
// 計(jì)算平衡因子
int balanceFactory = getBalanceFactory(node);
if (Math.abs(balanceFactory) <= 1) return node;
// 不符合平衡二叉樹條件,需要平衡維護(hù)
if (balanceFactory > 1){
if (getBalanceFactory(node.left) >= 0){
// 插入的元素在不平衡節(jié)點(diǎn)的左側(cè)的左側(cè)(LL),采用右旋轉(zhuǎn)
return rightRotate(node);
}else{
// 插入的元素在不平衡節(jié)點(diǎn)的左側(cè)的右側(cè)(LR),采用先左旋轉(zhuǎn)再右旋轉(zhuǎn)
node.left = leftRotate(node.left);
return rightRotate(node);
}
}
if (balanceFactory < -1){
if (getBalanceFactory(node.right) <= 0){
// 插入的元素在不平衡節(jié)點(diǎn)的右側(cè)的右側(cè)(RR),采用左旋轉(zhuǎn)
return leftRotate(node);
}else {
// 插入的元素在不平衡節(jié)點(diǎn)的右側(cè)的左側(cè)(RL),采用右旋轉(zhuǎn)再左旋轉(zhuǎn)
node.right = rightRotate(node.right);
return leftRotate(node);
}
}
return node;
}
刪除元素:
思路:依舊是遞歸執(zhí)行,遞歸的終止條件:1、走到最后沒有找到 key;2、找到key,即node.key.compareTo(key) == 0,此時(shí)也要分情況考慮,當(dāng)左子樹為空,則把右子樹的根節(jié)點(diǎn)作為新二叉樹的根節(jié)點(diǎn),然后將node的右子樹置為NULL,讓GC回收;同理,右子樹為空時(shí),則把左子樹的根節(jié)點(diǎn)作為新二叉樹的根節(jié)點(diǎn),然后將node的左子樹置為NULL;當(dāng)左右子樹都不為空時(shí),則找出右子樹的最小值 successor 用來頂替待刪除節(jié)點(diǎn)的位置(后繼策略)。最后還是需要對(duì)上述操作之后返回的retNode進(jìn)行平衡因子的判斷,如果失去了平衡性,則需要根據(jù)不同情況進(jìn)行左右旋轉(zhuǎn)。
public V remove(K key) {
Node node = getNode(root, key);
if (node != null) {
root = remove(root, key);
return node.value;
}
return null;
}
// 刪除以node為根的二分搜索樹中的鍵為key的節(jié)點(diǎn)
// 返回刪除節(jié)點(diǎn)后的新的二分搜索樹
private Node remove(Node node, K key) {
// 遞歸終止條件: 1、走到最后沒有找到key ; 2、找到key
if (node == null) return node;
Node retNode;
if (node.key.compareTo(key) == 0){
if (node.left == null){
// 待刪除的節(jié)點(diǎn)左子樹為空的情況
Node rightNode = node.right;
node.right = null;
size--;
retNode = rightNode;
} else if (node.right == null){
// 待刪除的節(jié)點(diǎn)右子樹為空的情況
Node leftNode = node.left;
node.left = null;
size--;
retNode = leftNode;
}else {
// 左右子樹都不為空,則找出右子樹的最小值,即采用e 的后繼。然后用這個(gè)節(jié)點(diǎn)頂替待刪除節(jié)點(diǎn)的位置
Node successor = minimum(node.right);
//由于removeMin 并未對(duì)刪除元素之后進(jìn)行平衡檢查,所以要么加上,要不不用;
// 我們這里采用不用,直接使用remove,因?yàn)閟uccessor就是node.right子樹的最小值
// successor.right = removeMin(node.right);
successor.right = remove(node.right, successor.key);
successor.left = node.left;
node.left = null;
node.right = null;
//注意:這里不需要size--,因?yàn)閞emoveMin(node.right) 已經(jīng)操作了size--
retNode = successor;
}
}else if (key.compareTo(node.key) < 0){
node.left = remove(node.left, key);
retNode = node;
}else {
node.right = remove(node.right, key);
retNode = node;
}
if (retNode == null) return retNode;
// 更新 height,左右孩子中大的height + 1
retNode.height = 1 + Math.max(getHeight(retNode.left), getHeight(retNode.right));
// 計(jì)算平衡因子
int balanceFactory = getBalanceFactory(retNode);
// 不符合平衡二叉樹條件,需要平衡維護(hù)
if (balanceFactory > 1){
if (getBalanceFactory(retNode.left) >= 0){
// 插入的元素在不平衡節(jié)點(diǎn)的左側(cè)的左側(cè)(LL),采用右旋轉(zhuǎn)
return rightRotate(retNode);
}else{
// 插入的元素在不平衡節(jié)點(diǎn)的左側(cè)的右側(cè)(LR),采用先左旋轉(zhuǎn)再右旋轉(zhuǎn)
retNode.left = leftRotate(retNode.left);
return rightRotate(retNode);
}
}
if (balanceFactory < -1){
if (getBalanceFactory(retNode.right) <= 0){
// 插入的元素在不平衡節(jié)點(diǎn)的右側(cè)的右側(cè)(RR),采用左旋轉(zhuǎn)
return leftRotate(retNode);
}else {
// 插入的元素在不平衡節(jié)點(diǎn)的右側(cè)的左側(cè)(RL),采用右旋轉(zhuǎn)再左旋轉(zhuǎn)
retNode.right = rightRotate(retNode.right);
return leftRotate(retNode);
}
}
return retNode;
}
小結(jié):
至此,AVL的基本操作已經(jīng)介紹完了,由于它不會(huì)像二分搜索樹那樣退化成鏈表,所以它的添加元素和刪除元素的時(shí)間復(fù)雜度都是O(log n)。