平衡二叉树(附Java实现源码)

java实现平衡二叉树 平衡二叉树也叫平衡二叉搜索树(Self-balancing binary search tree)又被称为AVL树,可以保证查询效率较高。具有以下特点: 它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。平衡二叉树的常用实现方法有红黑树、AVL、替罪羊树、Treap、伸展树等。举例说明, 看看下面哪些AVL树,为什么?平衡二叉搜索树的左右子树,高度差不能大于1,所以前两个满足。... 阅读详情

平衡二叉树

左旋转和右旋转的实现

左旋转

左旋转

//左旋转
    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;
    }

右旋转

右旋转

//右旋转
    public void rightRotate() {
        //创建一个新结点,把当前结点的值赋值给新结点
        Node newNode = new Node(this.value);
        //将新结点的右子树设置成当前结点的右子树
        newNode.right = this.right;
        //将新结点的左子树设置成当前结点的左子结点的右子树
        newNode.left = this.left.right;
        //将当前结点的值设置成当前结点左子结点的值
        this.value = this.left.value;
        //将当前结点的右子结点设置成新结点
        this.right = newNode;
        //将当前结点的左子结点设置成左子结点的左子树
        this.left = this.left.left;
    }

平衡二叉树的添加

步骤

  1. 如果要添加的结点为空直接退出方法
  2. 判断传入的结点的值与当前子树的根结点的关系
    1. 要添加的结点的值小于当前结点的值
      1. 若当前结点的左子结点为空,则将该结点设置为当前结点的左子结点
      2. 若当前结点的左子结点不为空,递归向左子树添加
    2. 要添加的结点的值大于当前结点
      1. 若当前结点的右子结点为空,则将该结点设置为当前结点的右子结点
      2. 若当前结点的右子结点不为空,递归向右子树添加
    3. 添加完成后,在每一层子树都进行判断
      1. 如果右子树的高度-左子树的高度 > 1 则进行左旋转
        1. 如果当前结点的右子树的左子树的高度大于当前结点的右子树的右子树的高度
          1. 先对当前结点的右子树进行右旋转
          2. 然后对当前结点进行左旋转
          3. 方法执行后直接return;从而返回上一个栈,避免左子树的高度-右子树的高度 > 1的情况的判断条件对树造成影响
      2. 左子树的高度-右子树的高度 > 1 则进行右旋转
        1. 如果当前结点的左子树的右子树的高度大于当前结点的左子树的左子树的高度
          1. 需要先对当前结点的左子树进行左旋转
          2. 然后对当前结点进行右旋转
          3. 方法执行后直接return;

代码实现

class AVLTree {

    private Node root;

    public Node getRoot() {
        return root;
    }

    //添加结点
    public void add(Node node) {
        //root为空则让root指向node
        if (this.root == null) {
            this.root = node;
        } else {
            this.root.add(node);
        }
    }

    //中序遍历
    public void infixOrder() {
        if (this.root == null) {
            System.out.println("二叉排序树为空,无法遍历!");
        } else {
            this.root.infixOrder();
        }
    }
}

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 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;
    }

    //右旋转
    public void rightRotate() {
        //创建一个新结点,把当前结点的值赋值给新结点
        Node newNode = new Node(this.value);
        //将新结点的右子树设置成当前结点的右子树
        newNode.right = this.right;
        //将新结点的左子树设置成当前结点的左子结点的右子树
        newNode.left = this.left.right;
        //将当前结点的值设置成当前结点左子结点的值
        this.value = this.left.value;
        //将当前结点的右子结点设置成新结点
        this.right = newNode;
        //将当前结点的左子结点设置成左子结点的左子树
        this.left = this.left.left;
    }

    //返回以当前结点为根结点的树的高度
    public int height() {
        //返回左子树和右子树中较大的高度再加上当前结点的高度
        return Math.max(left == null ? 0 : left.height(), right == null ? 0 : right.height()) + 1;
    }

    //返回左子树的高度
    public int leftHeight() {
        if (this.left == null) {
            return 0;
        }
        return this.left.height();
    }

    //返回右子树的高度
    public int rightHeight() {
        if (this.right == null) {
            return 0;
        }
        return this.right.height();
    }

    //添加结点
    public void add(Node node) {
        if (node == null) {
            return;
        }
        //判断传入的结点的值与当前子树的根结点的关系
        //要添加的结点的值小于当前结点的值
        if (node.value < this.value) {
            //当前结点的左子结点为空
            if (this.left == null) {
                this.left = node;
            } else {
                //递归向左子树添加
                this.left.add(node);
            }
        } else if (node.value > this.value) {//要添加的结点的值大于当前结点的值
            //当前结点的右子结点为空
            if (this.right == null) {
                this.right = node;
            } else {
                //递归向右子树添加
                this.right.add(node);
            }
        }

        //当添加完一个结点后,如果右子树的高度-左子树的高度 > 1 则左旋转
        if (rightHeight() - leftHeight() > 1) {
            //如果当前结点的右子树的左子树的高度大于当前结点的右子树的右子树的高度
            //则需要先对当前结点的右子树进行右旋转
            //因为rightHeight() - leftHeight() > 1 所以this.right肯定不为null
            if (this.right.leftHeight() > this.right.rightHeight()) {
                this.right.rightRotate();
            }
            //左旋转
            leftRotate();
            //一定要return!因为旋转完后已经平衡,
            //如果还让下面的进行判断,很有可能出问题
            return;
        }
        //左子树的高度-右子树的高度 > 1 则右旋转
        if (leftHeight() - rightHeight() > 1) {
            //如果当前结点的左子树的右子树的高度大于当前结点的左子树的左子树的高度
            //则需要先对当前结点的左子树进行左旋转
            //因为leftHeight() - rightHeight() > 1 所以this.left肯定不为null
            if (this.left.rightHeight() > this.left.leftHeight()) {
                this.left.leftRotate();
            }
            //右旋转
            rightRotate();
            return;
        }
    }

    //中序遍历
    public void infixOrder() {
        if (this.left != null) {
            this.left.infixOrder();
        }
        System.out.println(this);
        if (this.right != null) {
            this.right.infixOrder();
        }
    }
}

