数据结构-平衡二叉树AVL的增删与遍历

1、概念:

平衡二叉树(Balanced Binary Tree)具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。

1.1 平衡因子:

左子树的高度减去右子树的高度,及B = B左 - B右。由平衡二叉树的定义可知,平衡因子的取值只可能为0,1,-1。0:左右子树等高。1:左子树比较高。-1:右子树比较高

1.2 最小不平衡子树

距离插入结点最近的,且平衡因子绝对值大于1的结点为根 的子树,我们称为最小不平衡子树

1.3 AVL调整----旋转

1.3.1左旋:

//1、获取根节点的左孩子,将左孩子升级为当前最小不平衡书的根节点(后面仍以左孩子称呼)
//2、将左孩子的右节点绑定到原根节点的左子树上
//3、将原根节点绑定到左孩子的右子树上

1.3.2右旋

//1、获取当前最小不平衡树根节点的右孩子,将其升级为当前最小不平衡书的根节点(后面仍以右孩子称呼)
//2、如果右孩子有左孩子,放到原根节点的右子树上
//3、将原根节点变为右孩子的左孩子

1.3.3 旋转使用条件:

右旋:最小不平衡子树的BF>=2 且 它的左子树BF符号>0
左旋:最小不平衡子树的BF<=-2 且 它的右子树BF符号<0
左右旋:最小不平衡子树的BF>=2 且 它的左子树BF符号<0。需要先对左子树进行左旋,然后对最小不平衡子树右旋
右左旋:最小不平衡子树的BF<=-2 且 它的右子树BF符号>0,需要先对右子树进行右旋,然后对最小不平衡子树左旋

LL,LR,RR,RL

 

2、代码展示

注意两点:

1、求节点的高度,递归:

    private int getTreeDepth(TreeNode node) {
        if (node == null) {
            return 0;
        }
        return 1 + Math.max(getTreeDepth(node.left), getTreeDepth(node.right));
    }

2、添加时旋转时机的把握。

3、删除的逻辑(这块好像还是不太对,从删除点开始重新整理书不太对,先这样处理,有机会回来改正,或者有大佬看到了给指点一下)

package com.suirui.binarytree.AVL;

import javafx.scene.Parent;

import javax.xml.soap.Node;
import java.util.ArrayList;
import java.util.LinkedList;

/**
 * Created by zongx on 2020/2/25.
 */
public class AVLTree {

    private TreeNode root = null;
    private final int LEFT = 1;
    private final int RIGHT = -1;
    private final int MAX_LEFT = 2;
    private final int MAX_RIGHT = -2;

    //右转---传入最小不平衡子树的根节点
    public TreeNode rightRotation(TreeNode node) {
        if (node == null) {
            return null;
        }

        //1、获取根节点的左孩子,将左孩子升级为当前最小不平衡书的根节点(后面仍以左孩子称呼)
        TreeNode leftChild = node.left;

        leftChild.parent = node.parent;
        if (node.parent == null) {
            //如果为null的话,代表当前node是整个AVL树的根节点,所以AVL根节点替换为leftChild
            this.root = leftChild;
        } else if (node.parent.left == node) {
            //如果node在其父节点的左子树上,将其左孩子绑定到其父节点的左子树上
            node.parent.left = leftChild;
        } else if (node.parent.right == node) {
            //如果node在其父节点的右子树上,将其左孩子绑定到其父节点的右子树上
            node.parent.right = leftChild;
        }

        //2、将左孩子的右节点绑定到原根节点的左子树上
        node.left = leftChild.right;
        if (leftChild.right != null) {
            leftChild.right.parent = node;
        }

        //3、将原根节点绑定到左孩子的右子树上
        leftChild.right = node;
        node.parent = leftChild;

        //4、返回最新根节点

        return leftChild;
    }

    public TreeNode leftRotation(TreeNode node) {
        if (node == null) {
            return null;
        }
        //1、获取当前最小不平衡树根节点的右孩子,将其升级为当前最小不平衡书的根节点(后面仍以右孩子称呼)
        TreeNode rightChild = node.right;
        rightChild.parent = node.parent;
        if (node.parent == null) {
            this.root = rightChild;
        } else if (node.parent.left == node) {
            node.parent.left = rightChild;
        } else if (node.parent.right == node) {
            node.parent.right = rightChild;
        }

        //2、如果右孩子有左孩子,放到原根节点的右子树上
        node.right = rightChild.left;
        if (rightChild.left != null) {
            rightChild.left.parent = node;
        }

        //3、将原根节点变为右孩子的左孩子
        rightChild.left = node;
        node.parent = rightChild;

        return rightChild;
    }


