十-6, 一文搞定二叉排序树的创建, 遍历, 查找和删除(Java实现)

10.1 二叉排序树的概述

[二叉排序树]

二叉排序树(Binary Search Tree),或者二叉搜索树

指的是一棵空树或者具有下列性质的二叉树:

    1. 左子树< 根节点 < 右子树
    1. 任意节点的左右子树也分别为二叉排序树

10.2 二叉排序树创建和遍历的实现

[实现思路]

在二叉树的无论是遍历还是查找删除时, 我们通常是在结点类中实际编写对结点各种操作的代码, 而在二叉树类中仅仅是使用根节点来调用这个方法.

  • 二叉排序树也不例外, 我们在结点类中编写对二叉排序树的创建和遍历
    • 在往二叉排序中添加节点node时, 我们首先判断根节点是否为空, 如果为空, 直接root = node;
    • 根节点不为空, 那么我们的判断分为了小于和大于两种,
    • 如果node.value <= this.value, 我们往左子树递归遍历, (为什么偏偏是递归,请看下面结点类中代码的注释.), 直到我们找到了某一个合适叶子结点.
    • 如果node.value > this.value, 我们往右子树递归遍历,直到找到了某一个合适的叶子结点.

[案例描述]

给定如下数组:

arr = {7, 3, 10, 12, 5, 1, 9}

要求如下:

  1. 创建二叉排序树
  2. 中序遍历二叉排序树

[完整代码示例]

结点类

package DataStrcture.binarysearchtree;

public class TreeNode {
    public TreeNode leftNode;
    public TreeNode rightNode;
    public int value;

    //设置左右节点
    public void setLeftNode(TreeNode node) {
        this.leftNode = node;
    }

    public void setRightNode(TreeNode node) {
        this.rightNode = node;
    }

    //构造器, toString()
    public TreeNode(int val) {
        this.value = val;
    }

    public String toString() {
        return "Node{Value= " + value + "}";
    }

    //添加结点到二叉树
    public void addNode(TreeNode node) {
        //左子树
        if (node.value <= this.value) {
            //往左子树递归查找
                //左子树为空, 找到了
            if (this.leftNode == null) {
                this.leftNode = node;
                //未找到
            } else {
                //如果还未到树的最底部,则递归继续查找合适的添加结点位置

                // Q: ?为什么此处用递归呢?
                // 因为待添加结点有可能在左子树下的左左子树, 也有可能在左子树下的左右子树噢,
                // 这个方向的判断不一定是准确的合适的, 所以我们需要再一次调用自身方法进行深入的判断
                this.leftNode.addNode(node);
            }
            //右子树
        } else if (node.value > this.value) {
            //往右子树递归查找
                //右子树为空,找到了
            if (this.rightNode == null) {
                this.rightNode = node;
                //未找到
            } else {
                this.rightNode.addNode(node);
            }
        }
    }

    //中序遍历二叉树: 这里要记住中序遍历二叉树得到的序列是顺序的序列噢
    public void midOrder() {
        if (this.leftNode != null) this.leftNode.midOrder();

        System.out.println(this);

        if(this.rightNode != null) this.rightNode.midOrder();
    }
}

二叉排序树类

package DataStrcture.binarysearchtree;

public class BasicSearchTree {
    //指定根节点. 初始化根节点
    public TreeNode root;
    public BasicSearchTree(TreeNode node){
        this.root = node;
    }
    /**
     * 实现二叉排序树: 左结点 < 根节点 < 右子树
     */
    public static void main(String[] args) {
        int arr[] = {7, 3, 10, 12, 5, 1, 9};
        //初始化BST的对象
        BasicSearchTree bst = new BasicSearchTree(null);
        //用数组的数字作为结点的值初始化结点
        // 初始化同时就添加到二叉排序树中
        for(int x : arr){
            bst.addNode(new TreeNode(x));
        }
        bst.midOrder();
    }

    /**
     * 构建二叉排序树
     **/
    public void addNode(TreeNode node){
        if(root == null){
            root = node;
        }else{
            root.addNode(node);
        }
    }
    //中序二叉树
    public void midOrder(){
        if(root == null){
            System.out.println("二叉树为空, 遍历失败");
        }else{
            root.midOrder();
        }
    }
}

10.3 二叉排序树查找和删除指定节点的实现

[案例描述]

删除二叉排序树中的指定节点, 要求删除结点后仍为一个二叉排序树

[思路分析]

当我们删除二叉排序中的结点时, 根据待删除结点在树中所处的位置不同, 我们分为三种情况去处理和实现:

  1. 待删除结点为叶子结点;