平衡二叉树的删除

​ 删除操作,也和二叉排序树的删除差不多,只是在删除完结点后,要重新判断左右孩子的深度,如果失衡,就要调整。

平衡二叉树删除结点的三种情况

  1. 被删除的结点为叶子结点,就找到了要删除的结点
  2. 被删除的结点有左子树或者右子树
  3. 被删除的结点既有左子树,又有右子树

删除叶子结点

例子1:

4a318a9b51af466ebe7969ed94af8920
  1. 我们删除14结点,递归返回
  2. 返回到双亲结点10,判断结点的10的左右深度,发现结点10的左子树的高度-右子树的高度=1,因此不用做调整,递归返回
  3. 返回到根结点20,判断结点20的左右深度,发现结点20左子树的高度-右子树的高度=-1,因此不用做调整
  4. 所以,调整后,不用做任何调整,得到平衡二叉树
d142348ecc0d43e08b0543e7c13bbc5d

例子2:

bd7f0a184c3f46a6abf4491d82cb90f2

​ 我们删除结点7,可以发现是个叶子结点,直接删除,得到下面这棵二叉树:

1b824e8c3ba14c8f8f36fae583c40467

​ 可以发现,原本结点7的父结点8的左右子树的高度都为0,达到平衡。故继续向上查找,发现8的父结点20的左子树高度与右子树高度差值为2,也就是该结点20失衡。需要进行调整。这个地方要进行何种调整呢?这里发现结点30的左子树高度大于右子树高度,则我们需要先对以结点30为根结点的子树进行右旋转得到下图。

19211146-24b59dec7cd1cc25

​ 接下来对以20为根结点的树进行左旋,得到下图,这就是调整后的树的形状。

19211146-c7e9218770f3de34

例子3:

19211146-3442ce1ac05c8405

将上图中结点8删除,得到下图:

19211146-4d37af544fd40b67

可以观察到该树处于失衡状态。结点20处于平衡状态,结点25的左右子树高度差为2,需要进行调整。我们找到结点20的右子树上的子结点30,发现结点30的左右子树高度一样,因此我们只需进行RR型调整,也就是左旋一次,根据结点30进行逆时针旋转。调整后的树如下:

19211146-03f625b6deb99574

删除叶子结点的步骤

  1. 将该结点直接从树中删除

2.向上递归判断父亲或者祖先是否失衡

3.如果其父结点未失衡,则继续向上检索推算其父结点的父结点是否失衡…如此反复2的判断,直到根结点;如果向上推算过程中发现了失衡的现象,则进行4的处理;

4.如果其父结点失衡

  • 在调整该父结点时,左子树的高度-右子树的高度 > 1 则右旋转,如果当前结点的左子树的右子树的高度大于当前结点的左子树的左子树的高度,则需要先对当前结点的左子树进行左旋转,然后再对父结点进行右旋转;
  • 如果右子树的高度-左子树的高度 > 1 则左旋转,如果当前结点的右子树的左子树的高度大于当前结点的右子树的右子树的高度,则需要先对当前结点的右子树进行右旋转,再对父结点进行左旋转。

5.如果平衡化处理结束后,发现与原来以父结点为根结点的树的高度发生变化,则继续进行2的检索推算;如果与原来以父结点为根结点的高度一致时,则可说明父结点的父结点及祖先结点的平衡因子将不会有变化,因此可以退出处理

删除结点只有左子树或只有右子树

例子1:

19211146-288574fffcbdd2d7

​ 在该平衡树中,我们要删除的是值为29的结点。我们用该结点的左子树替换该结点,然后删除值为29这个结点。得到树的形状如下:

19211146-69a0576abe4183f6

​ 可以看到该树已经处于平衡状态。不需要进行处理。

​ 如果将例子1中值为40的结点删除,先用其右子树与之替换,然后删除该结点,可以得到如下的形状:

19211146-cd247446043fd7d4

​ 后面的操作与删除叶子结点一样。

​ 可以发现该树失衡。我们找到值为50的结点,找到其父结点,结点值为30。然后找到其左孩子,结点值为25。发现该结点的右子树比左子树高度高。因此我们这里要先进行左旋,也就是绕值为29的结点进行逆时针旋转,得到下图:

19211146-c3367545fd20d148

​ 接下来进行右旋,得到下图。

111

删除结点有左子树或右子树的步骤

  1. 将左子树(右子树)替代原有删除结点的位置

2.向上递归判断父亲或者祖先是否失衡

3.如果其父结点未失衡,则继续向上检索推算其父结点的父结点是否失衡…如此反复2的判断,直到根结点;如果向上推算过程中发现了失衡的现象,则进行4的处理;

