树结构_AVL树(平衡二叉树),红黑树与B系列树简介

【AVL树(平衡二叉树)】

对于BST树而言,其优点是顺序性。但诸如{2,1,3,5,4,6}这样的数据所形成的BST树(如下图),其根节点左子树高度为2,右子树高度为4.两边高度相差大,这将导致在巨量数据存储时,两边高度差极大,结点单边存储而导致查找效率低下

AVL树就是为了平衡这种极端而来的。AVL树具有左旋,右旋,双旋操作。都是为了降低左右子树高度差,使之不超过1

核心代码实现:

1,AVL树是BST树的升级版。在旋转前,应该先要知道左右树的高度差是多少,有没有超过1。所以在BST树的Node类中追加 height,leftHeight,rightHeight方法,用于获取当前结点为父节点的子树高度,左子树的高度,右子树的高度。(其中height方法采用递归实现,非常精妙)

/**
     * 相当精妙的算法:通过递归获得二叉树的高度
     * @return 二叉树的高度
     */
    public int height(){
        //通过递归左右结点,为空时停止递归,否则继续递归,每轮递归高度加1,递归完成选取大的作为高度。佩服佩服
        return Math.max(left==null?0:left.height(),right==null?0:right.height())+1;
    }

    /**
     * 返回当前结点的左子树高度
     * @return 左子树高度
     */
    public int leftHeight(){
        if (left==null){
            return 0;
        }
        return left.height();
    }

    /**
     * 返回当前结点的右子树高度
     * @return 右子树高度
     */
    public int rightHeight(){
        if (right==null){
            return 0;
        }
        return right.height();
    }

2,当右子树高度-左子树高度>1 时,开始左旋:

1)复制出一个与当前结点一样的结点

2)将新结点的左子树与当前结点的左子节点相连,将当前结点的右子节点的左子结点与新结点的右结点相连

3)将当前结点的右子节点的值,赋给当前结点,并将当前结点的右节点与右子节点的右子节点相连

4)将当前结点的左节点与新结点相连

在Node中添加 leftRotate 方法,对当前结点,进行左旋平衡操作

/**
     * 对当前结点,进行左旋平衡操作
     */
    public void leftRotate(){
        //1,复制出一个与当前结点一样的结点
        Node newNode=new Node(this.data);
        //2,将新结点的左子树与当前结点的左子节点相连,将当前结点的右子节点的左子结点与新结点的右结点相连
        newNode.setLeft(this.left);
        newNode.setRight(this.right.left);
        //3,将当前结点的右子节点的值,赋给当前结点,并将当前结点的右节点与右子节点的右子节点相连
        this.data=this.right.data;
        this.right=this.right.right;
        //4,将当前结点的左节点与新结点相连
        this.left=newNode;
    }

3,当左子树高度-右子树高度>1 时,开始右旋: 

1)复制出一个与当前结点一样的结点

2)将新结点的右节点与当前结点的右子节点相连,将当前结点的左子节点的右子结点与新结点的左结点相连

3)将当前结点的左子节点的值,赋给当前结点,并将当前结点的左节点与左子节点的左子节点相连

4)将当前结点的右节点与新结点相连

在Node中添加 rightRotate 方法,对当前结点,进行右旋平衡操作

 /**
     * 对当前结点,进行右旋平衡操作
     */
    public void rightRotate(){
        //1,复制出一个与当前结点一样的结点
        Node newNode=new Node(this.data);
        //2,将新结点的右节点与当前结点的右子节点相连,将当前结点的左子节点的右子结点与新结点的左结点相连
        newNode.setRight(this.right);
        newNode.setLeft(this.left.right);
        //3,将当前结点的左子节点的值,赋给当前结点,并将当前结点的左节点与左子节点的左子节点相连
        this.data=this.left.data;
        this.left=this.left.left;]
        //4,将当前结点的右节点与新结点相连
        this.right=newNode;
    }

4,有了左旋和右旋的实现方法,我们现在可以在Node类的add方法中添加对结点左旋,右旋的判断。

当添加了结点使得树满足 rightHeight()-leftHeight()>1 就需要左旋,leftHeight()-rightHeight()>1 需要右旋。

但这还不够,若有一组数据{10,11,7,6,8,9},其通过直接右旋得到的结果,仍然不平衡(如下图)

这时候,就需要对它的局部进行左旋,当局部左旋后,再进行整体右旋(如下图),这样才能得到我们需要的平衡二叉树

 结合上面的分析,对Node类的add方法进行修改

