平衡二叉树
二叉查找树的问题
当我们依次添加n个有序键值对的时候,二叉查找树的高度为n,这样就大大降低了查找的效率,因为每次查找还要判断每个结点中一个空的链接,甚至比单链表还要慢,为了解决这个问题,我们希望构造的二叉查找树的高度为~lgN,这就是我们要说的平衡二叉树。
简单介绍
1)平衡二叉树也叫做平衡二叉查找树(Self-balancing binary search tree)又称为AVL树,可以保证查询效率高。
2)具有以下特点:它的左右两个子树的高度差绝对值不超过1,并且两个子树都是平衡二叉树。平衡二叉树的常用实现方法有红黑树、AVL、替罪羊树、Treap、伸展树等。
构建平衡二叉树
要构造平衡二叉树,首先我们需要知道当前节点左右子树的深度,先构造个方法返回左右子树高度。
//返回左子树的高度
public int leftHeight(TreeNode root){
return height(root.left);
}
public int leftHeight(){
return height(root.left);
}
//返回右子树的高度
private int rightHeight(TreeNode root){
return height(root.right);
}
public int rightHeight(){
return height(root.right);
}
//返回树的高度
private int height(TreeNode root){
if(root == null) return 0;
return Math.max(height(root.left) + 1,height(root.right) + 1);
}
public int height(){
if(root == null) return 0;
return Math.max(height(root.left) + 1,height(root.right) + 1);
}
知道了高度以后,就是对树结构的操作了,这里介绍一下左右旋转。
单旋转
左旋转
ps:以下说的根结点指要进行旋转的树或子树的根结点
- 创建一个新的结点,它的值为根结点的值
- 将新节点的左连接指向根结点的左结点
- 将新节点的右连接指向根结点的右结点的左节点
- 把根结点的值换位根结点的右子结点的值
- 根结点的左链接指向新节点
- 根结点的右链接指向根结点右子节点的右子节点
右旋转
- 创建一个新的结点,它的值为根结点的值
- 将新节点的右连接指向根结点的右结点
- 将新节点的左连接指向根结点的左结点的右节点
- 把根结点的值换位根结点的左子结点的值
- 根结点的右链接指向新节点
- 根结点的左链接指向根结点左子节点的左子节点
当树的左子树高度 - 右子树高度 > 1时,就需要进行右旋转,相反,如果右子树高度 - 左子树高度 > 1,就要进行左旋转。
例如:
但是目前的旋转还有一个问题,例如:给定int[] key{7,6,10,8,11,9},这样进行左旋转以后还是不平衡,所以我们少考虑了其他情况。
双旋转
左旋转
设right为要进行旋转的根结点的右子结点,如果right的左子树比右子树高度高时,我们需要先对right进行一次右旋转,再对根结点进行左旋转。
右旋转
设left为要进行右旋转的左子结点,如果left的右子树比左子树高度高,我们需要先队left进行左旋转,再对根结点进行右旋转。
代码
根据以上的分析,我们代码实现对平衡二叉树的构造
首先我们创建单旋转的两个方法。
public void leftRotate(TreeNode root){
TreeNode newNode = new TreeNode(root.key,root.val);
newNode.left = root.left;
newNode.right = root.right.left;
root.key = root.right.key;
root.val = root.right.val;
root.left = newNode;
root.right = root.right.right;
}
//右旋转
public void rightRotate(TreeNode root){
TreeNode newNode = new TreeNode(root.key,root.val);
newNode.right = root.right;
newNode.left = root.left.right;
root.key = root.left.key;
root.val = root.left.val;
root.right = newNode;
root.left = root.left.left;
}
其次构造一个调整二叉查找树的方法
//调整二叉查找树
public void reCreate(TreeNode root){
if((rightHeight(root) - leftHeight(root)) > 1){//右子树高度比左子树高度大1以上
if(leftHeight(root.right) > rightHeight(root.right)){//如果右结点的左子树比右结点的右子树高度大,先对右结点右旋转
rightRotate(root.right);
}
leftRotate(root);
}else if((leftHeight(root) - rightHeight(root)) > 1){//左子树高度比右子树高度大1以上
if(rightHeight(root.left) > leftHeight(root.left)){
leftRotate(root.left);
}
rightRotate(root);
}
}
当我们为二叉树添加节点时,在返回新的根结点之前我们对二叉查找树进行一次调整,二叉查找树的添加操作之前我们已经说过,这里之间拿之前的代码在返回值之前调用一些reCreate就行了。
//搜索树添加结点方法
public void put(Key key,Value val){
root = put(root,key,val);
}
private TreeNode put(TreeNode root, Key key, Value val){
//如果key存在于以root为根结点的树中,则更新value值,否则将key value插入到树中
if(root == null) return new TreeNode(key,val);
if(root.key.compareTo(key) > 0) root.left = put(root.left,key,val);
else if(root.key.compareTo(key) < 0) root.right = put(root.right,key,val);
else root.val = val;
reCreate(root);
return root;
}