4.如果其父结点失衡

  • 在调整该父结点时,左子树的高度-右子树的高度 > 1 则右旋转,如果当前结点的左子树的右子树的高度大于当前结点的左子树的左子树的高度,则需要先对当前结点的左子树进行左旋转,然后再对父结点进行右旋转;
  • 如果右子树的高度-左子树的高度 > 1 则左旋转,如果当前结点的右子树的左子树的高度大于当前结点的右子树的右子树的高度,则需要先对当前结点的右子树进行右旋转,再对父结点进行左旋转。

5.如果平衡化处理结束后,发现与原来以父结点为根结点的树的高度发生变化,则继续进行2的检索推算;如果与原来以父结点为根结点的高度一致时,则可说明父结点的父结点及祖先结点的平衡因子将不会有变化,因此可以退出处理

删除结点既有左子树又有右子树

例子1:

19211146-490904d69778a7e3

​ 我们从该平衡树中删除结点20,我们分析得到结点20的左子树的高度-右子树的高度=1,因此我们从结点20的左子树中找到最大值,最大值为结点15。我们对两个结点的值进行替换。得到的树的形状如下所示:

19211146-1a26bf6dafed2967

接下来删除结点20,得到下图。

19211146-818e8b563dd4f584

​ 发现原先删除的结点20的父结点10的左子树高度-右子树高度=2,处于失衡状态。因此需要对以10为根结点的子树进行调整,要先进行一次左旋操作,得到下图:

19211146-cc96d005174b37d9

​ 再进行一次右旋操作。最后得到的树的形状如下:

19211146-d673958001aa1c9d

删除结点有左右子树的步骤

  1. 如果当前结点的左子树的高度大于等于右子树的高度 则从左子树中找最大值和要删除的结点的值进行替换
  2. 如果当前结点右子树的高度大于左子树的高度 则从右子树中找最小值和要删除的结点的值进行替换
  3. 替换完后,左子树的最大值的结点(右子树的最小值的结点)就变成了我们要删除的对象
  4. 最大值在左子树的位置要么是叶子结点,要么是没有右子树的一个结点,最小值在左子树的位置要么是叶子结点,要么是没有左子树的一个结点
  5. 最后,只需再按照删除叶子结点或者只有一棵子树的结点的方法删除即可

代码实现

​ 该方法用于删除叶子结点或者有一个子结点的情况下,对失衡进行调整。

public void adjust(Node parent) {
        while (parent != null) {
            //如果右子树的高度-左子树的高度 > 1 则左旋转
            if (parent.rightHeight() - parent.leftHeight() > 1) {
                //如果当前结点的右子树的左子树的高度大于当前结点的右子树的右子树的高度
                //则需要先对当前结点的右子树进行右旋转
                //因为rightHeight() - leftHeight() > 1 所以this.right肯定不为null
                if (parent.right.leftHeight() > parent.right.rightHeight()) {
                    parent.right.rightRotate();
                }
                //左旋转
                parent.leftRotate();
            }
            //左子树的高度-右子树的高度 > 1 则右旋转
            else if (parent.leftHeight() - parent.rightHeight() > 1) {
                //如果当前结点的左子树的右子树的高度大于当前结点的左子树的左子树的高度
                //则需要先对当前结点的左子树进行左旋转
                //因为leftHeight() - rightHeight() > 1 所以this.left肯定不为null
                if (parent.left.rightHeight() > parent.left.leftHeight()) {
                    parent.left.leftRotate();
                }
                //右旋转
                parent.rightRotate();
            }
            parent = searchParent(parent.value);
        }
    }

​ 完整的代码实现

class AVLTree {

    private Node root;

    public Node getRoot() {
        return root;
    }

    //添加结点
    public void add(Node node) {
        //root为空则让root指向node
        if (this.root == null) {
            this.root = node;
        } else {
            this.root.add(node);
        }
    }

    //中序遍历
    public void infixOrder() {
        if (this.root == null) {
            System.out.println("二叉排序树为空,无法遍历!");
        } else {
            this.root.infixOrder();
        }
    }

    //查找要删除的结点
    public Node search(int value) {
        if (this.root == null) {
            return null;
        } else {
            return this.root.search(value);
        }
    }

    public Node search(int value,Node virtualRoot) {
        if (virtualRoot == null) {
            return null;
        } else {
            return virtualRoot.search(value);
        }
    }

    //查找要删除结点的父结点
    public Node searchParent(int value) {
        if (this.root == null) {
            return null;
        } else {
            return this.root.searchParent(value);
        }
    }

    public Node searchParent(int value,Node virtualRoot) {
        if (virtualRoot == null) {
            return null;
        } else {
            return virtualRoot.searchParent(value);
        }
    }

    public void adjust(Node parent) {
        while (parent != null) {
            //如果右子树的高度-左子树的高度 > 1 则左旋转
            if (parent.rightHeight() - parent.leftHeight() > 1) {
                //如果当前结点的右子树的左子树的高度大于当前结点的右子树的右子树的高度
                //则需要先对当前结点的右子树进行右旋转
                //因为rightHeight() - leftHeight() > 1 所以this.right肯定不为null
                if (parent.right.leftHeight() > parent.right.rightHeight()) {
                    parent.right.rightRotate();
                }
                //左旋转
                parent.leftRotate();
            }
            //左子树的高度-右子树的高度 > 1 则右旋转
            else if (parent.leftHeight() - parent.rightHeight() > 1) {
                //如果当前结点的左子树的右子树的高度大于当前结点的左子树的左子树的高度
                //则需要先对当前结点的左子树进行左旋转
                //因为leftHeight() - rightHeight() > 1 所以this.left肯定不为null
                if (parent.left.rightHeight() > parent.left.leftHeight()) {
                    parent.left.leftRotate();
                }
                //右旋转
                parent.rightRotate();
            }
            parent = searchParent(parent.value);
        }
    }

