平衡二叉树
平衡二叉树: 平衡二叉树也叫平衡二叉搜索树 (Self-balancing Binary Search Tree) 又被称为 AVL 树,可以保证查询效率较高。平衡二叉树它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。平衡二叉树的常用实现方法有红黑树、AVL、替罪羊树、Treap、伸展树等。
左旋转
举例说明,下面这颗二叉树的左子树的深度为 1 ,右子树的深度为 3, 相差的绝对值超过了 1 ,通过左旋转达到平衡。
步骤:
-
创建新的结点(newNode),新结点的 value 是当前结点的 value
-
把新结点的左子结点设置为当前结点的左子结点
-
把新结点的右子结点设置为当前结点的右子树的左子结点
-
把当前结点的 value 设置为当前结点的右子结点的 value
-
当前结点的右子结点指向当前结点的右子结点的右子结点
-
当前结点的左子结点指向新的结点
之前的结点 4 就没有结点指向它,回收机制回收后平衡二叉树就调整完成,如下
代码
public void leftRotation() {
// 如果需要双旋转,即右子树的的 左子树的深度 > 右子树的深度,就需要先将右子树右旋转
if (right.leftHeight() > right.rightHeight()) {
right.rightRotation();
}
// 创建新的结点,新结点的 value 是当前结点的 value
Node node = new Node(value);
// 把新结点的左子结点设置为当前结点的左子结点
node.left = left;
// 把新结点的右子结点设置为当前结点的右子树的左子结点
node.right = right.left;
// 把当前结点的 value 设置为当前结点的右子结点的 value
value = right.value;
// 当前结点的右子结点指向当前结点的右子结点的右子结点
right = right.right;
// 当前结点的左子结点指向新的结点
left = node;
}
右旋转
举例说明,下面这颗二叉树的右子树的深度为 1 ,左子树的深度为 3, 相差的绝对值超过了 1 ,通过右旋转达到平衡。
步骤(和左旋转步骤对称,图就省略):
- 创建新的结点, value 是当前结点的 value
- 把新的结点的右子结点设置为当前结点的右子结点
- 把新的结点的左子结点设置为当前结点的左子树的右子结点
- 把当前的结点的 value 设置为当前结点的左子结点的 value
- 把当前结点的左子结点指向当前结点的左子结点的左子结点
- 把当前结点的右子结点指向新的结点
代码
public void rightRotation() {
// 如果需要双旋转,即左子树的的 右子树的深度 > 左子树的深度,就需要先将左子树左旋转
if (left.rightHeight() > left.leftHeight()) {
left.leftRotation();
}
// 创建新的结点, value 是当前结点的 value
Node node = new Node(value);
// 把新的结点的右子结点设置为当前结点的右子结点
node.right = right;
// 把新的结点的左子结点设置为当前结点的左子树的右子结点
node.left = left.right;
// 把当前的结点的 value 设置为当前结点的左子结点的 value
value = left.value;
// 把当前结点的左子结点指向当前结点的左子结点的左子结点
left = left.left;
// 把当前结点的右子结点指向新的结点
right = node;
}
双旋转
如果遇到下面这两种情况
-
左子树的的 右子树的深度 > 左子树的深度,就需要先将左子树左旋转
-
右子树的的 左子树的深度 > 右子树的深度,就需要先将右子树右旋转
完整代码
下面代码在二叉排序树的基础上添加平衡树的方法
public class AVLTreeDemo {
public static void main(String[] args) {
int[] arr = { 10, 11, 7, 6, 8, 9 };
AVLTree avlTree = new AVLTree();
for(int i = 0; i < arr.length; i++) {
avlTree.add(new Node(arr[i]));
}
/* 再添加最后元素 9 时,该二叉平衡树转化如下(10 的左子树先左旋转, 以 10 为根节点的树再右旋转)
10 10 8
* * * * * *
7 11 8 11 7 10
* * => * * => * * *
6 8 7 9 6 9 11
* *
9 6
*/
System.out.println("-------------------中序遍历--------------------");
avlTree.infixOrder();
System.out.println();
System.out.println("根节点:" + avlTree.root);
System.out.println("8 left--> " + avlTree.root.left);
System.out.println("8 right--> " + avlTree.root.right);
System.out.println("7 left--> " + avlTree.root.left.left);
System.out.println("10 left--> " + avlTree.root.right.left);
System.out.println("10 right--> " + avlTree.root.right.right);
}
}
class AVLTree {
Node root;
// 向平衡二叉树中中添加结点法
public void add(Node node) {
if (root != null) {
root.add(node);
} else {
root = node;
}
}
// 中序遍历
public void infixOrder() {
if (root != null) {
root.infixOrder();
} else {
System.out.println("空树");
}
}
}
class Node {
int value;
Node left;
Node right;
public Node(int value) {
this.value = value;
}
@Override
public String toString() {
return value + " ";
}
// 该结点为根节点的树的高度
public int height() {
return Math.max(left == null ? 0 : left.height(), right == null ? 0 : right.height()) + 1;
}
// 左子树的深度
public int leftHeight() {
if (left == null) {
return 0;
} else {
return left.height();
}
}
// 右子树的深度
public int rightHeight() {
if (right == null) {
return 0;
} else {
return right.height();
}
}
// 左旋转的方法
public void leftRotation() {
// 如果需要双旋转,即右子树的的 左子树的深度 > 右子树的深度,就需要先将右子树右旋转
if (right.leftHeight() > right.rightHeight()) {
right.rightRotation();
}
// 创建新的结点,新结点的 value 是当前结点的 value
Node node = new Node(value);
// 把新结点的左子结点设置为当前结点的左子结点
node.left = left;
// 把新结点的右子结点设置为当前结点的右子树的左子结点
node.right = right.left;
// 把当前结点的 value 设置为当前结点的右子结点的 value
value = right.value;
// 当前结点的右子结点指向当前结点的右子结点的右子结点
right = right.right;
// 当前结点的左子结点指向新的结点
left = node;
}
// 右旋转的方法
public void rightRotation() {
// 如果需要双旋转,即左子树的的 右子树的深度 > 左子树的深度,就需要先将左子树左旋转
if (left.rightHeight() > left.leftHeight()) {
left.leftRotation();
}
// 创建新的结点, value 是当前结点的 value
Node node = new Node(value);
// 把新的结点的右子结点设置为当前结点的右子结点
node.right = right;
// 把新的结点的左子结点设置为当前结点的左子树的右子结点
node.left = left.right;
// 把当前的结点的 value 设置为当前结点的左子结点的 value
value = left.value;
// 把当前结点的左子结点指向当前结点的左子结点的左子结点
left = left.left;
// 把当前结点的右子结点指向新的结点
right = node;
}
// 向平衡二叉树中添加结点
public void add(Node node) {
if (node == null) {
return;
}
// 添加的结点小于当前结点
if (node.value < this.value) {
if (this.left == null) {
this.left = node;
} else { // 向左边递归添加
this.left.add(node);
}
} else { // 添加的结点大于当前结点
if (this.right == null) {
this.right = node;
} else { // 向右边递归添加
this.right.add(node);
}
}
// 如果该树的 右子树的高度 - 左子树的高度 > 1 时,需要左旋转达到平衡
if (rightHeight() - leftHeight() > 1) {
leftRotation();
} else if (leftHeight() - rightHeight() > 1) { // 如果该树的 左子树的高度 - 右子树的高度 > 1 时,需要右旋转达到平衡
rightRotation();
}
}
// 中序遍历
public void infixOrder() {
if (this.left != null) {
this.left.infixOrder();
}
System.out.print(this);
if (this.right != null) {
this.right.infixOrder();
}
}
}
运行结果
-------------------中序遍历--------------------
6 7 8 9 10 11
根节点:8
8 left--> 7
8 right--> 10
7 left--> 6
10 left--> 9
10 right--> 11