数据结构与算法—树5
声明:以下是学的尚硅谷网课并结合网上资料所记的笔记。可能会有一些错误,发现了会修改。
平衡二叉树
- 又叫平衡二叉搜索树,AVL树。是在二叉排序树基础上的,可以保证查询效率较高,解决二叉排序树单支的情况(类似单链表)查询速度慢的问题。
- 特点:它是一颗空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一颗平衡二叉树。平衡二叉树的常用实现方法有红黑树、AVL、替罪羊树、Treap、伸展树等。
如上图所示,A,B为平衡二叉树,C不是平衡二叉树,因为根节点的左子树高度为3,而右子树高度为1,高度差超过了1,所以不是平衡二叉树。
平衡二叉树的构建
子树高度的获取
关键: 当左子树和右子树的高度差大于1时,就不再是一颗AVL树了。所以需要得到树的高度,左子树和右子树的高度。伪代码如下:
//返回当前结点的高度,以该结点为根结点的树的高度
public int getHeight() {
return Math.max(left == null? 0 : left.getHeight(), right == null ? 0 : right.getHeight()) + 1;
}
//返回左子树高度
public int getLeftHeight() {
if(left == null) {
return 0;
}
return left.getHeight();
}
//返回右子树高度
public int getRightHeight() {
if(right == null) {
return 0;
}
return right.getHeight();
}
二叉排序树转为平衡二叉树
由二叉排序树转为平衡二叉树有三种情况:
- 如果 右子树的高度 - 左子树的高度 > 1,左旋转,降低右子树的高度。例:给一个数列{4,3,6,5,7,8},创建出对应的平衡二叉树。
步骤:
- 创建新的结点,以当前节点的值。
- 把新的结点的左子树设置成当前节点的左子树。
- 把新的结点的右子树设置成当前结点的右子树的左子树。
- 把当前结点的值替换成右子结点的值。
- 把当前结点的右子树设置成当前结点的右子树的右子树。
- 把当前节点的左子树(左子结点)设置成新的结点。
代码:
private void leftRotate() {
//创建新的结点,以当前节点的值
Node newNode = new Node(value);
//把新的结点的左子树设置成当前节点的左子树
newNode.left = left;
//把新的结点的右子树设置成当前结点的右子树的左子树
newNode.right = right.left;
//把当前结点的值替换成右子结点的值
value = right.value;
//把当前结点的右子树设置成当前结点的右子树的右子树
right = right.right;
//把当前节点的左子树(左子结点)设置成新的结点
left = newNode;
}
- 如果 左子树的高度 - 右子树的高度 > 1,右旋转,降低左子树的高度。例:给一个数列{10,12,8,9,7,6},创建出对应的平衡二叉树。
步骤:
- 创建新的结点,值等于当前节点的值。
- 把新的结点的右子树设置成当前节点的右子树。
- 把新的结点的左子树设置成当前结点的左子树的右子树。
- 把当前结点的值替换成左子结点的值。
- 把当前结点的左子树设置成当前结点的左子树的左子树。
- 把当前节点的右子树(右子结点)设置成新的结点。
代码:
private void rightRotate() {
//创建新的结点,以当前节点的值
Node newNode = new Node(value);
//把新的结点的右子树设置成当前节点的右子树
newNode.right = right;
//把新的结点的左子树设置成当前结点的左子树的右子树
newNode.left = left.right;
//把当前结点的值替换成左子结点的值
value = left.value;
//把当前结点的左子树设置成当前结点的左子树的左子树
left = left.left;
//把当前节点的右子树(右子结点)设置成新的结点
right = newNode;
}
- 双旋转。 前面的两个数列,进行单旋转(即一次旋转)就可以将非平衡二叉树转成平衡二叉树,但是在某些情况下,单旋转不能完成平衡二叉树的转换,比如数列 {10,11,7,6,8,9} 。
上面的左子树比右子树高,进行右旋转后,发现右子树比左子树高,单旋转并没用,需要双旋转。
问题分析
- 当符合左旋转条件时。判断:如果该结点的右子树的左子树的高度大于它的右子树的右子树的高度,先对当前结点的右子结点进行右旋转以降低该结点右子树的左子树的高度,再对当前节点进行左旋转的操作。如果不满足上述条件则直接进行左旋转。
- 当符合右旋转条件时。判断:如果该结点的左子树的右子树的高度大于它的左子树的左子树的高度,先对当前结点的左子结点进行左旋转以降低该结点左子树的右子树的高度,再对当前节点进行右旋转的操作。如果不满足上述条件则直接进行右旋转。
双旋转代码:
//当添加完一个结点后,如果 右子树的高度 - 左子树的高度 > 1,左旋转
if(getRightHeight() - getLeftHeight() > 1) {
//如果该结点的 右子树的左子树的高度大于它的右子树的右子树的高度。
if(right != null && right.getLeftHeight() > right.getRightHeight()) {
//先对当前结点的右子结点进行右旋转
right.rightRotate();
//再对当前节点进行左旋转
leftRotate();
} else {
//直接进行左旋转即可
leftRotate();
}
return;
}
//当添加完一个结点后,如果 左子树的高度 - 右子树的高度 > 1,右旋转
if(getLeftHeight() - getRightHeight() > 1)