    /**
     * @param node 传入的结点
     * @return 返回以node为根节点的二叉排序树的最小结点的值
     */
    public Node findRightTreeMin(Node node) {
        Node target = node;
        while (target.left != null) {
            target = target.left;
        }
        return target;
    }

    /**
     * @param node 传入的结点
     * @return 返回以node为根节点的二叉排序树的最大结点的值
     */
    public Node findLeftTreeMax(Node node) {
        Node target = node;
        while (target.right != null) {
            target = target.right;
        }
        return target;
    }

    //删除结点
    /*
        第一种情况: 删除叶子节点 (比如:2, 5, 9, 12)
            思路
            (1) 需求先去找到要删除的结点 targetNode
            (2) 找到 targetNode 的 父结点 parent
            (3) 确定 targetNode 是 parent 的左子结点 还是右子结点
            (4) 根据前面的情况来对应删除 左子结点 parent.left = null 右子结点 parent.right = null;
        第二种情况: 删除只有一颗子树的节点 比如 1 思路
            (1) 需求先去找到要删除的结点 targetNode
            (2) 找到 targetNode 的 父结点 parent
            (3) 确定 targetNode 的子结点是左子结点还是右子结点
            (4) targetNode 是 parent 的左子结点还是右子结点
            (5) 如果 targetNode 有左子结点
                5. 1 如果 targetNode 是 parent 的左子结点
                    parent.left = targetNode.left;
                5.2 如果 targetNode 是 parent 的右子结点 parent.right = targetNode.left;
            (6) 如果 targetNode 有右子结点
                6.1 如果 targetNode 是 parent 的左子结点 parent.left = targetNode.right;
                6.2 如果 targetNode 是 parent 的右子结点 parent.right = targetNode.right;
         情况三 : 删除有两颗子树的节点. (比如:7, 3,10 )
             思路
             (1) 需求先去找到要删除的结点 targetNode
             (2) 找到 targetNode 的 父结点 parent
             (3) 从 targetNode 的右子树找到最小的结点
             (4) 用一个临时变量,将 最小结点的值保存 temp = 11
             (5) 删除该最小结点
             (6) targetNode.value = temp
     */

    public void delNode(int value) {
        if (this.root == null) {
            return;
        } else {
            //找到要删除的结点
            Node targetNode = search(value);
            //没有找到
            if (targetNode == null) {
                return;
            }
            //如果二叉树只有一个结点,而且前面又找到了要删除的结点
            //则说明根节点就是我们要删除的结点
            if (this.root.left == null && this.root.right == null) {
                this.root = null;
                return;
            }
            //找到targetNode 父结点
            Node parent = searchParent(value);
            //如果要删除的结点是叶子结点
            if (targetNode.left == null && targetNode.right == null) {
                //判断targetNode是父结点的左子结点还是右子结点
                if (parent.left != null && parent.left.value == value) {
                    parent.left = null;
                } else if (parent.right != null && parent.right.value == value) {
                    parent.right = null;
                }
                //删除后对每个父节点进行检查是否失衡并进行调整
                adjust(parent);
                //如果targetNode有左右子树
            } else if (targetNode.left != null && targetNode.right != null) {
                //如果targetNode左子树的高度大于等于右子树的高度 则从左子树中找最大值和要删除的结点的值进行替换
                //替换完后,左子树的最大值的结点就变成了我们要删除的对象
                //而最大值在左子树的位置要么是叶子结点,要么是没有右子树的一个结点
                //则只需再按照删除叶子结点或者只有一棵子树的结点的方法删除即可
                if (targetNode.leftHeight() - targetNode.rightHeight() >= 0) {
                    Node LeftMaxNode = findLeftTreeMax(targetNode.left);
                    int temp = LeftMaxNode.value;
                    LeftMaxNode.value = targetNode.value;
                    targetNode.value = temp;
                    delNode(LeftMaxNode.value,targetNode.left);
                }else {
                    //如果targetNode右子树的高度大于左子树的高度 则从右子树中找最小值和要删除的结点的值进行替换
                    //替换完后,右子树的最小值的结点就变成了我们要删除的对象
                    //而最小值在左子树的位置要么是叶子结点,要么是没有左子树的一个结点
                    //则只需再按照删除叶子结点或者只有一棵子树的结点的方法删除即可
                    Node rightMaxNode = findRightTreeMin(targetNode);
                    int temp = rightMaxNode.value;
                    rightMaxNode.value = targetNode.value;
                    targetNode.value = temp;
                    delNode(rightMaxNode.value,targetNode.right);
                }
            } else { //只有一个子结点
                //如果targetNode有左子结点
                if (targetNode.left != null) {//需要考虑删除的结点是不是根结点
                    if (parent != null) {//当要删除的结点不是根结点
                        //如果targetNode是parent的左子结点
                        if (parent.left.value == value) {
                            parent.left = targetNode.left;
                        } else {//targetNode是parent的右子结点
                            parent.right = targetNode.left;
                        }
                    } else {//要删除的结点是根结点 删除后不需要进行调整
                        this.root = targetNode.left;
                    }
                } else { //targetNode有右子结点
                    if (parent != null) {//要删除的结点不是根结点
                        //targetNode 是 parent的左子结点
                        if (parent.left.value == value) {
                            parent.left = targetNode.right;
                        } else {//targetNode是parent的右子结点
                            parent.right = targetNode.right;
                        }
                    } else {//要删除的结点是根结点
                        this.root = targetNode.right;
                    }
                }
                //删除后对每个父节点进行检查是否失衡并进行调整
                adjust(parent);
            }
        }
    }

