十一、平衡二叉树(AVL Tree)
1、概念
先看一个问题,如果给二叉排序树添加节点时是顺序的,那么创建出来的二叉排序树可能是一个单向链表,一直左子树或一直右子树,这样就失去了二叉树的优势(父节点可以含有两个子节点),而且树的高度可能会很大,查询效率会大大降低。
1 9
2 8
3 7
4 6
5 5
为了避免这种现象,产生了平衡二叉树。
又叫平衡二叉搜索树(Self-balancing binary search tree),特点是它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
常用的有:AVL Tree、红黑树、Treap Tree、替罪羊树和伸展树等。
2、方法
为了保证子树的高度差的绝对值不超过1,需要在给二叉树添加节点的时候,判断子树的高度差,一旦超过1,就需要通过左旋转或右旋转,来平衡左右子树的高度。
哪边子树高度较低,就向哪边旋转。
左旋转:
- 创建一个临时节点,节点的值是父节点的值。
- 将临时节点的左子节点指向父节点的左子节点。
- 将临时节点的右子节点指向父节点的右子节点的左子节点。
- 将父节点的值设置为右子节点的值。
- 将父节点的右子节点指向父节点的右子节点的右子节点。
- 将父节点的左子节点指向临时节点。
右旋转:
- 创建一个临时节点,节点的值是父节点的值。
- 将临时节点的右子节点指向父节点的右子节点。
- 将临时节点的左子节点指向父节点的左子节点的右子节点。
- 将父节点的值设置为左子节点的值。
- 将父节点的左子节点指向父节点的左子节点的左子节点。
- 将父节点的右子节点指向临时节点。
注意:
- 进行过左旋转后,就不要再进行右旋转。反之也是。
- 有时单纯地左旋转或右旋转,并不行保证树的结构是平衡二叉树,这就需要在左旋转之前先判断右子树的两个子树高度,一旦右子树的两个子树的高度不一致,则需要先对右子树进行右旋转。同样地,在右旋转之前,也需要判断左子树的两个子树的高度,是否一致,如果不一致,则需要先对左子树进行左旋转。
3、示例
class AvlTree<T extends Comparable<T>> {
private TreeNode<T> root;
public void addTreeNode(TreeNode<T> node) {
if (null == root) {
root = node;
return;
}
root.addNode(node);
}
public void infixOrder() {
Stack<TreeNode<T>> stack = new Stack<TreeNode<T>>();
TreeNode<T> node = root;
while (!stack.isEmpty() || null != node) {
if (null != node) {
stack.push(node);
node = node.getLeft();
} else {
node = stack.pop();
System.out.print(node.getData() + " ");
node = node.getRight();
}
}
System.out.println();
}
public TreeNode<T> getRoot() {
return root;
}
}
class TreeNode<T extends Comparable<T>> {
private T data;
private TreeNode<T> left;
private TreeNode<T> right;
public TreeNode(T data) {
this.data = data;
}
public void addNode(TreeNode<T> node) {
if (0 < this.data.compareTo(node.data)) {
if (null == this.left) {
this.left = node;
return;
}
this.left.addNode(node);
} else {
if (null == this.right) {
this.right = node;
return;
}
this.right.addNode(node);
}
// 右子树的高度和左子树的高度相差超过1,进行左旋转
if (1 < (this.rightHeight() - this.leftHeight())) {
// 存在右子树,且右子树的两个子树高度不一致
if (null != right && right.rightHeight() < right.leftHeight()) {
right.rightRotate(); // 需要先对右子树进行右旋转
}
this.leftRotate();
// 返回
return;
}
// 左子树的高度和右子树的高度相差超过1,进行右旋转
if (1 < (this.leftHeight() - this.rightHeight())) {
// 存在左子树,且左子树的两个子树高度不一致
if (null != left && left.leftHeight() < left.rightHeight()) {
left.leftRotate(); // 需要先对左子树进行左旋转
}
this.rightRotate();
}
}
/**
* 节点子树的深度
*/
public int height() {
int leftHeight = 0;
int rightHeight = 0;
if (null != this.left) {
leftHeight = this.left.height();
}
if (null != this.right) {
rightHeight = this.right.height();
}
return ((leftHeight >= rightHeight) ? leftHeight : rightHeight) + 1;
}
public int leftHeight() {
if (null == this.left) {
return 0;
}
return this.left.height();
}
public int rightHeight() {
if (null == this.right) {
return 0;
}
return this.right.height();
}
/**
* 节点左旋转
*/
public void leftRotate() {
TreeNode<T> tempNode = new TreeNode<T>(this.data);
tempNode.left = this.left;
tempNode.right = this.right.left;
this.data = this.right.data;
this.right = this.right.right;
this.left = tempNode;
}
/**
* 节点右旋转
*/
public void rightRotate() {
TreeNode<T> tempNode = new TreeNode<T>(this.data);
tempNode.right = this.right;
tempNode.left = this.left.right;
this.data = this.left.data;
this.left = this.left.left;
this.right = tempNode;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public TreeNode<T> getLeft() {
return left;
}
public void setLeft(TreeNode<T> left) {
this.left = left;
}
public TreeNode<T> getRight() {
return right;
}
public void setRight(TreeNode<T> right) {
this.right = right;
}
}
class Test {
public static void main(String[] args) {
// Integer[] array = { 1, 2, 3, 4, 5 };
Integer[] array = { 2, 1, 6, 5, 7, 3 };
AvlTree<Integer> avlTree = new AvlTree<Integer>();
for (int i = 0, len = array.length; i < len; i++) {
avlTree.addTreeNode(new TreeNode<Integer>(array[i]));
}
avlTree.infixOrder();
TreeNode<Integer> root = avlTree.getRoot();
System.out.println(root.height()); // 二叉树的深度
System.out.println(root.getLeft().height()); // 左子树的深度
System.out.println(root.getRight().height()); // 右子树的深度
}
}