二叉排序树BST(Binary Sort/Search Tree)

二叉排序树

1.基本简介

二叉排序树:BST(Binary Sort/Search Tree),对于二叉排序树的任何一个非叶子节点,要求左子节点的值比当前节点的值小,右子节点的值比当前节点的值大。

特别说明:如果有相同的值,可以将该节点放在左子节点或右子节点。

比如当前数列{7,3,10,12,5,1,9},对应的二叉排序树为:
在这里插入图片描述

二叉排序树的特点:可以高效的完成对数据的查询和添加。

补充:

数组:查询快,在中间位置进行添加和删除的时候较慢,涉及到数据的整体移动

链表:查询慢,添加和删除较快,不需要进行数据的整体移动。

2.相关操作

2.1.添加操作

  • 将要插入节点的值和当前节点的值进行比较
  • 如果要插入节点的值小于当前节点的值,则继续判断当前节点的左子节点是否为null,如果为null,则将要插入节点插入到当前节点的左子节点位置,如果不为null,则递归的往左子节点进行插入
  • 如果要插入节点的值大于等于当前节点的值,则继续判断当前节点的右子节点是否为null,如果为null,则将插入节点插入到当前节点的右子节点位置,如果不为null,则递归的往右子节点进行插入
    /**
     * 以递归的方式添加节点
     * @param node
     */
    public void add(Node node){
        if (node == null) {
            return;
        }
//        如果要插入节点的值 < 当前节点的值
        if (node.value < this.value) {
//            判断当前节点的左子节点是否为null
            if (this.left == null) {
//                如果当前节点的左子节点为null,让要插入的节点插入到当前节点的左子节点
                this.left = node;
            } else {
//                如果当前节点的左子节点不为null,递归的往左子节点进行插入
                this.left.add(node);
            }
        } else { // 如果要插入节点的值 >= 当前节点的值
//            判断当前节点的右子节点是否为null
            if (this.right == null) {
//                如果当前节点的右子节点为null,让要插入的节点插入到当前节点的右子节点
                this.right = node;
            } else {
//                如果当前节点的右子节点不为null,递归的往右子节点进行插入
                this.right.add(node);
            }
        }
    }

2.2.删除操作

二叉排序树的删除情况比较复杂,有下面三种情况需要考虑

(1)删除叶子节点,比如:2、5、9、12

(2)删除只有一棵子树的节点,比如:1

(3)删除有两棵子树的节点,比如:7、3、10

在这里插入图片描述

情况一:删除叶子节点(比如:2,5,7,9)

  • 先找到要删除的节点 targetNode
  • 找到 targetNode 的父节点 parent
  • 确定 targetNodeparent 的左子节点还是右子节点
  • 根据具体情况来进行删除
    • targetNodeparent 的左子节 parent.left = null
    • targetNodeparent 的右子节 parent.right = null

情况二:删除只有一棵子树的节点(比如:1)

  • 先找到要删除的节点 targetNode

  • 找到 targetNode 的父节点 parent

  • 确定 targetNodeparent左子节点 还是 右子节点

  • 确定 targetNode 的子节点是 左子节点 还是 右子节点

  • 如果 targetNode 的子节点是 左子节点

    • 如果 targetNodeparent左子节点parent.left = targetNode.left
    • 如果 targetNodeparent右子节点parent.right = targetNode.left
  • 如果 targetNode 的子节点是 右子节点

    • 如果 targetNodeparent左子节点parent.left = targetNode.right
    • 如果 targetNodeparent右子节点parent.right = targetNode.right

情况三:删除有两棵子树的节点(比如:7,3,10)

  • 先找到要删除的节点 targetNode
  • 找到 targetNode 的父节点 parent
  • targetNode 的右子树找到最小节点,或者从 targetNode 的左子树找到最大节点
  • 用一个临时变量,将该最小节点/最大节点的值保存起来 temp
  • 删除该最小节点/最大节点
  • targetNode.value = temp

3.代码实现

package com.zk.datastruct.binarysorttree;

/**
 * @Description:
 * @ClassName: BinarySortTreeDemo
 * @Author: ZK
 * @Date: 2021/2/6 19:21
 * @Version: 1.0
 */
public class BinarySortTreeDemo {

    public static void main(String[] args) {
//        定义数组,数组中的元素就是二叉排序树中的节点值
        int[] arr = {7,3,10,1,5,9,12,2};
//        创建一个二叉排序树
        BinarySortTree root = new BinarySortTree();
//        将数组构建成一棵二叉排序树
        for (int i = 0; i < arr.length; i++) {
            root.add(new Node(arr[i]));
        }
//        中叙遍历该二叉树
        root.midOrder();

//        删除某一个节点(情况1、2、3均可)
        root.delNode(2);
        root.delNode(5);
        root.delNode(9);
        root.delNode(12);
        root.delNode(7);
        root.delNode(3);
        root.delNode(10);
        root.delNode(1);
        System.out.println();

//        再次中叙遍历该二叉树
        root.midOrder();

    }

}

class BinarySortTree{
    private Node root;

//    添加节点
    public void add(Node node){
        if (root == null) {
            root = node;
        } else {
            root.add(node);
        }
    }
//    中叙遍历
    public void midOrder(){
        if (root == null) {
            System.out.println("当前二叉排序树为null");
            return;
        }
        root.midOrder();
    }

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

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