    //重载删除方法,用于将删除有左子树和右子树的结点转化成删除叶子节点或单子树结点
    public void delNode(int value,Node virtualRoot) {
        if (virtualRoot == null) {
            return;
        } else {
            //找到要删除的结点
            Node targetNode = search(value,virtualRoot);
            //没有找到
            if (targetNode == null) {
                return;
            }
            //如果二叉树只有一个结点,而且前面又找到了要删除的结点
            //则说明根节点就是我们要删除的结点
            if (virtualRoot.left == null && virtualRoot.right == null) {
                virtualRoot = null;
                return;
            }
            //找到targetNode 父结点
            Node parent = searchParent(value,virtualRoot);
            //如果要删除的结点是叶子结点
            if (targetNode.left == null && targetNode.right == null) {
                //判断targetNode是父结点的左子结点还是右子结点
                if (parent.left != null && parent.left.value == value) {
                    parent.left = null;
                } else if (parent.right != null && parent.right.value == value) {
                    parent.right = null;
                }
                //删除后对每个父节点进行检查是否失衡并进行调整
                adjust(parent);
                //如果targetNode有左右子树
            } else if (targetNode.left != null && targetNode.right != null) {
                //如果targetNode左子树的高度大于等于右子树的高度 则从左子树中找最大值和要删除的结点的值进行替换
                //替换完后,左子树的最大值的结点就变成了我们要删除的对象
                //而最大值在左子树的位置要么是叶子结点,要么是没有右子树的一个结点
                //则只需再按照删除叶子结点或者只有一棵子树的结点的方法删除即可
                if (targetNode.leftHeight() - targetNode.rightHeight() >= 0) {
                    Node LeftMaxNode = findLeftTreeMax(targetNode.left);
                    int temp = LeftMaxNode.value;
                    LeftMaxNode.value = targetNode.value;
                    targetNode.value = temp;
                    delNode(LeftMaxNode.value,virtualRoot);
                }else {
                    //如果targetNode右子树的高度大于左子树的高度 则从右子树中找最小值和要删除的结点的值进行替换
                    //替换完后,右子树的最小值的结点就变成了我们要删除的对象
                    //而最小值在左子树的位置要么是叶子结点,要么是没有左子树的一个结点
                    //则只需再按照删除叶子结点或者只有一棵子树的结点的方法删除即可
                    Node rightMaxNode = findRightTreeMin(targetNode);
                    int temp = rightMaxNode.value;
                    rightMaxNode.value = targetNode.value;
                    targetNode.value = temp;
                    delNode(rightMaxNode.value,virtualRoot);
                }
            } else { //只有一个子结点
                //如果targetNode有左子结点
                if (targetNode.left != null) {//需要考虑删除的结点是不是根结点
                    if (parent != null) {//当要删除的结点不是根结点
                        //如果targetNode是parent的左子结点
                        if (parent.left.value == value) {
                            parent.left = targetNode.left;
                        } else {//targetNode是parent的右子结点
                            parent.right = targetNode.left;
                        }
                    } else {//要删除的结点是根结点 删除后不需要进行调整
                        virtualRoot = targetNode.left;
                    }
                } else { //targetNode有右子结点
                    if (parent != null) {//要删除的结点不是根结点
                        //targetNode 是 parent的左子结点
                        if (parent.left.value == value) {
                            parent.left = targetNode.right;
                        } else {//targetNode是parent的右子结点
                            parent.right = targetNode.right;
                        }
                    } else {//要删除的结点是根结点
                        virtualRoot = targetNode.right;
                    }
                }
                //删除后对每个父节点进行检查是否失衡并进行调整
                adjust(parent);
            }
        }
    }

}

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 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;
    }

    //右旋转
    public void rightRotate() {
        //创建一个新结点,把当前结点的值赋值给新结点
        Node newNode = new Node(this.value);
        //将新结点的右子树设置成当前结点的右子树
        newNode.right = this.right;
        //将新结点的左子树设置成当前结点的左子结点的右子树
        newNode.left = this.left.right;
        //将当前结点的值设置成当前结点左子结点的值
        this.value = this.left.value;
        //将当前结点的右子结点设置成新结点
        this.right = newNode;
        //将当前结点的左子结点设置成左子结点的左子树
        this.left = this.left.left;
    }

    //返回以当前结点为根结点的树的高度
    public int height() {
        //返回左子树和右子树中较大的高度再加上当前结点的高度
        return Math.max(left == null ? 0 : left.height(), right == null ? 0 : right.height()) + 1;
    }

    //返回左子树的高度
    public int leftHeight() {
        if (this.left == null) {
            return 0;
        }
        return this.left.height();
    }

    //返回右子树的高度
    public int rightHeight() {
        if (this.right == null) {
            return 0;
        }
        return this.right.height();
    }

    //添加结点
    public void add(Node node) {
        if (node == null) {
            return;
        }
        //判断传入的结点的值与当前子树的根结点的关系
        //要添加的结点的值小于当前结点的值
        if (node.value < this.value) {
            //当前结点的左子结点为空
            if (this.left == null) {
                this.left = node;
            } else {
                //递归向左子树添加
                this.left.add(node);
            }
        } else if (node.value > this.value) {//要添加的结点的值大于当前结点的值
            //当前结点的右子结点为空
            if (this.right == null) {
                this.right = node;
            } else {
                //递归向右子树添加
                this.right.add(node);
            }
        }

        //当添加完一个结点后,如果右子树的高度-左子树的高度 > 1 则左旋转
        if (rightHeight() - leftHeight() > 1) {
            //如果当前结点的右子树的左子树的高度大于当前结点的右子树的右子树的高度
            //则需要先对当前结点的右子树进行右旋转
            //因为rightHeight() - leftHeight() > 1 所以this.right肯定不为null
            if (this.right.leftHeight() > this.right.rightHeight()) {
                this.right.rightRotate();
            }
            //左旋转
            leftRotate();
            //一定要return!因为旋转完后已经平衡,
            //如果还让下面的进行判断,很有可能出问题
            return;
        }
        //左子树的高度-右子树的高度 > 1 则右旋转
        if (leftHeight() - rightHeight() > 1) {
            //如果当前结点的左子树的右子树的高度大于当前结点的左子树的左子树的高度
            //则需要先对当前结点的左子树进行左旋转
            //因为leftHeight() - rightHeight() > 1 所以this.left肯定不为null
            if (this.left.rightHeight() > this.left.leftHeight()) {
                this.left.leftRotate();
            }
            //右旋转
            rightRotate();
            return;
        }
    }

    //中序遍历
    public void infixOrder() {
        if (this.left != null) {
            this.left.infixOrder();
        }
        System.out.println(this);
        if (this.right != null) {
            this.right.infixOrder();
        }
    }

    //查找要删除的结点
    public Node search(int value) {
        if (value == this.value) {
            return this;
        } else if (value < this.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 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);
            }
        }
        //没有找到
        return null;
    }
}

