上一篇文章讲了二叉搜索树,但是它会在极端情况下退化为链表,造成查找时间复杂度退化为
O
(
N
)
O(N)
O(N),那么怎样才能让它不退化为链表呢?这篇文章告诉你,快来看看把!
1. 平衡二叉树的定义
平衡说的是树的高度平衡,平衡二叉树可以这么定义:
- 一颗空树
- 如果不是空树,那么它的左子树和右子树都是平衡二叉树,且左右子树的高度差绝对值不超过1
树的高度怎么定义?
- 如果是空树,高度为0
- 如果不是空树,那高度就是左子树和右子树的高度最大值+1;
举个例子:
A的左子树高度3,右子树高度为1,左右子树高度差2,因此这不是平衡二叉树
又如: 虽然A的左子树和右子树的高度差是0,但是其左右子树都不是平衡二叉树,因此整棵树不平衡。
2. AVL树的调整
AVL树是一颗平衡的二叉搜索树,它与普通的二叉搜索树最大的区别就是它的平衡性,平衡性保证了二叉搜索树不会在某些情况下退化为链表,保证了最坏的查找时间复杂度为
O
(
N
)
O(N)
O(N),那么,AVL树如何保证平衡的呢?就是通过调整。那AVL树不平衡的时候,有哪些情况呢?下面一个一个看。
案例1: 如下图所示的二叉树
根节点1已经失去平衡了(左子树高度1,右子树高度3),现在我们需要做一个操作,使之平衡。这个操作不是只要保证这棵树再次平衡就可以了,还需要保证操作前后,树的中序遍历顺序不改变,就是说操作之后,整棵树还是一颗而二叉搜索树。因此我们要找到一个过程,它只调整数的结构,但不影响数的二叉搜索性质。 那如何操作呢?如下图所示: 这样操作以后,整棵树是平衡的了,那还是二叉搜索树吗?我们只需要看看操作前后的中序遍历结果是不是一个有序列表即可。 操作之前中序遍历顺序:3,4,5,6,7,8 操作之后中序遍历顺序:3,4,5,6,7,8 可见这样的操作是可行的,我们不妨给这个操作起个名字,叫左旋。这样的蜜汁操作是怎么想到的呢?灵感一现吗?不,它是有依据的,如上图所示,根据BST的特性,
- 结点6的父节点4,以及4的左子树3,都是小于结点6的,因此可以整体放在结点6的左子树的位置;
- 而结点6的左子树5,比6小,但是至少比其父节点4要大,因此可以同时作为结点4的右子树。
案例2: 如下图所示的情况 根结点8的左子树高度3,右子树高度1,这个情况其实跟上一个情况是对称的,调整这种情况的方式对应叫右旋,如下图所示: 中序遍历结果这里就不验证了哈,原理与左旋类似。
案例3: 如下图所示的情况 这个形状与案例2中的形状类似,结点9的左子树高度是3,右子树高度1,只不过结点9的左子树的左子树比结点9的左子树的右子树的高度低,如果我们可以把结点9的左子树进行一番操作,改成与案例2中一样的形状就可以按照案例2的情况处理了,因此我们可以先结点9的左子树左旋操作, 然后再按照案例2的方法右旋即可。
案例4: 最后一种情况 跟案例3类似,先对结点6右旋,再对结点3左旋即可。 我们对AVL树进行插入,删除以后,都需要检查树的平衡性,必要时,进行树的调整。
3. AVL树的实现
定义AVL树结点类
class AVLNode<T extends Comparable<T>> {
T data;
AVLNode<T> left;
AVLNode<T> right;
int height;
public AVLNode(T data) {
this.data = data;
this.height = 1;
}
}
定义AVL树实现类
public class AVLTree<T extends Comparable<T>> {
private AVLNode<T> rootNode;
private int size;
public int size() {
return size;
}
}
首先实现一些utils方法,
-
获取树结点的高度:
private int height(AVLNode<T> node) {
return node == null ? 0 : node.height;
}
-
更新树结点的高度
private void updateHeight(AVLNode<T> node) {
if (node != null) node.height = Math.max(height(node.left), height(node.right)) + 1;
}
-
获取树某个结点的平衡因子
private int balanceFactor(AVLNode<T> node) {
return node == null ? 0 : height(node.left) - height(node.right);
}
-
右旋操作
private AVLNode<T> rightRotate(AVLNode<T> node) {
AVLNode<T> left = node.left;
node.left = left.right;
left.right = node;
updateHeight(node);
updateHeight(left);
return left;
}
-
左旋操作
private AVLNode<T> leftRotate(AVLNode<T> node) {
AVLNode<T> right = node.right;
node.right = right.left;
right.left = node;
updateHeight(node);
updateHeight(right);
return right;
}
-
调整树 当某棵树的平衡因子:
- 大于1,则代表需要右旋操作,如果此时其左子树的平衡因子小于0,则代表需要先左旋;
- 小于-1,则代表需要左旋操作,如果此时其右子树的平衡因子大于0,则代表需要先右旋。
根据这样的逻辑,调整某个结点的代码如下:
private AVLNode<T> adjustAVLTree(AVLNode<T> root) {
if (root == null) return null;
int balanceFactor = balanceFactor(root);
if (Math.abs(balanceFactor) <= 1) return root;
if (balanceFactor > 1) {
if (balanceFactor(root.left) < 0) root.left = leftRotate(root.left);
root = rightRotate(root);
} else {
if (balanceFactor(root.right) > 0) root.right = rightRotate(root.right);
root = leftRotate(root);
}
return root;
}
-
找到某棵树最左的结点
private AVLNode<T> mostLeft(AVLNode<T> root) {
if (root == null) return null;
AVLNode<T> cur = root;
for (; cur.left != null; cur = cur.left) ;
return cur;
}
put,remove,find方法实现:
- put方法
与二叉搜索树插入类似,只不过这里需要树的调整:
public boolean put(T value) {
if (value == null) return false;
AVLNode<T> newRoot = put(this.rootNode, value);
if (newRoot == null) return false;
this.rootNode = newRoot;
return true;
}
private AVLNode<T> put(AVLNode<T> root, T value) {
if (root == null) {
++this.size;
return new AVLNode<>(value);
}
if (root.data.compareTo(value) == 0) return null;
if (value.compareTo(root.data) < 0) {
AVLNode<T> newLeft = put(root.left, value);
if (newLeft == null) return null;
root.left = newLeft;
} else {
AVLNode<T> newRight = put(root.right, value);
if (newRight == null) return null;
root.right = newRight;
}
updateHeight(root);
return adjustAVLTree(root);
}
- find方法
与二叉搜索树类似
public boolean find(T value) {
if (value == null || this.rootNode == null) return false;
AVLNode<T> cur = this.rootNode;
while (cur != null) {
if (cur.data.compareTo(value) == 0) return true;
if (value.compareTo(cur.data) < 0) cur = cur.left;
else cur = cur.right;
}
return false;
}
- remove方法
为了方便设计,定义了一个remove的返回值类型
private static class RemoveResult<T extends Comparable<T>> {
boolean success;
AVLNode<T> retNode;
public RemoveResult(boolean success, AVLNode<T> retNode) {
this.success = success;
this.retNode = retNode;
}
public RemoveResult(boolean success) {
this(success, null);
}
}
public boolean remove(T value) {
if (value == null || this.rootNode == null) return false;
RemoveResult<T> removeResult = remove(this.rootNode, value);
if (removeResult.success) this.rootNode = removeResult.retNode;
return removeResult.success;
}
private RemoveResult<T> remove(AVLNode<T> root, T value) {
if (root == null) return new RemoveResult<>(false);
if (root.data.compareTo(value) == 0) {
if (root.left == null || root.right == null) {
--size;
if (root.left == null) root = root.right;
else root = root.left;
} else {
AVLNode<T> leftMostOfRight = mostLeft(root.right);
leftMostOfRight.right = remove(root.right, leftMostOfRight.data).retNode;
leftMostOfRight.left = root.left;
root = leftMostOfRight;
}
} else if (value.compareTo(root.data) < 0) {
RemoveResult<T> removeLeft = remove(root.left, value);
if (!removeLeft.success) return removeLeft;
root.left = removeLeft.retNode;
} else {
RemoveResult<T> removeRight = remove(root.right, value);
if (!removeRight.success) return removeRight;
root.right = removeRight.retNode;
}
updateHeight(root);
return new RemoveResult<>(true, adjustAVLTree(root));
}
4. 完整代码,带测试程序
package com.victory.common.data_structure;
import java.util.HashSet;
import java.util.Scanner;
import java.util.Set;
public class AVLTree<T extends Comparable<T>> {
private AVLNode<T> rootNode;
private int size;
public int size() {
return size;
}
public boolean put(T value) {
if (value == null) return false;
AVLNode<T> newRoot = put(this.rootNode, value);
if (newRoot == null) return false;
this.rootNode = newRoot;
return true;
}
public boolean find(T value) {
if (value == null || this.rootNode == null) return false;
AVLNode<T> cur = this.rootNode;
while (cur != null) {
if (cur.data.compareTo(value) == 0) return true;
if (value.compareTo(cur.data) < 0) cur = cur.left;
else cur = cur.right;
}
return false;
}
public boolean remove(T value) {
if (value == null || this.rootNode == null) return false;
RemoveResult<T> removeResult = remove(this.rootNode, value);
if (removeResult.success) this.rootNode = removeResult.retNode;
return removeResult.success;
}
public void inOrder() {
inOrder(this.rootNode);
System.out.println();
}
private void inOrder(AVLNode<T> root) {
if (root != null) {
inOrder(root.left);
System.out.print(" " + root.data + " ");
inOrder(root.right);
}
}
private RemoveResult<T> remove(AVLNode<T> root, T value) {
if (root == null) return new RemoveResult<>(false);
if (root.data.compareTo(value) == 0) {
if (root.left == null || root.right == null) {
--size;
if (root.left == null) root = root.right;
else root = root.left;
} else {
AVLNode<T> leftMostOfRight = mostLeft(root.right);
leftMostOfRight.right = remove(root.right, leftMostOfRight.data).retNode;
leftMostOfRight.left = root.left;
root = leftMostOfRight;
}
} else if (value.compareTo(root.data) < 0) {
RemoveResult<T> removeLeft = remove(root.left, value);
if (!removeLeft.success) return removeLeft;
root.left = removeLeft.retNode;
} else {
RemoveResult<T> removeRight = remove(root.right, value);
if (!removeRight.success) return removeRight;
root.right = removeRight.retNode;
}
updateHeight(root);
return new RemoveResult<>(true, adjustAVLTree(root));
}
private AVLNode<T> mostLeft(AVLNode<T> root) {
if (root == null) return null;
AVLNode<T> cur = root;
for (; cur.left != null; cur = cur.left) ;
return cur;
}
private AVLNode<T> put(AVLNode<T> root, T value) {
if (root == null) {
++this.size;
return new AVLNode<>(value);
}
if (root.data.compareTo(value) == 0) return null;
if (value.compareTo(root.data) < 0) {
AVLNode<T> newLeft = put(root.left, value);
if (newLeft == null) return null;
root.left = newLeft;
} else {
AVLNode<T> newRight = put(root.right, value);
if (newRight == null) return null;
root.right = newRight;
}
updateHeight(root);
return adjustAVLTree(root);
}
private int height(AVLNode<T> node) {
return node == null ? 0 : node.height;
}
private void updateHeight(AVLNode<T> node) {
if (node != null) node.height = Math.max(height(node.left), height(node.right)) + 1;
}
private int balanceFactor(AVLNode<T> node) {
return node == null ? 0 : height(node.left) - height(node.right);
}
private AVLNode<T> rightRotate(AVLNode<T> node) {
AVLNode<T> left = node.left;
node.left = left.right;
left.right = node;
updateHeight(node);
updateHeight(left);
return left;
}
private AVLNode<T> leftRotate(AVLNode<T> node) {
AVLNode<T> right = node.right;
node.right = right.left;
right.left = node;
updateHeight(node);
updateHeight(right);
return right;
}
private AVLNode<T> adjustAVLTree(AVLNode<T> root) {
if (root == null) return null;
int balanceFactor = balanceFactor(root);
if (Math.abs(balanceFactor) <= 1) return root;
if (balanceFactor > 1) {
if (balanceFactor(root.left) < 0) root.left = leftRotate(root.left);
root = rightRotate(root);
} else {
if (balanceFactor(root.right) > 0) root.right = rightRotate(root.right);
root = leftRotate(root);
}
return root;
}
private static class AVLNode<T extends Comparable<T>> {
T data;
AVLNode<T> left;
AVLNode<T> right;
int height;
public AVLNode(T data) {
this.data = data;
this.height = 1;
}
}
private static class RemoveResult<T extends Comparable<T>> {
boolean success;
AVLNode<T> retNode;
public RemoveResult(boolean success, AVLNode<T> retNode) {
this.success = success;
this.retNode = retNode;
}
public RemoveResult(boolean success) {
this(success, null);
}
}
public static void main(String[] args) {
automationTest();
}
private static void artificialTest() {
AVLTree<Integer> integerAVLTree = new AVLTree<>();
Scanner sc = new Scanner(System.in);
String command = null;
int n;
while (true) {
command = sc.next();
if ("put".equals(command)) {
n = sc.nextInt();
if (integerAVLTree.put(n)) System.out.println(n + " 插入成功");
else System.out.println(n + " 插入失败");
}
if ("remove".equals(command)) {
n = sc.nextInt();
if (integerAVLTree.remove(n)) System.out.println(n + " 删除成功");
else System.out.println(n + " 删除失败");
}
if ("print".equals(command)) {
integerAVLTree.inOrder();
}
if ("find".equals(command)) {
n = sc.nextInt();
if (integerAVLTree.find(n)) System.out.println(n + " 存在当前树中");
else System.out.println(n + " 不存在当前树中");
}
if ("size".equals(command)) System.out.println("当前树的结点个数是:" + integerAVLTree.size());
if ("exit".equals(command)) break;
}
}
private static volatile boolean runFlag = true;
private static void automationTest() {
new Thread(() -> {
Set<Integer> integerSet = new HashSet<>();
AVLTree<Integer> integerAVLTree = new AVLTree<>();
int cnt = 0;
int command, number;
while (runFlag) {
command = (int) (Math.random() * 4) + 1;
number = (int) (Math.random() * Integer.MAX_VALUE) + 1;
switch (command) {
case 1://put
if (integerAVLTree.size() < 10000) {
boolean put = integerAVLTree.put(number);
if (integerSet.contains(number)) {
if (put) {
System.out.println("hash表中已经包含了:" + number + " 应该添加失败,但是添加成功了");
return;
}
} else {
if (!put) {
System.out.println("hash表中没有包含:" + number + " 应该添加成功,但是添加失败了");
return;
}
integerSet.add(number);
++cnt;
}
}
break;
case 2://remove
boolean remove = integerAVLTree.remove(number);
if (integerSet.contains(number)) {
if (!remove) {
System.out.println("hash表中已经包含了:" + number + " 应该删除成功,但是删除失败了");
return;
}
--cnt;
integerSet.remove(number);
} else {
if (remove) {
System.out.println("hash表中没有包含:" + number + " 应该删除失败,但是删除成功了");
return;
}
}
break;
case 3://find
if (integerSet.contains(number)) {
boolean b = integerAVLTree.find(number);
if (!b) {
System.out.println("hash表中已经包含了:" + number + " 应该查找成功,但是查找失败了");
return;
}
}
break;
case 4://size
if (integerAVLTree.size() != cnt) {
System.out.println("size应该是:" + cnt + ",但实际上它是" + integerAVLTree.size());
return;
}
break;
}
}
}).start();
Scanner scanner = new Scanner(System.in);
String next = scanner.next();
if ("exit".equals(next)) runFlag = false;
}
}
觉得文章不错的话,给个支持吧 ^_^
|