    /**
     * 删除 以node为跟节点的的二叉排序树的最小节点(右子树的最小值)
     * 返回删除掉的该节点的值
     * @param node
     * @return
     */
    public int delRightTreeMinNode(Node node){
//        定义目标节点
        Node target = node;
//        找到以node为根节点的子树的最小节点(右子树的最小节点)
        while(target.left != null){
            target = target.left;
        }
//        删除该最小节点
        delNode(target.value);
//        返回该最小节点的值
        return target.value;
    }

    /**
     * 删除 以node为根节点的二叉排序树的最大节点(左子树的最大值)
     * 返回删除掉的该节点的值
     * @param node
     * @return
     */
    public int delLeftTreeMaxNode(Node node){
        Node target = node;
        while(target.right != null){
            target = target.right;
        }
        delNode(target.value);
        return target.value;
    }

//    删除节点
    /**
     * 删除二叉排序树中的某一节点(包括情况1、2、3)
     * @param value 要删除的节点值
     */
    public void delNode(int value){
        if (root == null) {
            return;
        } else {
//            1.找到要删除的节点
            Node targetNode = search(value);
            if (targetNode == null) {
                return;
            }
            if (root.left == null && root.right == null) {
                root = null;
                return;
            }
//            2.找到要删除节点的父节点
            Node parent = searchParent(value);
              /** 情况1:删除的是叶子节点 */
            if (targetNode.left == null && targetNode.right == null) {
//                1-1.删除的是左子节点
                if (parent.left != null && parent.left.value == value) {
                    parent.left = null;
                }
//                1-2.删除的是右子节点
                if (parent.right != null && parent.right.value == value) {
                    parent.right = null;
                }
            } else if (targetNode.left != null && targetNode.right != null) {
                  /** 情况3:删除有两棵子树的节点 */
//                找到以右节点为根节点的子树的最小值,并删除该节点
                int minValue = delRightTreeMinNode(targetNode.right);
//                将当前要删除的targetNode的值使用minValue覆盖掉,相当于删除的targetNode
                targetNode.value = minValue;
            } else {
                /** 情况2:删除只有一棵子树的节点 */
//                2-1.确定targetNode的子节点是左子节点还是右子节点
                if (targetNode.left != null) {
//                    targetNode只有左节点
//                    2-2.确定targetNode是parent的左子节点还是右子节点
                    if (parent != null) {
                        if (parent.left.value == targetNode.value) {
                            parent.left = targetNode.left;
                        } else {
                            parent.right = targetNode.left;
                        }
                    } else {
//                        考虑10->1这种情况,删除10节点,这时候10没有父节点
                        root = targetNode.left;
                    }
                } else {
//                    targetNode只有右节点
//                    2-2.确定targetNode是parent的左子节点还是右子节点
                    if (parent != null) {
                        if (parent.left.value == targetNode.value) {
                            parent.left = targetNode.right;
                        } else {
                            parent.right = targetNode.right;
                        }
                    } else {
                        root = targetNode.right;
                    }
                }
            }
        }
    }

}


class Node{
    int value;
    Node left;
    Node right;

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

    /**
     * 以递归的方式添加节点
     * @param node
     */
    public void add(Node node){
        if (node == null) {
            return;
        }
//        如果要插入节点的值 < 当前节点的值
        if (node.value < this.value) {
//            判断当前节点的左子节点是否为null
            if (this.left == null) {
//                如果当前节点的左子节点为null,让要插入的节点插入到当前节点的左子节点
                this.left = node;
            } else {
//                如果当前节点的左子节点不为null,递归的往左子节点进行插入
                this.left.add(node);
            }
        } else { // 如果要插入节点的值 >= 当前节点的值
//            判断当前节点的右子节点是否为null
            if (this.right == null) {
//                如果当前节点的右子节点为null,让要插入的节点插入到当前节点的右子节点
                this.right = node;
            } else {
//                如果当前节点的右子节点不为null,递归的往右子节点进行插入
                this.right.add(node);
            }
        }
    }

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

    /**
     * 查找某一个节点
     * @param value 要查找的目标节点的值
     * @return 目标节点/null
     */
    public Node search(int value){
//        如果查找的节点刚好和当前节点值相等
        if (value == this.value) {
            return this;
        } else if (value < this.value) {
//            如果查找的值小于当前节点的值,向左子节点递归查找
            if (this.left == null) {
                return null;
            } else {
                return this.left.search(value);
            }
        } else {
//            如果查找的值大于当前节点的值,向右子节点递归查找
            if (this.right == null) {
                return null;
            } else {
                return this.right.search(value);
            }
        }
    }

    /**
     * 查找当前节点的父节点
     * @param value 当前子节点的值
     * @return 当前子节点的父节点/null
     */
    public Node searchParent(int value){
        if ( (this.left != null && this.left.value == value) || (this.right != null && this.right.value == value) ) {
            return this;
        } else {
//            递归向左子树查找
            if (this.left != null && this.value > value) {
                return this.left.searchParent(value);
            } else if (this.right != null && this.value <= value) {
//                递归向右子树查找
                return this.right.searchParent(value);
            } else {
//                没有父节点
                return null;
            }
        }
    }

    @Override
    public String toString() {
        return "Node [value = " + this.value + "]";
    }
}

评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

难过的风景

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值