对代码中的一些解释:

为什么要重载delNode、searchParent、search方法?

对于第三种情况,既有左子树又又右子树,我们是把要删除的结点和子树中的最大或最小值的位置进行替换,把删出有左子树和右子树的情况换成了删除叶子结点或者有一个子结点的情况,但是,替换的的位置往往和它的值的大小所该在的子树不符,比如:从左子树找最大值和要删除的结点(也就是左子树的父结点)进行交换,左子树的值全部都是小于父结点的,这时候就相当于,左子树中有了一个大于根结点的值,而在查找的时候,默认大于根结点的值都在右子树,使用默认的删除方法、查找方法、查找父结点的方法都是从根结点开始查找的,是无法查到要删除的结点的,这时候就需要重载一下这三个方法,修改其根结点为传入的子树的根结点,这样就可以查找并删除它了。

【Java集合源码剖析】TreeMap源码剖析 前言 本文不打算延续前几篇的风格(对所有的源码加入注释),因为要理解透TreeMap的所有源码,对博主来说,确实需要耗费大量的时间和经历,目前看来不大可能有这么多时间的投入,故这里意在通过于阅读源码对TreeMap有个宏观上的把握,并就其中一些方法的实现做比较深入的分析。 红黑树简介 TreeMap是基于红黑树实现的,这里只对红黑树做个简单的介绍,红黑树是一种特殊的二叉排序树,关于二叉排序树,参见:http://blog.youkuaiyun.com/ns_code/article/details/ 阅读详情

相关推荐

AUTOSAR开发实战:NvM模块如何解决汽车电子中的EEPROM与Flash存储难题?

本文深入探讨了AUTOSAR中的NvM模块如何有效解决汽车电子系统中EEPROM与Flash存储的难题。通过分层架构设计,NvM模块实现了硬件抽象与统一管理,支持数据一致性检查、错误恢复等高级功能,显著提升了存储可靠性和耐久性。文章结合实战案例,详细解析了NvM的核心机制及工程优化方案,为汽车电子开发者提供宝贵参考。

e3f4g5的博客 563

平衡二叉树(AVL)【java实现+图解】

一种自平衡二叉搜索树,它是在每个节点上增加一个平衡因子,然后通过调整树中节点的高度来保持树的平衡。平衡因子是左子树的高度减去右子树的高度,用它可以表示出当前节点的平衡程度。删除:每删除一个节点后,也需要更新输的高度,平衡因子可能改变,依据平衡因子对二叉树进行调整。添加:每次添加一个节点后更新输的高度,平衡因子可能改变,依据平衡因子对二叉树进行调整;判断是否是平衡二叉树:通过判断每个子树的平衡因子的是否在[-1,1]区间内。,当一个节点的平衡因子绝对值大于1时,这个节点就被称为不平衡节点。

weixin_63541561的博客 1646

告别C盘爆红!手把手教你将WSL2下的Ubuntu 22.04完整迁移到D盘(附清理指南)

本文详细指导如何将WSL2下的Ubuntu 22.04从C盘无损迁移到D盘,包括导出备份、注销原系统、导入新位置等关键步骤,并提供迁移后的优化配置、开发环境衔接及长期维护技巧,帮助用户彻底解决C盘空间不足问题。

weixin_28698059的博客 472

平衡二叉树java实现(插入、删除、平衡调整)

平衡二叉树的构建、删除、调整算法分析及java实现

No_Words_的博客 952

StarRocks慢查询排查实战:从Query Plan到Profile的保姆级调优指南

