• 31-二叉排序树


    1. 引入

    给你一个数列 {7, 3, 10, 12, 5, 1, 9},要求能够高效的完成对数据的查询和添加。

    1. 使用数组

    • 数组未排序
      • 优点:直接在数组尾部添加,速度快
      • 缺点:查找速度慢
    • 数组排序
      • 优点:可以使用二分查找,查找速度快
      • 缺点:为了保证数组有序,在添加新数据时,找到插入位置后,后面的数据需整体移动,速度慢

    2. 使用链表

    不管链表是否有序,查找速度都慢,添加数据速度比数组快,不需要数据整体移动

    3. 使用二叉排序树

    2. BST 介绍

    • 二叉排序/搜索树(Binary Sort/Search Tree,简称 BST),对于二叉排序树的任何一个非叶子结点,要求:
      • 左子结点的值比当前结点的值小
      • 右子结点的值比当前结点的值大
    • 特别说明:如果有相同的值,可以将该节点放在左子结点或右子结点
    • 比如针对前面的数据 {7, 3, 10, 12, 5, 1, 9},对应的二叉排序树为:

    3. BST 删除结点

    3.1 删除叶子结点

    1. 找到待删除的结点 targetNode
    2. 找到 targetNode 的父结点
    3. 确定 targetNode 是 parent 的左子结点,还是右子结点
    4. 根据 step3 来进行删除

    3.2 删除只有一颗子树的结点

    1. 找到待删除的结点 targetNode
    2. 找到 targetNode 的父结点 parent
    3. 确定 targetNode 是父结点的左子结点还是右子结点
    4. 确定 targetNode 的子结点是左子结点还是右子结点
    5. 分 4 种情况
      // 父之左 & 子在左
      parent.left = targetNode.left;
      // 父之左 & 子在右
      parent.left = targetNode.right;
      // 父之右 & 子在左
      parent.right = targetNode.left;
      // 父之右 & 子在右
      parent.right = targetNode.right;
      

    3.3 删除有两颗子树的结点

    1. 找到待删除的结点 targetNode
    2. 找到 targetNode 的父结点 parent
    3. 从 targetNode 的右(左)子树中找出 value 最小(大)的结点
    4. 用一个临时变量 temp 来保存该最小(大)结点的 value
    5. 删除该最小(大)结点,并让 targetNode.value = temp // 可以理解为:用 targetNode 的右(左)子树中的最小(大)结点来替代 targetNode

    4. 代码实现

    class BinarySortTree {
        Node root;
    
        public void add(Node node) {
            if (root == null) root = node;
            else root.add(node);
        }
    
        public void infixOrder() {
            if (root != null) root.infixOrder();
            else System.out.println("空树");
            System.out.println();
        }
    
        public Node searchNode(int value) {
            if (root == null) return null;
            else return root.searchNode(value);
        }
    
        public Node searchParent(int value) {
            if (root == null || value == root.value) return null;
            else return root.searchParent(value);
        }
    
        /**
         * 删除node的最小value子结点, 并返回该最小结点的value
         * @param node 二叉排序树的根结点
         * @return 以node为根结点的二叉排序树的最小值
         */
        public int delTreeMinNode(Node node) {
            Node target = node;
            // 循环的查找左子结点
            while (target.left != null) target = target.left;
            // 这时, target就指向了最小结点
            delNode(target.value);
            return target.value;
        }
    
        /**
         * 删除node的最大value子结点, 并返回该最大结点的value
         * @param node 二叉排序树的根结点
         * @return 以node为根结点的二叉排序树的最小值
         */
        public int delTreeMaxNode(Node node) {
            Node target = node;
            // 循环的查找右子结点
            while (target.right != null) target = target.right;
            // 这时, target就指向了最小结点
            delNode(target.value);
            return target.value;
        }
    
        public void delNode(int value) {
            if (root == null) return;
    
            // 1. 找到待删除结点
            Node targetNode = searchNode(value);
            // a. 待删除结点不存在
            if (targetNode == null) return;
            // b. 存在, 是根结点,且这颗树只有这 1 个结点
            if (root.left == null && root.right == null) {
                root = null;
                return;
            }
            // 2. 找待删除结点的父结点
            Node parent = searchParent(value);
            // 3. 删除
            if (targetNode.left == null && targetNode.right == null) { // 3.1 叶子结点
                // 判断 targetNode 是 parent 的左子结点还是右子结点
                if (parent.left != null && parent.left.value == value)
                    parent.left = null;
                else if (parent.right != null && parent.right.value == value)
                    parent.right = null;
            } else if (targetNode.left != null && targetNode.right != null) { // 3.2 双子结点
                // a. 删除 targetNode 的右子树中 value 最小的结点, 并将其 value 赋值给 targetNode
                targetNode.value = delTreeMinNode(targetNode.right);
                // b. 删除 targetNode 的左子树中 value 最大的结点, 并将其 value 赋值给 targetNode
                // targetNode.value = delTreeMaxNode(targetNode.left);
            } else { // 3.3 单子结点
                if (parent.left != null && parent.left.value == value) { // 父之左
                    if (targetNode.left != null) { // 子在左
                        parent.left = targetNode.left;
                    } else { // 子在右
                        parent.left = targetNode.right;
                    }
                } else { // 父之右
                    if (targetNode.left != null) { // 子在左
                        parent.right = targetNode.left;
                    } else { // 子在右
                        parent.right = targetNode.right;
                    }
                }
            }
        }
    }
    
    class Node {
        int value;
        Node left;
        Node right;
    
        public Node(int value) {
            super();
            this.value = value;
        }
    
        // 查找结点
        public Node searchNode(int value) {
            if (value == this.value) {
                return this;
            } else if(value < this.value) {
                if (this.left != null) return this.left.searchNode(value);
                else return null;
            } else {
                if (this.right != null) return this.right.searchNode(value);
                else return null;
            }
        }
    
        /**
         * 查找某个结点的父结点
         * @param value 子结点的value
         * @return 父结点
         */
        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);
                else
                    return null;
            }
        }
    
        // (递归)添加结点
        public void add(Node node) {
            if (node == null) return;
            // 判断node的value和当前子树根结点的value的大小关系
            if (node.value < this.value) {
                if (this.left != null) his.left.add(node);
                else this.left = node;
            } else {
                if (this.right != null) this.right.add(node);
                else this.right = node;
            }
        }
    
        // 中序遍历
        public void infixOrder() {
            if (this.left != null) this.left.infixOrder();
            System.out.print(this + " ");
            if (this.right != null) this.right.infixOrder();
        }
    
        @Override
        public String toString() {
            return "[value=" + value + "]";
        }
    }
    
  • 相关阅读:
    数组的扩展搜集自无忧脚本
    C#简单模拟用户登录类
    C++ builder数据库连接大全
    童话故事下载地址
    如何对GridView行自动编号?
    document.execCommand() 用法说明
    兼容IE和FF的js脚本做法(比较常用)
    人民币数字转换成大写形式
    C# webBrowser 模拟登陆填充操作等(写网页注册机之类的时候要用到)
    拖动布局之保存布局页面
  • 原文地址:https://www.cnblogs.com/liujiaqi1101/p/12327976.html
Copyright © 2020-2023  润新知