1.简介
1.二叉排序树的问题:
如果原始是数据是排好序的(如1,2,3,4,5,6),那么最终创建的二叉排序树的结构就会变成一条斜线,类似于一条单链表,此时如果需要查找/插入某个元素就要一个一个元素的比较,这样就没有优势了.由于每次都要比较左子树,其查询速度甚至比单链表还慢;
2.对于任意二叉排序树中任意一个非叶子节点,左右两棵子树的高度差的绝对值不超过1
,这样的二叉排序树就是平衡二叉树或者平衡二叉搜索树(Self-balancing binary search tree),平衡二叉树又被称为AVL树;
如图:
3.平衡二叉树中可以保证查询效率较高
;
4.平衡二叉树的常用实现方法有红黑树、AVL、替罪羊树、Treap、伸展树
等;
2.案例-(左/右)单旋转
1.要求:
给定数列 {4,3,6,5,7,8},创建出对应的平衡二叉树;
给定数列 {10,12,8,9,7,6},创建出对应的平衡二叉树;
2.左旋转思路分析:
3.右旋转思路分析:
3.代码实现
//节点对象
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 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;
}
/**
* 左旋转
*/
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;
}
/**
* 返回右子树的高度
*
* @return
*/
public int rightHeight() {
if (this.right == null) {
return 0;
}
return this.right.height();
}
/**
* 返回左子树的高度
*
* @return
*/
public int leftHeight() {
if (this.left == null) {
return 0;
}
return this.left.height();
}
/**
* 返回该节点为根节点的树的高度
*
* @return
*/
public int height() {
return Math.max(this.left == null ? 0 : this.left.height(), this.right == null ? 0 : this.right.height()) + 1;
}
/**
* 查找要删除的节点的父节点
*
* @param value 要删除的节点的值
* @return
*/
public Node searchParent(int value) {
//当前节点就是要删除节点的父节点
if ((this.left != null && this.left.value == value) ||
(this.right != null && this.right.value == value)) {
return this;
} else {
if (value < this.value && this.left != null) {
return this.left.searchParent(value);
} else if (value >= this.value && this.right != null) {
//注意: 判断大于的时候有个等号,因为在添加节点时遇到相同值的节点是放在右子树中!!!
return this.right.searchParent(value);
} else {
//找不到父节点
return null;
}
}
}
/**
* 查找要删除的节点
*
* @param value 要删除的节点的值
* @return
*/
public Node search(int value) {
if (this.value == value) {
return this;
} else if (this.value > value) {
//如果要查找的节点小于当前节点,递归左子树
if (this.left == null) {
return null;
}
return this.left.search(value);
} else {
if (this.right == null) {
return null;
}
return this.right.search(value);
}
}
/**
* 节点中序遍历: 左->中->右
*/
public void infixOrder() {
if (this.left != null) {
this.left.infixOrder();
}
System.out.println(this);
if (this.right != null) {
this.right.infixOrder();
}
}
/**
* 添加节点(递归)
*
* @param node 传入的节点
*/
public void add(Node node) {
if (null == node) {
return;
}
//判断传入的节点的值与当前子树的根节点的值的关系
if (node.value < this.value) {
if (this.left == null) {
//如果当前节点的左子节点为null,直接将传入节点放到左子节点位置
this.left = node;
} else {
//如果左子节点不为空,那么基于左子节点进行递归,找到合适的位置存放传入的节点
this.left.add(node);
}
} else {
if (this.right == null) {
//如果当前节点的右子节点为null,直接将传入节点放到右子节点位置
this.right = node;
} else {
//如果右子节点不为空,那么基于右子节点进行递归,找到合适的位置存放传入的节点
this.right.add(node);
}
}
//当添加一个节点之后,判断左右两棵子树的高度差是否大于1,如果大于,则需要进行对应的处理
if (this.rightHeight() - this.leftHeight() > 1) {
this.leftRotate();
}else if (this.leftHeight() - this.rightHeight() > 1){
this.rightRotate();
}
}
}
4.案例-双旋转
问题:
前面的两个数列,进行单旋转(即一次旋转)就可以将非平衡二叉树转成平衡二叉树,但是在某些情况下,单旋转不能完成平衡二叉树的转换.比如:
int[] arr = {10, 11, 7, 6, 8, 9}; //运行原来的代码可以看到,并没有转成AVL树;
int[] arr = {2,1,6,5,7,3}; //运行原来的代码可以看到并没有转成树;
双旋转思路分析:
①.当符合右旋转的条件时(当前节点的左子树的右子树的高度大于当前节点的左子树的左子树的高度):
A.先要对当前节点的左子树进行左旋转;
B.再对当前节点进行右旋转操作即可;
②.当符合左旋转的条件时(当前节点的右子树的左子树的高度大于当前节点的右子树的右子树的高度):
A.先要对当前节点的右子树进行右旋转;
B.在对当前节点进行左旋转操作即可;
代码实现:
//节点对象
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 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;
}
/**
* 左旋转
*/
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;
}
/**
* 返回右子树的高度
*
* @return
*/
public int rightHeight() {
if (this.right == null) {
return 0;
}
return this.right.height();
}
/**
* 返回左子树的高度
*
* @return
*/
public int leftHeight() {
if (this.left == null) {
return 0;
}
return this.left.height();
}
/**
* 返回该节点为根节点的树的高度
*
* @return
*/
public int height() {
return Math.max(this.left == null ? 0 : this.left.height(), this.right == null ? 0 : this.right.height()) + 1;
}
/**
* 查找要删除的节点的父节点
*
* @param value 要删除的节点的值
* @return
*/
public Node searchParent(int value) {
//当前节点就是要删除节点的父节点
if ((this.left != null && this.left.value == value) || (this.right != null && this.right.value == value)) {
return this;
} else {
if (value < this.value && this.left != null) {
return this.left.searchParent(value);
} else if (value >= this.value && this.right != null) {
//注意: 判断大于的时候有个等号,因为在添加节点时遇到相同值的节点是放在右子树中!!!
return this.right.searchParent(value);
} else {
//找不到父节点
return null;
}
}
}
/**
* 查找要删除的节点
*
* @param value 要删除的节点的值
* @return
*/
public Node search(int value) {
if (this.value == value) {
return this;
} else if (this.value > value) {
//如果要查找的节点小于当前节点,递归左子树
if (this.left == null) {
return null;
}
return this.left.search(value);
} else {
if (this.right == null) {
return null;
}
return this.right.search(value);
}
}
/**
* 节点中序遍历: 左->中->右
*/
public void infixOrder() {
if (this.left != null) {
this.left.infixOrder();
}
System.out.println(this);
if (this.right != null) {
this.right.infixOrder();
}
}
/**
* 添加节点(递归)
*
* @param node 传入的节点
*/
public void add(Node node) {
if (null == node) {
return;
}
//判断传入的节点的值与当前子树的根节点的值的关系
if (node.value < this.value) {
if (this.left == null) {
//如果当前节点的左子节点为null,直接将传入节点放到左子节点位置
this.left = node;
} else {
//如果左子节点不为空,那么基于左子节点进行递归,找到合适的位置存放传入的节点
this.left.add(node);
}
} else {
if (this.right == null) {
//如果当前节点的右子节点为null,直接将传入节点放到右子节点位置
this.right = node;
} else {
//如果右子节点不为空,那么基于右子节点进行递归,找到合适的位置存放传入的节点
this.right.add(node);
}
}
//当添加一个节点之后,判断左右两棵子树的高度差是否大于1,如果大于,则需要进行对应的处理
if (this.rightHeight() - this.leftHeight() > 1) {
//双旋转: 如果当前节点的右子树的左子树的高度大于当前节点的右子树的右子树高度
if (this.right != null && this.right.leftHeight() > this.right.rightHeight()) {
//先要对当前节点的右子树进行右旋转
this.right.rightRotate();
}
//在对当前节点进行左旋转操作即可
this.leftRotate();
//这里的return千万不能省略!!!
return;
}
if (this.leftHeight() - this.rightHeight() > 1) {
//双旋转: 如果当前节点的左子树的右子树的高度大于他的右子树高度
if (this.left != null && this.left.rightHeight() > this.left.leftHeight()) {
//先要对当前节点的左子树进行左旋转
this.left.leftRotate();
}
//在对当前节点进行右旋转
this.rightRotate();
//这里的return千万不能省略!!!
return;
}
}
}