先看一下二叉搜索树的复杂度分分析
当我们添加元素到二叉搜索树中时:
寻找的次数只与树的高度有关 与树的节点数并无大的关系
复杂度:O(logn)==O(h)
有点像二分 一半一半的排除查找 因此为O(logn)
但是也存在最坏的情况:
当n非常大的时候
我们可以看出两种情况下搜索的性能相差甚远
解决办法:
平衡:
所谓更平衡就是:左右子树的高度差越来越小
如何改进平衡二叉树?
改进的规则:
AVL树:
记住顺序:平衡因子是左子树的高度-右子树的高度
举个例子:7的平衡因子==-2
7的左子树高度为3 右子树高度为 5
3-5==-2
AVL树的特点:
AVL树的平衡因子的绝对值一旦超过1
我们称之为失衡
当我们添加节点导致的失衡
我们添加的时候是在下面角落处进行添加的
肯定不可能在上面度为2的地方进行添加
因为二叉树是最大度为2的树
当我们由于添加节点元素导致失衡时
如何进行弥补改正呢?
首先给出一组
这一组只是一整棵树的下面一小部分
还是那句话我们添加就是针对下面的一小块区域进行的操作
注意水平线:T0 T1 T2 T3的树节点的高度是不同的
那么我们现在可知:平衡因子是左高减右高
n的平衡因子是0
p的平衡因子是0
g的平衡因子是1
LL单旋:进行右旋转 在左边的左边添加节点
为什么进行右旋转呢?
因为添加的节点是在g的左子树上
当我们添加红色节点段时:g失去平衡
此时让g失去平衡的是它左边的左边的T0处的添加
因此我们进行右旋转
如图:
一开始假设这是一颗平衡的二叉搜索树
并且所有的节点都是位于红色线的上面
当我们添加元素之后肯定会打破这个平衡
当我们添加了一个节点之后 发现一个节点超过了红线
由于这个是在左边的左边
即是T0或T1处加上的一个节点
那么要进行右旋转
g.left=p.right
p.right=g
这时候你会发现所有的节点都到红线的上方了
那么此时又是一颗平衡二叉搜素树了 (根节点p上面的部分依旧是平衡的)
除此之外我们还要把节点之间的属性改一下
代码如下:
//包装右旋转的方法
private void rotateRight(Node<E> grand){
Node<E> parent=grand.left;
Node<E> child=parent.left;
grand.left=child;
parent.right=grand;
//让parent代替相应的父节点 更新parent的parent
parent.parent=grand.parent;
if(grand.isLeftChild()){
grand.parent.left=parent;
} else if(grand.isRightChild()){
grand.parent.right=parent;
} else {
root=parent;
}
//更新child的parent
if(child!=null){
child.parent=grand;
}
//更新grand的parent
grand.parent=parent;
//切记更改各个parent的顺序 先改parent 再child 最后grand
//更新高度
updateHeight(grand);
updateHeight(parent);
}
RR单旋:进行左旋转 在右边的右边添加节点
为什么进行左旋转呢?
因为添加的节点是在g的右子树上
代码如下:
//包装左旋转的方法
private void rotateLeft(Node<E> grand){
//把grand指向的parent拿出来
Node<E> parent=grand.right;
Node<E> child=parent.left;
grand.right=child;
parent.left=grand;
//让parent代替grand成为相对的父节点 更新parent的parent
parent.parent=grand.parent;
if(grand.isLeftChild()){//grand是左子树时
grand.parent.left=parent;
} else if(grand.isRightChild()){
grand.parent.right=parent;
} else {//说明grand上面没有节点了 grand就是根节点
root=parent;
}
//更新child的parent
if(child!=null){
child.parent=grand;
}
//更新grand的parent
grand.parent=parent;
//切记更改各个parent的顺序 先改parent 再child 最后grand
//更新高度
updateHeight(grand);
updateHeight(parent);
}
LR双旋:先进行左旋转再进行右旋转
这种情况下:在T2下面加上一个节点
如何分辨是什么双旋?
从g进来先往左再往右 left right
因此为LR双旋 前面我们有了左旋右旋基础
因此LR双旋 先进行左旋转再进行右旋转
1.先对p进行左旋转
为什么进行左旋转呢?
因为添加的节点是在p的右子树上
是指向右的
2.对g进行右旋转
因为添加的节点是在g的左子树上
是指向左的
RL双旋:
从根节点开始先右再左
因此为RL双旋
会在T1和T2进行添加节点元素
1.对p先进行右旋转
为什么呢?
因为添加的节点是在p的左子树上
2.对g进行左旋转
因为添加的节点元素是在g的右子树上
因此指向右的
因此进行左旋转
总结:
1.对二叉树进行双旋时
先对相对下面的元素进行旋转再进行相对上面的节点进行旋转
如先对p进行旋转再对g进行旋转
2.还有一点就是对旋转方向的总结
我们旋转肯定是对一个节点进行旋转对吧?
那么举个例子来看:
进行双旋时遵守规则:
先旋相对下面的 再旋相对上面的
第一步旋p的时候 发现添加的节点是相对p这一个节点是在左子树的
因此进行右旋转操作
第二步旋g的时候
只看g这个单个节点可知添加的节点是在g的右子树上
因此我们对g进行左旋转
对于双旋的代码如下:
//恢复平衡的方法
//重点:
在图中p对应parent g对应grand n对应node
//grand传进来的就是高度最低的不平衡节点
private void rebalance(Node<E> grand){
//这里传给grand的是树节点的node
//然而这里的tallerChild是属于AVLNode<E>的方法
//因此我们得强转之后才能调用
//parent是grand左右子树最高的节点 grand传进来的就是高度最低的不平衡节点
Node<E> parent=((AVLNode<E>)grand).tallerChild();
//node是parent左右子树最高的节点
Node<E> node=((AVLNode<E>)parent).tallerChild();
//找到这两个节点之后
//上树?
if(parent.isLeftChild()){//parent是grand左子树那么一开始是L
if(node.isLeftChild()){//成立 那么node是左 L
//那么就是LL
//那么就只是进行右旋转
rotateRight(grand);//在图中p对应parent g对应grand n对应node
} else {//R
//LR
//先对下面的不平衡节点进行右旋转
rotateRight(parent);
//再对较上面的不平衡节点进行左旋
rotateLeft(grand);
}
} else {//一开始parent是grand的右子树 R
if(node.isRightChild()){//R
//那么就是RR
rotateLeft(grand);
} else {//L
//那么即是RL
rotateLeft(parent);
rotateRight(grand);
}
}
}
在我们修复平衡二叉树时
从下往上找失衡的节点
找到n和p :
private void rebalance(Node<E> grand){
//这里传给grand的是树节点的node
//然而这里的tallerChild是属于AVLNode<E>的方法
//因此我们得强转之后才能调用
//parent是grand左右子树最高的节点 grand传进来的就是高度最低的不平衡节点
Node<E> parent=((AVLNode<E>)grand).tallerChild();
//node是parent左右子树最高的节点
Node<E> node=((AVLNode<E>)parent).tallerChild();
//找到这两个节点之后
}
在图中p对应parent g对应grand n对应node
代码中对应标识:
总代码如下:
AVL树:
增加节点之后进行判断它是否仍是平衡二叉树
若是那么给它恢复平衡
afterAdd:
package BinaryTree;
import java.util.Comparator;
public class AVLTree<E> extends BST<E>{
public AVLTree() {
this(null);
}
public AVLTree(Comparator<E> comparator) {
super(comparator);
}
@Override
protected void addafter(Node<E> node) {
while ((node=node.parent)!=null){
//如果平衡 更新高度
if(((AVLNode<E>)node).isbalance(node)){
((AVLNode<E>)node).Updataheight();
} else {//不平衡 那么恢复平衡
rebalance(node);
break;//只要恢复了平衡直接结束afteradd
}
}
}
/**
*恢复平衡
* 第一次传进来的node就是高度最低的不平衡的父节点
*/
private void rebalance(Node<E> grand){
//因为添加节点之后 肯定高度增加了 因此我们要找左右子树较高的那一个
Node<E> parent= ((AVLNode<E>)grand).tallerChild();
Node<E> node= ((AVLNode<E>)parent).tallerChild();
if(parent.isLeftChild()){//parent是grand的左子树
if(node.isLeftChild()){//LL
rotateright(grand);
} else {//LR
rotateleft(parent);
rotateright(grand);
}
} else {//parent是grand的右子树
if(node.isRightChild()){//RR
rotateleft(grand);
} else {//RL
rotateright(parent);
rotateleft(grand);
}
}
}
/**
*封装左和右旋转的操作
*/
//进行左旋转 默认grand.right=parent parent.right=node
private void rotateleft(Node<E> grand){
Node<E> parent=grand.left;
//1.先更新树的节点指向
grand.right=parent.left;
parent.left=grand;
//2.更新父属性
//虽然我们已经更新了链接方式 但是节点之间的父子关系还没有进行更新还是有线连着的
//首先更新parent的父属性
if(grand.isLeftChild()){
grand.parent.left=parent;
} else if(grand.isRightChild()) {
grand.parent.right=parent;
} else {//当原来没旋转之前 grand是整个AVL树的根节点
root=parent;
}
//其次更新parent下面一个子树的父属性
parent.left.parent=grand;
//最后更新grand的父属性
grand.parent=parent;
//更新高度
((AVLNode<E>)grand).Updataheight(grand);
((AVLNode<E>)parent).Updataheight(parent);
}
private void rotateright(Node<E> grand){
Node<E> parent=grand.left;
//1.更新链接关系
grand.left=parent.right;
parent.right=grand;
//2.更新父属性
//先更新parent的父属性
if(grand.isRightChild()){
grand.parent.right=parent;
} else {
grand.parent.left=parent;
}
//再更新parent的一个子树的父属性
parent.right.parent=grand;
//最后更新grand的父属性
//如果先更新grand的父属性
// 前面的parent在更新父属性时grand就变化了 不可行
grand.parent=parent;
//更新高度
((AVLNode<E>)grand).Updataheight(grand);
((AVLNode<E>)parent).Updataheight(parent);
}
@Override
protected Node<E> creatNode(E elements, Node<E> parent) {
return new Node<E>(elements,parent);
}
/**
*因为我们AVL相比BST多了许多
* 因此我们创建一个内部类
* 并且继承BST中创建出的Node<E>的全部属性
*/
private static class AVLNode<E> extends Node<E>{
int height=1;//新加的节点一定是叶子节点
public AVLNode(E elements, Node<E> parent) {
super(elements, parent);
}
/**
*封装一下 更新高度
*/
private void Updataheight(Node<E> node){
((AVLNode<E>)node).Updataheight();
}
/**
* 先每一个AVL节点都创建一个更新高度的方法
*/
public int Updataheight(){
int heightleft=left==null?0:((AVLNode<E>)left).height;
int heightright=right==null?0:((AVLNode<E>)right).height;
return 1+Math.max(heightleft,heightright);
}
/**
* 判断是否平衡
*/
private boolean isbalance(Node<E> node){
return Math.abs(((AVLNode<E>)node).balanceFactor())<=1;
}
/**
*计算AVL树的平衡因子
*/
private int balanceFactor(){
//因为这里我们AVL是继承的BST BST继承的BinaryTree
//BinaryTree中是不具有height这个属性的 只有AVL树中才有
//因此我们要把这个left和right属性强制转化为AVL节点类型
int heightleft=left==null?0:((AVLNode<E>)left).height;
int heightright=right==null?0:((AVLNode<E>)right).height;
return heightleft-heightright;
}
public Node<E> tallerChild(){
int heightleft=left==null?0:((AVLNode<E>)left).height;
int heightright=right==null?0:((AVLNode<E>)right).height;
if(heightleft<heightright){
return right;
}
if (heightleft>heightright){
return left;
}
//如果高度相等 返回相同方向的
return isLeftChild()?left:right;
}
}
}
发现规律:
统一旋转代码:
这四种情况最后的结果是一样的
我们只要搞清楚最后的abcdefg的情况是什么样的就行
创建一个方法时期适用于所有旋转
由完全二叉树的性质可设:
节点大小从小到大:abcdefg
我们实现这个朴实的方法:
//之前未改变时的根节点
private void rotate(Node<E> r,Node<E> a,Node<E> b,Node<E> c,Node<E> d,Node<E> e,Node<E> f,Node<E> g){
//但是无论怎么做 d最后都是根节点
//让d成为根节点
d.parent= r.parent;
if(r.isLeftChild()){
r.parent.left=d;
} else if(r.isRightChild()){
r.parent.right=d;
} else {
root=d;
}
//在更新a b c的时候不仅要表面b的左子树是a 还要表明a的父节点是b
//双双 表明才是算无遗策
b.left=a;
if(a!=null) {
a.parent = b;
}
b.right=c;
if(c!=null) {
c.parent = b;
}
//更新一下b的高度
updateHeight(b);
//更新f e g
f.left=e;
if(e!=null) {
e.parent = f;
}
f.right=g;
if(g!=null){
g.parent=f;
}
updateHeight(f);
}
那么问题来了:
如何传参呢?
前面我们知道
首先:abcdefg是从小到大依次排序起来的
然后 我们应该对应着各个旋转的图片进行把参数书写完毕
RR单旋:
LR双旋:
RL双旋:
那么进行套就可以得出:
private void rebalancetwo(Node<E> grand){
Node<E> parent=((AVLNode<E>)grand).tallerChild();
Node<E> node=((AVLNode<E>)parent).tallerChild();
if(parent.isLeftChild()){
if(node.isLeftChild()){//LL
rotate(grand,node.left,node,node.right,parent,parent.right,grand,grand.right);
} else {//LR
rotate(grand,parent.left,parent,node.left,node,node.right,grand,grand.right);
}
} else {
if(node.isLeftChild()){//RL
rotate(grand,grand.left,grand,node.left,node,node.right,parent,parent.right);
} else {//RR
rotate(grand,grand.left,grand,parent.left,parent,node.left,node,node.right);
}
}
}
那么我们这种方法的总代码:
//恢复平衡的方法
//重点:
在图中p对应parent g对应grand n对应node
//grand传进来的就是高度最低的不平衡节点
private void rebalance(Node<E> grand){
//这里传给grand的是树节点的node
//然而这里的tallerChild是属于AVLNode<E>的方法
//因此我们得强转之后才能调用
//parent是grand左右子树最高的节点 grand传进来的就是高度最低的不平衡节点
Node<E> parent=((AVLNode<E>)grand).tallerChild();
//node是parent左右子树最高的节点
Node<E> node=((AVLNode<E>)parent).tallerChild();
//找到这两个节点之后
//上树?
if(parent.isLeftChild()){//parent是grand左子树那么一开始是L
if(node.isLeftChild()){//成立 那么node是左 L
//那么就是LL
//那么就只是进行右旋转
rotateRight(grand);//在图中p对应parent g对应grand n对应node
} else {//R
//LR
//先对下面的不平衡节点进行右旋转
rotateRight(parent);
//再对较上面的不平衡节点进行左旋
rotateLeft(grand);
}
} else {//一开始parent是grand的右子树 R
if(node.isRightChild()){//R
//那么就是RR
rotateLeft(grand);
} else {//L
//那么即是RL
rotateLeft(parent);
rotateRight(grand);
}
}
}
private void rebalancetwo(Node<E> grand){
Node<E> parent=((AVLNode<E>)grand).tallerChild();
Node<E> node=((AVLNode<E>)parent).tallerChild();
if(parent.isLeftChild()){
if(node.isLeftChild()){//LL
rotate(grand,node.left,node,node.right,parent,parent.right,grand,grand.right);
} else {//LR
rotate(grand,parent.left,parent,node.left,node,node.right,grand,grand.right);
}
} else {
if(node.isLeftChild()){//RL
rotate(grand,grand.left,grand,node.left,node,node.right,parent,parent.right);
} else {//RR
rotate(grand,grand.left,grand,parent.left,parent,node.left,node,node.right);
}
}
} //之前未改变时的根节点
private void rotate(Node<E> r, Node<E> a, Node<E> b, Node<E> c, Node<E> d, Node<E> e, Node<E> f,Node<E> g){
//但是无论怎么做 d最后都是根节点
//让d成为根节点
d.parent= r.parent;
if(r.isLeftChild()){
r.parent.left=d;
} else if(r.isRightChild()){
r.parent.right=d;
} else {
root=d;
}
//在更新a b c的时候不仅要表面b的左子树是a 还要表明a的父节点是b
//双双 表明才是算无遗策
b.left=a;
if(a!=null) {
a.parent = b;
}
b.right=c;
if(c!=null) {
c.parent = b;
}
//更新一下b的高度
updateHeight(b);
//更新f e g
f.left=e;
if(e!=null) {
e.parent = f;
}
f.right=g;
if(g!=null){
g.parent=f;
}
updateHeight(f);
}
删除:
在计算平衡因子的时候
我们删除16这个节点之后只会使它的父节点失衡
除此之外其他的都不会失衡
因为平衡因子的计算都是根据左右的高度差进行计算的
当父节点只有一个子节点的时候
我们删除这个子节点确实影响了父节点的高度
但是父节点的依然平衡
删除之后的操作:
AVLTree中:
/**
*重写removeafter
*/
@Override
protected void removeafter(Node<E> node) {
while((node=node.parent)!=null){
//如果平衡 更新高度
if(((AVLNode<E>)node).isbalance(node)){
((AVLNode<E>)node).Updataheight();
} else {//不平衡 那么恢复平衡
rebalance(node);
}
}
}
BST中:
/**
* 通过下面的方法找到对应的节点之后
* 我们进行删除操作
* 设计模式:
* 我们删除的时候 总共要删除三类节点:
* 度为0 1 2的节点
* 我们在删除度为2的节点时 不可以直接删除而是先拿前驱或后继节点的元素值去覆盖它
* 然后在把用来的前驱或后继节点给删除
* 这被删除的节点只可能是度为0或1的节点 这与前两类的删除模式相重复
* 因此:
* 我们先删除度为2的节点
* 之后再处理后继或前驱节点时 也相当于处理度为1或0的节点了
*/
private void remove(Node<E> node) {
if (node == null) {
return;
}
//先删除度为2的节点
if (node.left!=null&&node.right!=null) {
Node<E> f = predecessor(node);//找到前驱节点
node.elements = f.elements;//值覆盖
node = f;
//由于我们后面不只是为了度为2这一种情况而准备的
//有可能传进来的node直接是度为1或0 那么我们不可以直接用f去处理
//因此我们用node指向f所指向的节点
}
Node<E> replacement = node.left != null ? node.left : node.right;
if (replacement != null) { //度为1
replacement.parent = node.parent;
if (node.parent == null) {//度为1且为根节点
root = replacement;
} else if (node == node.parent.left) {//一定要用node这条线的判断指向 自己想想
//replacement.parent = node.parent;
node.parent.left = replacement;
} else if (node == node.parent.right) {
//replacement.parent = node.parent;
node.parent.right = replacement;
removeafter(node);
}
//到这已经代表replacement==null也就是说那个为了删除度为2节点去覆盖的是叶子节点
} else if (node.parent == null) {
root = null;//度为0 且为根节点
removeafter(node);
} else {//度为0 不是根节点
if (node.parent.left == node) {
node.parent.left = null;
} else {//node.parent.right==node
node.parent.right =null;
}
removeafter(node);
}
}