vlambda博客
学习文章列表

动态查找表---平衡二叉树

问题:

对于二叉排序树,假如给出一个{1,2,3,4,5,6}这样的数列,那么该树的一侧便会过长,一侧会过短,导致查询效率过低,为了解决这样的问题,提出了平衡二叉树。


平衡二叉树:

平衡二叉树也叫做平衡二叉搜索(查找)树,又被称为AVL树。具有如下性质:

(1) 它必须是二叉搜索(查找)树

(2) 它的每个节点的左右子树的高度之差都不超过1,左右子树都是一颗平衡二叉树。


平衡二叉树的常用实现方法有红黑树AVL替罪羊树Treap伸展树等。(这里的AVL指的是算法,AVL算法,不是AVL树的意思)




调整方式:


一、单旋 (左旋、右旋)


1、左旋

(1)创建一个新的节点newNode,创建一个新的节点,值等于当前根节点的值
(2)把新节点的左子树设置为当前节点的左子树
newNode.left = left
(3)把新节点的右子树设置为当前节点的右子树的左子树
newNode.right = right.left
(4)把当前节点的值换为右子节点的值
value = right.value
(5)把当前节点的右子树设置为右子树的右子树
right = right.right
(6)把当前节点的左子树设置为新节点
left = newNode


2、右旋

动态查找表---平衡二叉树

(1)创建一个新的节点newNode,创建一个新的节点,值等于当前根节
(2)把新节点的右子树设置为当前节点的右子树
newNode.right = right
(3)把新节点的左子树设置为当前节点的左子树的右子树
newNode.left = left.right
(4)把当前节点的值换为左子节点的值
value = left.value
(5)把当前节点的左子树设置为左子树的左子树
left = left.left
(6)把当前节点的右子树设置为新节点
right = newNode


二、双旋

我们会发现,对于有些树,单旋是无法解决问题的:

动态查找表---平衡二叉树

问题分析:
当符合右旋的条件时(左旋同理),如果它的左子树的右子树高度大于它的右子树高度,假设左子树的高度为x,右子树的高度为(x-2),左子树的左子树高度(x-2),左子树的右子树高度(x-1)
右旋后:左子树的高度为x-2,右子树的高度x((x-1)+1=x),仍不满足平衡二叉树

解决:先对这个结点的左节点进行左旋转,再对当前结点进行右旋转即可。



代码实现:


package cn.wangqin;
public class Demo { public static void main(String[] args) {        int[] arr = {4,3,6,5,7,8};        //int[] arr = {10,12,8,9,7,6}; //int[] arr = {10,11,7,6,8,9};
AVLTree at = new AVLTree();
for(int i=0;i<arr.length;i++) { at.addNodeTree(new Node(arr[i])); }
at.treeInOrder();
System.out.println(at.getRoot().height()); System.out.println(at.getRoot().leftHeight()); System.out.println(at.getRoot().rightHeight()); }}
class AVLTree { private Node root;
public Node getRoot() { return root; }
public void addNodeTree(Node node) { if (root == null) { root = node; }else { root.nodeAdd(node); } }
//通过中序方式遍历 public void treeInOrder() { if(root == null) { return; }else { root.inOrder(); } }
}
class Node { int value; Node left; Node right;
public Node(int value) { this.value = value; }
@Override public String toString() { return "Node{" + "value=" + value + '}'; }
//递归形式添加节点,注意要满足二叉排序树 public void nodeAdd(Node node) { if(node == null) { return; }
if(node.value < this.value) { if(this.left == null) { this.left = node; }else { this.left.nodeAdd(node); } }else { if (this.right == null) { this.right = node; }else{ this.right.nodeAdd(node); } }
//添加节点后,判断是否需要旋转 if(rightHeight()-leftHeight()>1) {            //是否需要双旋进行判断            if(left!=null && left.rightHeight()>left.rightHeight()) {                right.leftRotate(); leftRotate();            }else { leftRotate(); }
//必须有,已经平衡后就不需要向下判断了,也防止其它情况发生 return; }
if(leftHeight()-rightHeight()>1) {            //是否需要双旋进行判断 //如果它的左子树的右子树高度大于它的左子树的高度 if(left!=null && left.rightHeight()>left.rightHeight()) { //先对当前节点的左节点(左子树)-->左旋转 left.leftRotate(); //再对当前节点进行右旋转 rightRotate(); }else { //直接进行右旋转即可 rightRotate(); } } }
public void inOrder() { if(this.left != null) { this.left.inOrder(); } System.out.println(this); if (this.right != null) { this.right.inOrder(); } }

//返回左子树高度 public int leftHeight() { if(this.left == null) { return 0; }else { return this.left.height(); } }
//返回右子树高度 public int rightHeight() { if(this.right == null) { return 0; }else { return this.right.height(); } }
//返回以当前节点为根节点的树的高度:每递归一次高度加1,到最后一个叶子节点便不递归 public int height() { return Math.max(this.left==null?0:this.left.height(),this.right==null?0:this.right.height())+1; }
//左旋转 public void leftRotate() { Node newNode = new Node(this.value); newNode.left = this.left; newNode.right = this.right.left; this.value =this.right.value; this.right = this.right.right; this.left = newNode; }
//右旋转 public void rightRotate() { Node newNode = new Node(this.value); newNode.right = this.right; newNode.left = this.left.right; this.value =this.left.value; this.left = this.left.left; this.right = newNode; }}




疑惑解答:递归中的this代表哪个节点?


在Node类中的addNode() 方法中,有一段左右高度判断代码:

rightHeight()-leftHeight()>1

虽然没有加this,但是我们知道其实就是this.rightHeight()。那么递归中this代表哪个数据项呢?


以{10,12,8,9,7,6}为例,每次从根节点开始添加完节点后,其实是这样进行高度比较的,我们首先在addNode()方法中添加代码:

System.out.println(this);

然后输出:10,10,8,10,8,10,7,8,10


其实应该分割成:10,10,(8,10),(8,10),(7,8,10)


以添加叶子节点6为例:(输出:7,8,10)

(1) 从root开始递归,然后最后添加6,递归过程中不会执行高度判断代码,直到执行到最后叶子节点。


(2) 首先this代表7,然后才向下执行左右高度判断代码,左右子树符合平衡条件

(3) 然后返回上一级的递归,此时this代表8,也执行高度高度判断代码

(4) 然后再继续返回上一级的递归,此时thi代表10,也执行高度高度判断代码,不符合平衡条件,这时候才会进行旋转调整。

也就是添加节点后,节点从下向上开始判断左右平衡,不平衡就旋转调整

因此输出this,会出现7,8,10这种从下到上这种情况。