本文提供StarRocks慢查询排查的实战指南,从Query Plan解析到Profile诊断,详细讲解如何识别执行计划中的危险信号和优化关键指标。通过物化视图优化、分布式执行调整和资源管控方案,帮助DBA快速定位并解决查询性能问题,提升数据库效率。

weixin_42568801的博客 378

CMU开源NavRL:深度强化学习如何破解无人机动态避障难题?

卡内基梅隆大学(CMU)开源了NavRL框架,利用深度强化学习解决无人机在复杂环境中的动态避障难题。该框架通过创新的状态表示、多目标奖励函数设计以及独立的安全屏障机制,使无人机能够通过仿真训练自主学会灵活、安全的导航策略,并成功实现从仿真到真实环境的零样本迁移。

hope8的博客 720

Java数据结构——平衡二叉树(AVL树)

AVL树的引入 搜索二叉树有着极高的搜索效率,但是搜索二叉树会出现以下极端情况: 这样的二叉树搜索效率甚至比链表还低。在搜索二叉树基础上出现的平衡二叉树(AVL树)就解决了这样的问题。当平衡二叉树(AVL树)的某个节点左右子树高度差的绝对值大于1时,就会通过旋转操作减小它们的高度差。 基本概念 AVL树本质上还是一棵二叉搜索树,它的特点是: 本身首先是一棵二叉搜索树。 每个结点的左右子树的高度之差的绝对值(平衡因子)最多为1。也就是说,AVL树,本质上是带了平衡功能的二叉查找树(二叉排序树,二叉搜索树)

m0_62969222的博客 3691

W5500以太网&PoE扩展模块原理图设计,已量产

本文将以 W5500 以太网 & PoE 扩展板为对象,从嵌入式硬件设计的视角,深度拆解其电路架构、核心器件选型、关键功能实现,以及这款板子解决行业痛点的设计思路。

不脱发的程序猿 933

Java与数据结构——平衡二叉树(AVL)

给定数列{ 10,12,8,9,7,6 },构建 AVL 树,当插入 6 时,发现 leftHeight - rightHeight > 1 ,此时不再是一个 AVL 树,进行。:给定数列{ 4,3,6,5,7,8 },构建 AVL 树,当插入 8 时,发现 rightHeight - leftHeight > 1 ,此时不再是一个 AVL 树,进行。(3)把新节点的右子树设置成当前节点的右子树的左子树,newNode.right = this.right.left;

u012621831的博客 1404

如何使用freesurfer对T1w结构像进行分区指标的提取?——皮层面积,灰质体积,平均厚度,厚度标准差,平均曲率、高斯曲率、曲率指数、折叠指数

如何使用freesurfer对T1w结构像进行分区指标的提取?——皮层面积,灰质体积,平均厚度,厚度标准差,平均曲率、高斯曲率、曲率指数、折叠指数

从事脑科学核磁共振方法学研究,在Nature communications等权威期刊发表研究论文,熟练掌握磁共振处理方法和统计学方法,欢迎大家和我交流。 3786

研制过程评审活动(三)工程研制阶段

工程研制阶段主要任务、及评审活动

Abel的博客 2387

数据结构,java实现平衡二叉树,AVL树

之前看HashMap源码的时候遇到一些问题,jdk1.8中对HashMap做了一些优化,其中就包括当链表节点大于8个时会将链表转换成红黑树以提高查找效率。我对红黑树了解的比较少,而且之前学的二叉树相关的知识也忘了许多,所以决定近期去复习一下。 红黑树是一种平衡二叉树,平衡二叉树又属于二叉搜索树(二叉排序树)。这几天我就从二叉搜索树开始,把这几种数据结构都用java实现了一遍,二叉排序树比较简单没...

Numblgw的博客 336

STM32+FreeRTOS环境下LwIP协议栈移植详解

本文详细介绍了在STM32F407ZGT6平台上移植轻量级TCP/IP协议栈LwIP的过程。使用FreeRTOS操作系统和LAN8720A以太网PHY芯片,通过STM32CubeMX生成基础工程配置。重点包括LwIP源码的获取与工程集成、关键配置文件的编写(如lwipopts.h)、操作系统适配层sys_arch.c的实现,以及以太网接口驱动ethernetif.c的移植。文章特别强调内存管理、中断优先级、硬件校验和等关键配置项的注意事项,为基于STM32和LwIP的嵌入式网络开发提供了实用参考。

电脑管家 751

Java集合系列源码分析(七)-- 平衡二叉树

一、平衡二叉树 平衡二叉树(Balanced Binary Tree)是为了防止树的高度增长过快,出现类似于上一节最后举出的那种特殊情况,降低了二叉排序树的性能。 于是人们规定,保证任意节点的左、右子树高度差的绝对值不超过1,这样的二叉树成为平衡二叉树。 二、平衡二叉树的方法 首先,平衡二叉树操作中始终需要保持平衡,因此必须要准守平衡原则,平衡二叉树的平衡原则是:每次需要调整的都是最小不平衡二叉...

Double____C的博客 361

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

平衡二叉树 AVL树结构详解 [Java实现]--源码部分

作者NeroJings来源https://blog.youkuaiyun.com/zhang6622056/article/details/82698859正文部分见:平衡二叉树 A...

纯洁的微笑 910

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

平衡二叉树Java语言实现

作为二叉查找树的升级版,平衡二叉树(AVL-Tree)可以有效保障树的深度,防止因为插入操作的顺序和删除操作等导致二叉查找树左右过于不均衡,下面的代码也是书上的源码代码,稍微改动和增加了些。 内部节点AvlNode: /** * Node of AVLTree * @author LQC * * @param element extends Comparable */