    /**
     * @return void
     * @Description: 从根节点插入数据
     * @Author: zongx
     * @Date: 2020/2/26
     * @Param: root
     * @Param: data
     */
    public boolean add(int value) {
        //1、如果没有根节点,则将当前节点数据作为根节点
        if (this.root == null) {
            this.root = new TreeNode(value);
            return true;
        }
        //2、判断value节点的位置
        //从根节点开始比较
        TreeNode current = root;
        while (true) {
            //要插入节点大于或等于当前节点,则判断右子树
            if (value >= current.data) {
                if (current.right == null) {
                    current.right = new TreeNode(value, current);
                    break;
                }
                current = current.right;
            }else {
                //要插入节点小于当前节点,则判断左子树
                if (current.left == null) {
                    current.left = new TreeNode(value, current);
                    break;
                }
                current = current.left;
            }
        }
        //此时current就是新加入节点的父节点
        //3、对树进行旋转使其满足AVL条件
        rebuild(current);
        return true;

    }

    /**
     * @return void
     * @Description: 对树进行再平衡
     * @Author: zongx
     * @Date: 2020/2/26
     * @Param: node
     */
    private void rebuild(TreeNode node) {
        //从给定的node节点开始,一直到根节点判断平衡因子。
        while (node != null) {
            int bf = calcNodeBalanceValue(node);
            if (bf == MAX_LEFT) {
                //计算出的平衡因子是+2,代表左子树高
                fixInsertion(node, LEFT);
            } else if (bf == MAX_RIGHT) {
                //计算出的平衡因子是-2,代表左子树高
                fixInsertion(node, RIGHT);
            }
            node = node.parent;
        }
    }


    /**
     * @return int
     * @Description: 计算平衡因子
     * @Author: zongx
     * @Date: 2020/2/26
     * @Param: node
     */
    private int calcNodeBalanceValue(TreeNode node) {
        if (node == null) {
            return 0;
        }
        //返回左子树高度减去右子树高度
        return getTreeDepth(node.left) - getTreeDepth(node.right);
    }

    /**
     * @return int
     * @Description: 获取根节点node的树的高度, 递归方式,
     * @Author: zongx
     * @Date: 2020/2/26
     * @Param: node
     */
    private int getTreeDepth(TreeNode node) {
        if (node == null) {
            return 0;
        }
        return 1 + Math.max(getTreeDepth(node.left), getTreeDepth(node.right));
    }

    /**
     * @return void
     * @Description: 根据平衡因子进行旋转操作
     * @Author: zongx
     * @Date: 2020/2/26
     * @Param: node 此处的node是最小不平衡树的根节点
     * @Param: left
     */
    private void fixInsertion(TreeNode node, int type) {
        //右旋:最小不平衡子树的BF>=2 且 它的左子树BF符号>0
        //左旋:最小不平衡子树的BF<=-2 且 它的右子树BF符号<0
        //左右旋:最小不平衡子树的BF>=2 且 它的左子树BF符号<0。需要先对左子树进行左旋,然后对最小不平衡子树右旋
        //右左旋:最小不平衡子树的BF<=-2 且 它的右子树BF符号>0,需要先对右子树进行右旋,然后对最小不平衡子树左旋
        if (type > 0) {
            //因为是左树比较高,所以判断node节点的左孩子的情况
            TreeNode leftChild = node.left;
            int childBf = calcNodeBalanceValue(leftChild);
            if (childBf > 0) {
                //左孩子平衡因子>0,根节点平衡因子>0----右旋
                rightRotation(node);
            } else if (childBf < 0) {
                //左孩子平衡因子<0,根节点平衡因子>0----左右旋
                leftRotation(leftChild);
                rightRotation(node);
            }
            return;
        }

        if (type == this.RIGHT) {
            //因为是右树比较高,所以判断node节点的右孩子的情况
            TreeNode rightChild = node.right;
            int childBf = calcNodeBalanceValue(rightChild);
            if (childBf < 0) {
                //右孩子平衡因子<0,根节点平衡因子<0----左旋
                leftRotation(node);
            } else if (childBf > 0) {
                //右孩子平衡因子>0,根节点平衡因子<0----右左旋
                rightRotation(rightChild);
                leftRotation(node);
            }
            return;
        }
    }



    public void midOrder() {
        if(this.root == null) {
            return;
        }
        System.out.println("--------中序遍历--------");
        LinkedList<TreeNode> stack = new LinkedList<>();
        TreeNode current = this.root;
        while(!stack.isEmpty() || current != null) {
            if (current != null) {
                stack.push(current);
                current = current.left;
            }else {
                current = stack.pop();
                System.out.print(current.data+",");
                current = current.right;
            }
        }
        System.out.println();
    }

    public void levelOrder() {
        if(this.root == null) {
            return;
        }
        System.out.println("--------层次遍历--------");
        LinkedList<TreeNode> queue = new LinkedList<>();
        TreeNode current = this.root;
        while (current != null || !queue.isEmpty()) {
            System.out.println("当前节点值:" + current.data + ", BF:" + calcNodeBalanceValue(current));
            if (current.left != null) {
                queue.add(current.left);
            }
            if (current.right != null) {
                queue.add(current.right);
            }
            current = queue.poll();
        }
        System.out.println();

    }