当待删除结点是叶子结点是时, 我们只需要将待删除结点的父节点的孩子指针置空即可, 这个时候就涉及到了待删除结点父节点的查找以及对待删除结点是父节点的左孩子还是右孩子的判断

  1. 待删除结点为只有一棵子树的结点;

  • 当待删除结点下面有一棵左子树或者右子树时, 我们仅仅需要将待删除结点的左孩子或右孩子结点重新赋值,
  • 赋给待删除结点的父节点作为父节点的新的左子树或右子树;
  • 在具体的实现中, 我们需要对待删除结点包含的是左子树还是右子树进行判断, 当然同时也需要判断待删除结点是父节点的左孩子还是右孩子,

这一系列的逻辑过程稍微有些复杂, 在实现代码这一小节,我们会给出实现这一过程的逻辑导图;

  1. 待删除结点为有两棵子树的结点;

当待删除结点既有左孩子又有右孩子, 我们需要从待删除结点的右子树中找出一个最小值结点, 去替换掉待删除结点

哪里是最小值结点呢? 待删除结点右子树中最左边的结点;
如何替换掉待删除结点? 找到这个最小值结点后, 把他的父节点的左孩子指针置空, 然后最小值结点的值赋给待删除结点

[代码实现]

删除结点实现的核心逻辑图:
在这里插入图片描述

  • 实现代码如下:

对待删除结点和父节点的查找方法

//1. 待删除结点的查找
    public TreeNodeDel searchTargetNode(int key) {
        if (this.value == key) return this;
        //左子树递归
        if (this.value > key) {
            if (this.leftNode != null)
                return this.leftNode.searchTargetNode(key);
            //右子树递归
        } else {
            if (this.rightNode != null)
                return this.rightNode.searchTargetNode(key);
        }
        return null;
    }

    //2. 待删除结点父节点的查找
    public TreeNodeDel parentNode(int key) {
        //递归查找的结束语句(递归出口)
        if (this.leftNode != null && this.leftNode.value == key
                || this.rightNode != null && this.rightNode.value == key) {
            return this;
        } else {
            //如果查找的值小于当前结点的值,并且当前结点的左子树不为空
            if (key < this.value && this.leftNode != null) {
                return this.leftNode.parentNode(key);//往左子树递归
                // 如果查找的值大于或等于当前结点的值,并且当前结点的右子树不为空
            } else if (key >= this.value && this.rightNode != null) {
                return this.rightNode.parentNode(key); //往右子树递归
            } else {
                return null;
            }

        }
    }

在结点类中具体实现的结点删除方法

   //删除结点
    /**
     * @param targetNode 待删除结点
     * @param key        结点的值
     */
    public void deleteNode(TreeNodeDel targetNode, int key) {
        TreeNodeDel parent = this.parentNode(key);
        /待删除结点可能的三种情况:
        // 1. 待删除结点为叶子结点
        //判断条件, 待删除结点的左子树或右子树为空
        if (targetNode.leftNode == null && targetNode.rightNode == null) {
            if (parent.leftNode == targetNode) {
                parent.leftNode = null;
            } else {
                parent.rightNode = null;
            }///2. 待删除结点有两棵子树
            // 判断条件, 待删除结点的左子树和右子树均不为空
        } else if (targetNode.leftNode != null && targetNode.rightNode != null) {
            //待删除结点的右子树中向左不断的遍历直到这棵右子树的最左端(这个节点为右子树的最小值)\
            TreeNodeDel minValParent = targetNode.rightNode;
            if(minValParent.leftNode == null){
                targetNode.value = minValParent.value;
                targetNode.rightNode = null;
            }else{
                //此处特别需要注意的是: 我们需要找到这个最左端结点的父节点,
                // 因为在把这个节点赋给待删除结点后, 这个节点就会被置空,
                // 用谁置空? 当然是这个节点的父节点啦
                while (minValParent.leftNode.leftNode != null) {
                    minValParent = minValParent.leftNode;
                }
                targetNode.value = minValParent.leftNode.value;
                minValParent.leftNode = null;
            }
        //3. 待删除结点只有一棵子树
        }else{
            if(parent.leftNode == targetNode){
                if(targetNode.leftNode != null){
                    parent.leftNode = targetNode.leftNode;
                }else{
                    parent.leftNode = targetNode.rightNode;
                }
            }else{
                if(targetNode.leftNode != null){
                    parent.rightNode = targetNode.leftNode;
                }
                else{
                    parent.rightNode = targetNode.rightNode;
                }
            }
        }
    }

在二叉排序树中使用删除的方法

 ///删除结点
    public void deleteNode(int value){
        TreeNodeDel targetNode = root.searchTargetNode(value);
        if(root == null || targetNode == null){
            System.out.println(" 二叉树为空, 或者未找到待删除结点, 删除失败");
        }else{
            root.deleteNode(targetNode,value);
        }
    }

完整代码示例-Java实现二叉排序树的查找和删除

posted @ 2022-05-26 20:31  青松城  阅读(163)  评论(0编辑  收藏  举报