public void add(Node newNode){
        if (newNode.getData()<this.getData()){//小了往左放
            if (this.getLeft()==null){
                this.setLeft(newNode);
            }else {
                this.getLeft().add(newNode);
            }
        }else {//大了往右放
            if (this.getRight()==null){
                this.setRight(newNode);
            }else {
                this.getRight().add(newNode);
            }
        }
        if (rightHeight()-leftHeight()>1){//需要左旋
            if (right!=null && right.leftHeight()>right.rightHeight()){//需要局部右旋
                right.rightRotate();
                leftRotate();
            }else {//直接左旋
                leftRotate();
            }
            return;//注意,这里要返回,否则就运行下面右旋代码去了

        }
        if (leftHeight()-rightHeight()>1){//需要右旋
            if (left!=null && left.rightHeight()>left.leftHeight()){//需要局部左旋
                left.leftRotate();
                rightRotate();
            }else {//直接右旋
                rightRotate();
            }
        }
    }

至此AVL树创建完成。

完整实现代码:

package cn.dataStructureAndAlgorithm.demo.tree.平衡二叉树_AVLTree;
class Node{
    private int data;
    private Node left;
    private Node right;

    public Node(int data) {
        this.data = data;
    }

    public void setData(int data) {
        this.data = data;
    }

    public int getData() {
        return data;
    }

    public Node getLeft() {
        return left;
    }

    public Node getRight() {
        return right;
    }

    public void setLeft(Node left) {
        this.left = left;
    }

    public void setRight(Node right) {
        this.right = right;
    }

    @Override
    public String toString() {
        return "Node{" +
                "data=" + data +
                '}';
    }

    /**
     * 符合BST树建立规则,左子节点小于父节点,右子节点大于父节点
     * @param newNode 要添加的结点
     */
    public void add(Node newNode){
        if (newNode.getData()<this.getData()){//小了往左放
            if (this.getLeft()==null){
                this.setLeft(newNode);
            }else {
                this.getLeft().add(newNode);
            }
        }else {//大了往右放
            if (this.getRight()==null){
                this.setRight(newNode);
            }else {
                this.getRight().add(newNode);
            }
        }
        if (rightHeight()-leftHeight()>1){//需要左旋
            if (right!=null && right.leftHeight()>right.rightHeight()){//需要局部右旋
                right.rightRotate();
                leftRotate();
            }else {//直接左旋
                leftRotate();
            }
            return;//注意,这里要返回,否则就运行下面右旋代码去了

        }
        if (leftHeight()-rightHeight()>1){//需要右旋
            if (left!=null && left.rightHeight()>left.leftHeight()){//需要局部左旋
                left.leftRotate();
                rightRotate();
            }else {//直接右旋
                rightRotate();
            }
        }
    }
    public void infixOrder(){
        if (this.left!=null){
            this.left.infixOrder();
        }
        System.out.print(this+" ");
        if (this.right!=null){
            this.right.infixOrder();
        }
    }

    /**
     * 按照输入的value,查找拥有相同值的结点
     * @param value 匹配的值
     * @return 找到返回结点,否则返回空
     */
    public Node search(int value){
        if (this.getData()==value){
            return this;
        }
        if (value<this.getData() && this.getLeft()!=null){
            return this.getLeft().search(value);
        }else if (value>=this.getData() && this.getRight()!=null){
            return this.getRight().search(value);
        }else {
            return null;
        }
    }

    /**
     * 按照value,查找到拥有相同值结点的父节点
     * @param value 匹配的值
     * @return 找到返回父节点,否则返回空
     */
    public Node searchParent(int value){
        if ((this.left != null && this.left.getData() == value) || (this.right != null && this.right.getData() == value)){
            return this;
        }
        if (value<this.getData() && this.getLeft()!=null){
            return this.getLeft().searchParent(value);
        }else if (value>=this.getData() && this.getRight()!=null){
            return this.getRight().searchParent(value);
        }else {
            return null;
        }
    }

    /**
     * 相当精妙的算法:通过递归获得二叉树的高度
     * @return 二叉树的高度
     */
    public int height(){
        //通过递归左右结点,为空时停止递归,否则继续递归,每轮递归高度加1,递归完成选取大的作为高度。佩服佩服
        return Math.max(left==null?0:left.height(),right==null?0:right.height())+1;
    }

    /**
     * 返回当前结点的左子树高度
     * @return 左子树高度
     */
    public int leftHeight(){
        if (left==null){
            return 0;
        }
        return left.height();
    }

    /**
     * 返回当前结点的右子树高度
     * @return 右子树高度
     */
    public int rightHeight(){
        if (right==null){
            return 0;
        }
        return right.height();
    }

    /**
     * 对当前结点,进行左旋平衡操作
     */
    public void leftRotate(){
        //1,复制出一个与当前结点一样的结点
        Node newNode=new Node(this.data);
        //2,将新结点的左子树与当前结点的左子节点相连,将当前结点的右子节点的左子结点与新结点的右结点相连
        newNode.setLeft(this.left);
        newNode.setRight(this.right.left);
        //3,将当前结点的右子节点的值,赋给当前结点,并将当前结点的右节点与右子节点的右子节点相连
        this.data=this.right.data;
        this.right=this.right.right;
        //4,将当前结点的左节点与新结点相连
        this.left=newNode;
    }