    public TreeNode getNode(int value) {
        TreeNode current = this.root;
        while (current != null){
            if (current.data == value) {
                return current;
            }else if (current.data > value) {
                current = current.left;
            }else {
                current = current.right;
            }
        }
        return null;
    }

    /**
     * @Description: 基本沿用二叉搜索树的逻辑,只是此时treenode结构发生变化,要考虑parent属性。还要考虑
     * @Author: zongx
     * @Date: 2020/2/26
     * @Param: value
     * @return void
    */
    public void delete(int value) {

        TreeNode delNode = getNode(value);
        //未找到。直接返回
        if (delNode == null) {
            System.out.println("没有要删除的元素");
            return;
        }
        TreeNode parent = delNode.parent;
        //分三种情况进行讨论。
        //1、删除的节点没有子树,将其父节点的子节点置为null
        if (delNode.left == null && delNode.right == null) {
            if (delNode == this.root) {
                this.root = null;
            } else if (delNode.parent.left == delNode) {
                delNode.parent.left = null;
            } else if (delNode.parent.right == delNode) {
                delNode.parent.right = null;
            }
            rebuild(parent);
            return;
        }

        //2、删除的节点只有一颗子树,将其父节点的子节点置为其子树
        //2.1 左子树不为空
        if (delNode.left != null && delNode.right == null) {
            if (delNode == this.root) {
                //因为是双向关连,所以新节点与父节点之间要相互关联
                delNode.left.parent = delNode.parent;
                this.root = delNode.left;
            } else if (delNode.parent.left == delNode) {
                //因为是双向关连,所以新节点与父节点之间要相互关联
                delNode.left.parent = delNode.parent;
                delNode.parent.left = delNode.left;
            } else if (delNode.parent.right == delNode) {
                //因为是双向关连,所以新节点与父节点之间要相互关联
                delNode.left.parent = delNode.parent;
                delNode.parent.right = delNode.left;
            }
            rebuild(parent);
            return;
        }
        //2.2 右子树不为空
        if (delNode.right != null && delNode.left == null) {
            if (delNode == this.root) {
                //因为是双向关连,所以新节点与父节点之间要相互关联
                delNode.right.parent = delNode.parent;
                this.root = delNode.right;
            } else if (delNode.parent.left == delNode) {
                //因为是双向关连,所以新节点与父节点之间要相互关联
                delNode.right.parent = delNode.parent;
                delNode.parent.left = delNode.right;
            } else if (delNode.parent.right == delNode) {
                //因为是双向关连,所以新节点与父节点之间要相互关联
                delNode.right.parent = delNode.parent;
                delNode.parent.right = delNode.right;
            }
            rebuild(parent);
            return;
        }
        //3 左右子树均不为空,此时的操作需要找寻后继节点,并修改结构
        TreeNode successor = getSuccessor(delNode);
        if (delNode == this.root) {
//            successor.parent = delNode.parent;
            this.root = successor;
        }else if (delNode.parent.left == delNode) {
            //因为是双向关连,所以新节点与父节点之间要相互关联
            successor.parent = delNode.parent;
            delNode.parent.left = successor;
        } else if (delNode.parent.right == delNode) {
            //因为是双向关连,所以新节点与父节点之间要相互关联
            successor.parent = delNode.parent;
            delNode.parent.right = successor;
        }
        rebuild(parent);
    }

    /**
     * @Description: 获取后继节点(具体看二叉搜索树该部分)
     * @Author: zongx
     * @Date: 2020/2/26
     * @Param: current
     * @return com.suirui.binarytree.AVL.TreeNode
    */
    private TreeNode getSuccessor(TreeNode delNode) {
        //此处的前提是,current肯定具有左右孩子
        //后继节点是中序遍历的下一个节点,所以肯定是右孩子左子树的最后一个
        TreeNode current = delNode.right;
        while (current.left != null) {
            current = current.left;
        }
        //修改结构,如果后继节点不是直接是右孩子,则需要改造右子树
        if (current != delNode.right) {
            //1、如果current有右子树,将current当前节点的右子树给当前节点的父节点
            if (current.right != null) {
                current.right.parent = current.parent;
            }
            current.parent.left = current.right;//这块直接使用left是因为,当前节点是通过left找到的,所以在left上
            //2、将要删除节点delNode的右子树,给当前节点
            delNode.right.parent = current;
            current.right = delNode.right;
        }
        //为后继节点赋值删除节点的左子树(后继节点应该没有左子树)
        delNode.left.parent = current;
        current.left = delNode.left;
        return current;
    }
    

    public static void main(String[] args) {
        AVLTree avlTree = new AVLTree();
        avlTree.add(10);
        avlTree.add(7);
        avlTree.add(15);
        avlTree.add(3);
        avlTree.add(8);
        avlTree.add(20);
        avlTree.midOrder();
        avlTree.levelOrder();

        avlTree.delete(20);
        avlTree.delete(15);
        avlTree.midOrder();
        avlTree.levelOrder();


    }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值