whucs2的专栏 427

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

Java平衡二叉树实现

平衡二叉树自己看书上定义。 直接上源码 若方法没写全,后续会补上。 public class BTS<Key extends Comparable<Key>,Value> { private class Node { private Key key; //键,用于比较,用Comparable类的子类来进行比较 private Value val; //值 private Node left; private Node rig

Pink_Brook的博客 265

映射表map(平衡二叉树实现)_手动实现Java集合容器之TreeMap(上)

上一篇我们手写了HashMap,还有一个很重要的Map的实现类TreeMap。打开源码第一句话:* A Red-Black tree based {@link NavigableMap} implementation.TreeMap是一个基于红黑树的实现。对红黑树没有了解怎么办,那就先搞清楚红黑树的原理。只要理解红黑树的玩法,TreeMap实现起来就没有那么大的难度了。红黑树(Red Black ...

weixin_39663378的博客 195

[转]C++实现平衡二叉树

作者:Rest探路者出处:http://www.cnblogs.com/Java-Starter/本文版权归作者和博客园共有,欢迎转载,但未经作者同意请保留此段声明,请在文章页面明显位置给出原文连接Github:https://github.com/cjy513203427 目录 1.概念 2.代码实现 2.1 定义平衡树结点: 2.2 左改组图解 2...

anjie1937的博客 190

Java平衡二叉树、AVL树的实现,节点增删自平衡

Java平衡二叉树、AVL树详解关于树的基本概念关于二叉树的特点AVL树、平衡二叉树添加节点使得树失衡左失衡、右失衡左右失衡、右左失衡失衡节点重平衡代码实现添加节点删除节点 关于树的基本概念 - 树定义:树是n(n>=0)个有限节点的集合,当n=0时称为空树,当n>0时,这些节点满足:每个节点可以有m(m>=0)个子节点;除根节点外每个节点有且只有一个父节点,根节点没有父节点;任意节点的子节点也是一棵树 - 节点:如上图所示的A、B、C···为为一个个节点,节点是树的最小单位,是存储数据

heng_zou的博客 569

平衡二叉树的旋转_基于Java8的红黑二叉树之红黑颜色的源码分析,看不懂别怪我,看懂了你就彻底明白了...

前言第一次发知乎,很忐忑求轻喷,如果标题太猖狂,那你也不能骂我,大家将就下祖国的花朵;本文基于读者已经明白了二叉树,也明白什么是平衡二叉树,对红黑有疑问的朋友进行解惑;红黑对于我是一个困扰多年的问题,几年前我带别人看源码,看插入移除元素,始终没有去看红黑颜色的更改,同时也感觉红黑颜色不仅仅时象征,肯定有某种深意;注意,仅对红黑颜色做主要解释,因为笔者有这个疑惑在网上没找到想要的答案,所以自己花时间...

weixin_39716703的博客 191

平衡二叉树的删除实现---递归

关于平衡二叉树,我查阅了一下资料,在删除部分的实现,目前还不是很完善。于是我自己动手写了一个递归删除,每一部分都做了注释,可以把代码直接拷贝到环境中进行测试。测试代码:public class TestAVLTree {      public static void main(String[] args) {          AVLTree&lt;Integer&gt; tree = new ...

GNin的博客 443

Java集合源码分析(五):平衡二叉树(AVL Tree)

在上一章的文章中,我们讲到了二叉排序树,它很好的平衡了插入与查找的效率,但二叉排序树如果不平衡,那么查找效率就会大大降低,今天要讲的这个平衡二叉树就是一种解决这个问题的方法。 一、平衡二叉树的定义 平衡二叉树 是一种二叉排序树,其中每一个节点的左子树和右子树的高度差至多等于1 。它是一种高度平衡的二叉排序树。意思是说,要么它是一棵空树,要么它的左子树和右子树都是平衡二叉树,且左子树和右子树的深度之差的绝对值不超过1 。我们将二叉树上结点的左子树深度减去右子树深度的值称为平衡因子BF (Balance Fac

不会编程的派大星的博客 318

深入理解红黑树原理与实现(附Java源码)

红黑树(RBTree)是一种相比平衡二叉树(AVL)平衡要求较低的的一种二叉搜索树,所谓平衡要求较低的意思是相比AVL树的每个节点的左右子树的高度差不能超过2,红黑树使用红黑两种颜色来标记二叉搜索树中的节点,并对这种着色进行限制,使得在插入删除操作后对不符合的情况必须进行调整来保持这样一种限制,从而实现自我平衡。我们先来看一下红黑树的定义,也就是着色限制 * 每个节点必须着色成红色或者黑色

Wenqi's Blog 2379

Java集合源码分析之基础(五):平衡二叉树(AVL Tree)

二叉排序树很好的平衡了插入与查找的效率,但不平衡的二叉排序树效率大打折扣。今天介绍的AVL树就是一种解决此问题的方案。 定义 平衡二叉树(Self-Balancing Binary Search Tree 或Height-Balanced Binary Search Tree),是一种二叉排序树,其中每一个节点的左子树和右子树的高度差至多等于1 。它是一种高度平衡的二叉排序树。意思是说,要么它...

qq_39409110的博客 189
上一篇: SpringSecurity
下一篇: 贪心算法解决电台覆盖问题
Sancean
博客等级 码龄6年 2472粉丝 149原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值