    /**
     * 对当前结点,进行右旋平衡操作
     */
    public void rightRotate(){
        //1,复制出一个与当前结点一样的结点
        Node newNode=new Node(this.data);
        //2,将新结点的右节点与当前结点的右子节点相连,将当前结点的左子节点的右子结点与新结点的左结点相连
        newNode.setRight(this.right);
        newNode.setLeft(this.left.right);
        //3,将当前结点的左子节点的值,赋给当前结点,并将当前结点的左节点与左子节点的左子节点相连
        this.data=this.left.data;
        this.left=this.left.left;
        //4,将当前结点的右节点与新结点相连
        this.right=newNode;
    }
}
class AVLTree{
    private Node root;

    public Node getRoot() {
        return root;
    }

    public void add(Node node){
        if (node==null){
            System.out.println("结点为空");
            return;
        }
        if (this.root==null){
            this.root=node;
        }else{
            this.root.add(node);
        }
    }
    public void infixOrder(){
        if (this.root==null){
            System.out.println("根节点为空");
            return;
        }
        this.root.infixOrder();
    }
    public Node search(int value){
        if (root==null){
            System.out.println("空树");
            return null;
        }
        return this.root.search(value);
    }
    public Node searchParent(int value){
        if (this.root==null){
            System.out.println("空树");
            return null;
        }
        return this.root.searchParent(value);
    }
    public void delete(int value) {
        if (this.root == null) {
            System.out.println("空树");
            return;
        }
        Node result = search(value);
        if (result == null) {
            System.out.println("未搜索到");
            return;
        }
        //0.只有一个节点
        if (this.root.getLeft() == null && this.root.getRight() == null) {
            this.root = null;
            return;
        }
        Node parent = searchParent(value);
        if (result.getRight() == null && result.getLeft() == null) {//1.是叶子结点
            if (parent.getRight().getData() == value) {
                parent.setRight(null);
            } else {
                parent.setLeft(null);
            }
        } else if (result.getLeft() != null && result.getRight() != null) {//2.含有左右子树的结点
            int minValue = delRightMin(result.getRight());
            result.setData(minValue);
        } else {//3.只含有一个子树的结点
            if (parent == null) {//若是根节点加一个子节点的情况(父节点为空)
                if (result.getLeft() != null) {
                    this.root = result.getLeft();
                } else {
                    this.root = result.getRight();
                }
                return;
            }
            if (result.getLeft() != null) {//含有左子树
                if (parent.getLeft().getData() == value) {//目标结点是父节点的左节点
                    parent.setLeft(result.getLeft());
                } else {//目标结点是父节点的右结点
                    parent.setRight(result.getLeft());
                }
            } else {//含有右子树
                if (parent.getLeft().getData() == value) {
                    parent.setLeft(result.getRight());
                } else {
                    parent.setRight(result.getRight());
                }
            }
        }
    }
    public int delRightMin (Node node){
        Node target = node;
        while (target.getLeft() != null) {
            target = target.getLeft();
        }
        delete(target.getData());
        return target.getData();
    }
}
public class 平衡二叉树_AVLTree {
    public static void main(String[] args) {
        AVLTree avlTree=new AVLTree();
        int[] data=new int[]{10,11,7,6,8,9};
        for (int temp:data){
            avlTree.add(new Node(temp));
        }
        avlTree.infixOrder();
        System.out.println();
        System.out.println(avlTree.getRoot().leftHeight());
        System.out.println(avlTree.getRoot().rightHeight());
        System.out.println(avlTree.getRoot().height());
        System.out.println(avlTree.getRoot());
    }
}
Node{data=6} Node{data=7} Node{data=8} Node{data=9} Node{data=10} Node{data=11} 
2
2
3
Node{data=8}

 

【红黑树】

红黑树,我在CSDN上找了下资料,感兴趣的朋友可以看一些。以后我用到了,我再来填坑。

重温数据结构:深入理解红黑树

【B树,B+树,B*树】

这3个B(树结构),很复杂。MySQL的数据库索引用的就是B+树。我在CSDN上找了下资料,感兴趣的朋友可以看一些。以后我用到了,我再来填坑。

关于B树的学习总结和B+树,B*树的简介

B树、B-树、B+树、B*树介绍

 

 


有关树结构的其他内容,见下各链接

【树结构_二叉树基础,顺序存储二叉树,线索化二叉树】

【树结构_堆排序】

【树结构_赫夫曼树,赫夫曼编码,文件解压缩】

【树结构_BST树(二叉排序树)】

 

【数据结构与算法整理总结目录 :>】<-- 宝藏在此(doge)  

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值