@
一、背景
二叉樹是一種常用的數據結構,更是實現衆多算法的一把利器。(可參考《自己動手作圖深入理解二叉樹、滿二叉樹及完全二叉樹》)
二分搜索樹(Binary Search Tree)做爲一種能實現快速定位查找的二叉樹也得到了廣泛應用(底層實現可參考《用一個圖書庫實例搞懂二分搜索樹的底層原理》)。
1 二分搜索樹是一顆二叉樹
2 二分搜索樹每個節點的左子樹的值都小於該節點的值,每個節點右子樹的值都大於該節點的值
3 任意一個節點的每棵子樹都滿足二分搜索樹的定義
- 但二分搜索樹也有其侷限性:比如我們給定[1,2,3,4,5,6,7]這樣的數據並按順序構成的二分搜索樹就褪化成了線性鏈表,二分搜索樹極度偏向右側,且深度達到7級,查找搜索的時間複雜度也從O(logn)褪化成了O(n).
二、平衡二分搜索樹---AVL樹
- 爲了解決二分搜索樹的不平衡性,科學家創造一種自平衡的二分搜索樹,這種樹也被簡稱爲AVL(G. M. Adelson-Velsky和E. M. Landis)樹,以下的圖即爲一棵AVL樹:
2.1 AVL樹的基本概念
每個結點的左右子樹的高度之差(平衡因子)不大於1的二分搜索樹,即爲AVL樹。
結點
- 結點是組成二叉樹的最小單元。
-- 用圖形表示
-- 用代碼表示
// 結點
class Node<E> {
E e;
Node left, right;
Node(E e) {
this.e= e;
this.left = null;
this.right = null;
}
}
高度
- 葉子結點高度默認爲1;非葉子結點的高度爲該結點能到達的左子樹或右子樹的葉子結點的最大跨度。
-- 用代碼描述
class Node<E> {
E e;
Node left, right;
// 高度
int height;
Node(E e) {
this.e = e;
this.left = null;
this.right = null;
// 葉子結點高度默認爲1
this.height = 1;
}
}
// 獲得節點node的高度
private int getHeight(Node node) {
if (node == null) {
return 0;
}
return node.height;
}
// 計算結點的高度
private void setHeight(Node node) {
node.height = Math.max(getHeight(node.left), getHeight(node.right)) + 1;
}
平衡因子
- 葉子結點的平衡因子爲0;非葉子結點的平衡因子爲該結點的左子結點或右子結點的高度差。
-- 用代碼描述
class Node<E> {
E e;
Node left, right;
// 高度
int height;
Node(E e) {
this.e = e;
this.left = null;
this.right = null;
// 葉子結點高度默認爲1
this.height = 1;
}
}
// 獲得節點node的高度
private int getHeight(Node node) {
if (node == null) {
return 0;
}
return node.height;
}
// 獲得節點node的平衡因子
private int getBalanceFactor(Node node){
if(node == null)
return 0;
return getHeight(node.left) - getHeight(node.right);
}
2.2 AVL樹的驗證
- 按AVL的定義,判斷一棵二叉樹是否爲AVL樹
- 首先需判斷這棵二叉樹是否爲二分搜索樹:即從根結點開始中序遍歷該二叉樹,形成的遍歷序列一定是按從小到大有序排列的。
- 其實判斷該二分搜索樹的每個結點的平衡因子的絕對值是否超過1。
-- 用代碼描述
/**
* AVL樹
* @param <E> 泛型元素
* @author zhuhuix
* @date 2020-07-21
*/
public class AVL<E extends Comparable<E>> {
// 私有內部類-樹結點
private class Node<E> {
E e;
Node left, right;
// 高度
int height;
Node(E e) {
this.e = e;
this.left = null;
this.right = null;
this.height = 1;
}
}
// 根結點
private Node root;
// 獲得節點node的高度
private int getHeight(Node node) {
if (node == null) {
return 0;
}
return node.height;
}
// 計算結點的高度
private int setHeight(Node node) {
return node.height = Math.max(getHeight(node.left), getHeight(node.right)) + 1;
}
// 獲得節點node的平衡因子
private int getBalanceFactor(Node node) {
if (node == null) {
return 0;
}
return getHeight(node.left) - getHeight(node.right);
}
// 增加元素
public void add(E e) {
root = addNode(root, e);
}
// 通過遞歸算法遍歷現有結點,將新結點插入到合適的位置
private Node addNode(Node node, E element) {
if (node == null) {
System.out.println("新增元素[" + element + "] height=1");
return new Node(element);
}
// 新加入元素小於結點值,往左子樹增加
if (element.compareTo((E) node.e) < 0) {
node.left = addNode(node.left, element);
// 新加入元素大於結點值,往右子樹增加
} else if (element.compareTo((E) node.e) > 0) {
node.right = addNode(node.right, element);
} else // element.compareTo(node.e) == 0
{
node.e = element;
}
// 更新height
node.height = setHeight(node);
System.out.println("元素[" + node.e + "] 更新高度: height=" + node.height);
return node;
}
// 判斷二叉樹是否爲二分搜索樹:從根結點中序遍歷形成的序列是否從小到大有序排列
public boolean isBST() {
ArrayList<E> arrayList = new ArrayList<>();
InOrderTraversal(root, arrayList);
for (int i = 0; i < arrayList.size() - 1; i++) {
// 相鄰兩個元素比較,如果前一個元素大於後一個元素,則不爲二分搜索樹
if (arrayList.get(i).compareTo(arrayList.get(i + 1)) > 0) {
return false;
}
}
System.out.println("中序遍歷:" + arrayList.toString());
return true;
}
// 通過中序遍歷形成序列
private void InOrderTraversal(Node node, ArrayList<E> arrayList) {
if (node == null) {
return;
}
InOrderTraversal(node.left, arrayList);
arrayList.add((E) node.e);
InOrderTraversal(node.right, arrayList);
}
// 判斷是否是一棵平衡二叉樹
public boolean isBalancedTree() {
return isBalanced(root);
}
// 通過遞歸遍歷判斷是否爲平衡二叉樹:判斷每個結點的平衡因子的絕對值是否有大於1的存在
private boolean isBalanced(Node node) {
if (node == null) {
return true;
}
// 獲取該結點的平衡因子,並判斷平衡因子的絕對值是否大於1
int balanceFactor = getBalanceFactor(node);
if (Math.abs(balanceFactor) > 1) {
System.out.println("元素["+node.e + "] 平衡因子=" + balanceFactor+",超過1");
System.out.println("元素["+node.e+ "] 左子樹的高度="+node.left.height+ ",右子樹的高度="+node.right.height);
return false;
}
// 遍歷判斷結點的左子樹和右子樹的各個結點
return isBalanced(node.left) && isBalanced(node.right);
}
public static void main(String[] args) {
// 定義一個數組
Integer[] arr = {48, 30, 66, 21, 34, 57, 78, 14};
// 將該數組構建成一個二分搜索樹
AVL<Integer> avl = new AVL<>();
for (int i = 0; i < arr.length; i++) {
avl.add(arr[i]);
}
// 判斷當前的二叉樹是否滿足二分搜索樹的定義
boolean isBST = avl.isBST();
boolean isBalance = avl.isBalancedTree();
// 判斷當前的樹是否滿足AVL定義
if (isBST && isBalance) {
System.out.println("該二叉樹滿足二分搜索樹及平衡的條件,是AVL樹!!!");
} else {
System.out.println("該二叉樹不是AVL樹;" + "是否滿足二分搜索樹條件:" + isBST + " ;是否滿足平衡條件:" + isBalance);
}
// 給該AVL樹加上一個結點,再次判斷是否判斷
avl.add(10);
// 判斷當前的二叉樹是否滿足二分搜索樹的定義
isBST = avl.isBST();
isBalance = avl.isBalancedTree();
// 判斷當前的樹是否滿足AVL定義
if (isBST && isBalance) {
System.out.println("該二叉樹滿足二分搜索樹及平衡的條件,是AVL樹!!!");
} else {
System.out.println("該二叉樹不是AVL樹;" + "是否滿足二分搜索樹條件:" + isBST + " ;是否滿足平衡條件:" + isBalance);
}
}
}
-
通過{48, 30, 66, 21, 34, 57, 78, 14}構建AVL樹
-
給以上AVL樹增加一個結點10,再次判斷該樹是否滿足AVL的定義
三、旋轉操作
往AVL樹中添加結點很可能會導致失去平衡,所以我們需要在每次插入結點後進行平衡的維護。破壞平衡性有如下四種情況:
3.1 L L--需要通過右旋操作
- 在結點的左子樹(L)的左孩子(L)添加新的結點,會導致失去平衡:
- 通過右旋操作(順時針轉)將平衡因子大於1的結點進行調整
- 完整動畫演示
- 代碼處理
// 右旋(順時針轉)
private Node rightRotate(Node y) {
Node x = y.left;
Node T = x.right;
// 向右旋轉過程
x.right = y;
y.left = T;
// 更新height
y.height = setHeight(y);
System.out.println("元素[" + y.e + "] 右旋後更新高度: height=" + y.height);
x.height = setHeight(x);
System.out.println("元素[" + x.e + "] 更新高度: height=" + x.height);
return x;
}
3.2 R R--需要通過左旋操作
- 在結點的右子樹(R)的右孩子(R)添加新的結點,會導致失去平衡:
- 通過左旋操作(逆時針轉)將平衡因子大於1的結點進行調整
- 完整動畫演示
- 代碼處理
// 左旋(逆時針轉)
private Node leftRotate(Node y) {
Node x = y.right;
Node T = x.left;
// 向左旋轉過程
x.left = y;
y.right = T;
// 更新height
y.height = setHeight(y);
System.out.println("元素[" + y.e + "] 左旋後更新高度: height=" + y.height);
x.height = setHeight(x);
System.out.println("元素[" + x.e + "] 更新高度: height=" + x.height);
return x;
}
3.3 L R--需要先通過左旋再右旋操作
- 在結點的左子樹(L)的右孩子(R)添加新的結點,會導致失去平衡:
- 先通過左子結點的左旋操作(逆時針轉)轉成LL形式,再通過右旋操作(順時針轉)將平衡因子大於1的結點進行調整
- 完整動畫演示
2.4 R L--需要先通過右旋再左旋操作
- 在結點的右子樹(R)的左孩子(L)添加新的結點,會導致失去平衡:
- 先通過右子結點的右旋操作(順時針轉)轉成RR形式,再通過左旋操作(逆時針轉)將平衡因子大於1的結點進行調整
- 完整動畫演示
四、AVL樹完整代碼實現
/**
* AVL樹
*
* @param <E> 元素
* @author zhuhuix
* @date 2020-07-21
*/
public class AVL<E extends Comparable<E>> {
// 私有內部類-樹結點
private class Node<E> {
E e;
Node left, right;
// 高度
int height;
Node(E e) {
this.e = e;
this.left = null;
this.right = null;
this.height = 1;
}
}
// 根結點
private Node root;
// 獲得節點node的高度
private int getHeight(Node node) {
if (node == null) {
return 0;
}
return node.height;
}
// 計算結點的高度
private int setHeight(Node node) {
return node.height = Math.max(getHeight(node.left), getHeight(node.right)) + 1;
}
// 獲得節點node的平衡因子
private int getBalanceFactor(Node node) {
if (node == null) {
return 0;
}
return getHeight(node.left) - getHeight(node.right);
}
// 右旋(順時針轉)
private Node rightRotate(Node y) {
Node x = y.left;
Node T = x.right;
// 向右旋轉過程
x.right = y;
y.left = T;
// 更新height
y.height = setHeight(y);
System.out.println("元素[" + y.e + "] 右旋後更新高度: height=" + y.height);
x.height = setHeight(x);
System.out.println("元素[" + x.e + "] 更新高度: height=" + x.height);
return x;
}
// 左旋(逆時針轉)
private Node leftRotate(Node y) {
Node x = y.right;
Node T = x.left;
// 向左旋轉過程
x.left = y;
y.right = T;
// 更新height
y.height = setHeight(y);
System.out.println("元素[" + y.e + "] 左旋後更新高度: height=" + y.height);
x.height = setHeight(x);
System.out.println("元素[" + x.e + "] 更新高度: height=" + x.height);
return x;
}
// 增加元素
public void add(E e) {
root = addNode(root, e);
}
// 通過遞歸算法遍歷現有結點,將新結點插入到合適的位置
private Node addNode(Node node, E element) {
if (node == null) {
System.out.println("新增元素[" + element + "] height=1");
return new Node(element);
}
// 新加入元素小於結點值,往左子樹增加
if (element.compareTo((E) node.e) < 0) {
node.left = addNode(node.left, element);
// 新加入元素大於結點值,往右子樹增加
} else if (element.compareTo((E) node.e) > 0) {
node.right = addNode(node.right, element);
} else // element.compareTo(node.e) == 0
{
node.e = element;
}
// 更新height
node.height = setHeight(node);
System.out.println("元素[" + node.e + "] 更新高度: height=" + node.height);
// 計算平衡因子
int balanceFactor = getBalanceFactor(node);
if (node != null) {
System.out.println("元素[" + node.e + "] "
+ "左子結點爲:[" + (node.left == null ? "" : node.left.e) + "]"
+ "右子結點爲:[" + (node.right == null ? "" : node.right.e) + "]"
+ ",balanceFactor=" + balanceFactor);
}
// 平衡維護
if (balanceFactor > 1 && getBalanceFactor(node.left) >= 0) {
System.out.println("元素[" + node.e + "] balanceFactor=" + balanceFactor + ",進行右旋");
return rightRotate(node);
}
if (balanceFactor < -1 && getBalanceFactor(node.right) <= 0) {
System.out.println("元素[" + node.e + "] balanceFactor=" + balanceFactor + ",進行左旋");
return leftRotate(node);
}
if (balanceFactor > 1 && getBalanceFactor(node.left) < 0) {
System.out.print("元素[" + node.e + "] balanceFactor=" + balanceFactor + " 先將[" + node.e + "的左子結點" + node.left.e + "] 進行左旋");
node.left = leftRotate(node.left);
System.out.println("再將元素[" + node.e + "] 進行右旋");
return rightRotate(node);
}
if (balanceFactor < -1 && getBalanceFactor(node.right) > 0) {
System.out.print("元素[" + node.e + "] balanceFactor=" + balanceFactor + " 先將[" + node.e + "的右子結點" + node.right.e + "] 進行右旋");
node.right = rightRotate(node.right);
System.out.println("再將元素[" + node.e + "] 進行左旋");
return leftRotate(node);
}
return node;
}
// 判斷二叉樹是否爲二分搜索樹:從根結點中序遍歷形成的序列是否從小到大有序排列
public boolean isBST() {
ArrayList<E> arrayList = new ArrayList<>();
InOrderTraversal(root, arrayList);
for (int i = 0; i < arrayList.size() - 1; i++) {
// 相鄰兩個元素比較,如果前一個元素大於後一個元素,則不爲二分搜索樹
if (arrayList.get(i).compareTo(arrayList.get(i + 1)) > 0) {
return false;
}
}
System.out.println("中序遍歷:" + arrayList.toString());
return true;
}
// 通過中序遍歷形成序列
private void InOrderTraversal(Node node, ArrayList<E> arrayList) {
if (node == null) {
return;
}
InOrderTraversal(node.left, arrayList);
arrayList.add((E) node.e);
InOrderTraversal(node.right, arrayList);
}
// 前序遍歷打印
public void preOrderTraversal() {
ArrayList<E> arrayList = new ArrayList<>();
preOrderTraversal(root, arrayList);
System.out.println("前序遍歷" + arrayList);
}
// 通過前序遍歷形成序列
private void preOrderTraversal(Node node, ArrayList<E> arrayList) {
if (node == null) {
return;
}
arrayList.add((E) node.e);
preOrderTraversal(node.left, arrayList);
preOrderTraversal(node.right, arrayList);
}
// 判斷是否是一棵平衡二叉樹
public boolean isBalancedTree() {
return isBalanced(root);
}
// 通過遞歸遍歷判斷是否爲平衡二叉樹:判斷每個結點的平衡因子的絕對值是否有大於1的存在
private boolean isBalanced(Node node) {
if (node == null) {
return true;
}
// 獲取該結點的平衡因子,並判斷平衡因子的絕對值是否大於1
int balanceFactor = getBalanceFactor(node);
if (Math.abs(balanceFactor) > 1) {
System.out.println("元素[" + node.e + "] 平衡因子=" + balanceFactor + ",超過1");
System.out.println("元素[" + node.e + "] 左子樹的高度=" + node.left.height + ",右子樹的高度=" + node.right.height);
return false;
}
// 遍歷判斷結點的左子樹和右子樹的各個結點
return isBalanced(node.left) && isBalanced(node.right);
}
public static void main(String[] args) {
// 定義一個數組
Integer[] arr = {48, 30, 66, 21, 34, 57, 78, 14};
// 將該數組構建成一個二分搜索樹
AVL<Integer> avl = new AVL<>();
for (int i = 0; i < arr.length; i++) {
avl.add(arr[i]);
}
// 判斷當前的二叉樹是否滿足二分搜索樹的定義
boolean isBST = avl.isBST();
boolean isBalance = avl.isBalancedTree();
// 判斷當前的樹是否滿足AVL定義
if (isBST && isBalance) {
System.out.println("該二叉樹滿足二分搜索樹及平衡的條件,是AVL樹!!!");
} else {
System.out.println("該二叉樹不是AVL樹;" + "是否滿足二分搜索樹條件:" + isBST + " ;是否滿足平衡條件:" + isBalance);
}
// 給該AVL樹加上一個結點,再次判斷是否判斷
avl.add(10);
// 判斷當前的二叉樹是否滿足二分搜索樹的定義
isBST = avl.isBST();
isBalance = avl.isBalancedTree();
// 判斷當前的樹是否滿足AVL定義
if (isBST && isBalance) {
System.out.println("該二叉樹滿足二分搜索樹及平衡的條件,是AVL樹!!!");
} else {
System.out.println("該二叉樹不是AVL樹;" + "是否滿足二分搜索樹條件:" + isBST + " ;是否滿足平衡條件:" + isBalance);
}
}
}
- 構建AVL樹過程
- 添加結點AVL樹平衡過程