二分搜索树

作者: 将代码写成诗 | 来源:发表于2019-08-18 13:31 被阅读0次

    原创作品,转载请标明来源。

    一、 什么是二叉树?

    editByWpp.png

    二叉树每个节点最多有两个孩子
    二叉树每个节点最多有一个父亲

    性质
    • 二叉树具有天然的递归结构
    • 每个节点的左子树也是二叉树
    • 每个节点的柚子树也是二叉树
    • 二叉树不一定是“满”的

    二、什么是二分搜索树?

    editByWpp.png
    • 存储的元素必须有可比较性(比如节点存储的是学生,我们可以以年龄比较或学号比较等)

    注意:本文的二分搜索树不包含重复元素
    如果想包含重复元素的话,只需要定义:左子树小于等于节点;或者柚子树大于节点即可

    二分搜索树的增删茶

    • 新增节点

    二分搜索树添加元素的非递归写法,和链表很像
    本文在二分搜索树方面的实现,关注递归实现

    public class BST<E extends Comparable<E>> {
    
    private class Node{
       public   E e;
       public Noe left, right;
       public Node(E e) {
          this.e = e;
          left = null;
          right = null;
    }
    // 向二分搜索树中添加新的元素e -- 优化前
        public void add1(E e) {
            if (root == null) {
                root = new Node(e);
                size++;
            } else {
                add(root,e);
            }
        }
    
        // 向以node为根的二分搜索树中插入元素E,递归算法---优化前的插入元素部分
        private void add1(Node node, E e) {
            // 递归终止条件
           if (e.equals(node.e)) {
                return;
           } else if (e.compareTo(node.e) < 0 && node.left == null) {
               node.left = new Node(e);
               size++;
               return;
           } else if (e.compareTo(node.e) > 0 && node.right == null) {
               node.right = new Node(e);
               size++;
               return;
           }
           if (e.compareTo(node.e) < 0 && node.left != null) {
               add(node.left,e);
           } else{ // (e.compareTo(node.e) > 0 && node.right != null)
               add(node.right,e);
           }
        }
        // 向二分搜索树中添加新的元素e -- 优化后
        public void add(E e) {
           root = add(root,e);
        }
        // 向以node为根的二分搜索树中插入元素E,递归算法---优化后的插入元素部分
        // null 也是一个二叉树来考虑
        private Node add(Node node, E e) {
            // 递归终止条件
            if (node == null) {
                size++;
                return new Node(e);
            }
           if (e.compareTo(node.e) < 0) {
              node.left = add(node.left,e);
           } else if (e.compareTo(node.e) > 0) {
               node.right = add(node.right,e);
           }
           return node;
        }
    }
    
    • 二分搜索树的查询操作
    // 递归算法
        private boolean contains(Node node, E e) {
            if (node == null)
                return false;
            if (e.compareTo(node.e) == 0)
                return true;
            else if (e.compareTo(node.e) < 0)
                return contains(node.left,e);
            else //e.compareTo(node.e) > 0
                return contains(node.right,e);
        }
    
    • 遍历(前序、中序、后序-也叫深度优先遍历)


      editByWpp.png
      editByWpp.png
    editByWpp.png
    前序遍历
     public void preOrder() {
            System.out.println();
            preOrder(root);
        }
       private void preOrder(Node node) {
            if (node == null)
                return;
            System.out.println(node.e);
            preOrder(node.left);
            preOrder(node.right);
        }
    
        // 非递归的前序遍历
        public void preOrderNR (){
    
            Stack<Node> stack = new Stack();
            stack.push(root);
            while (!stack.isEmpty()) {
                Node cur = stack.pop();
                System.out.println(cur.e);
                if (cur.right != null) stack.push(cur.right);
                if (cur.left != null)  stack.push(cur.left);
            }
        }
    中序遍历
        /**
         * 中序遍历
          */
        public void inOrder(){
            System.out.println();
            inOrder(root);
        }
    
        private void inOrder(Node node) {
            if (node == null)
                return;
            inOrder(node.left);
            System.out.print(node.e+ "  ");
            inOrder(node.right);
        }
        /**
         * 后序遍历
         */
        public void postOrder() {
            System.out.println();
            postOrder(root);
        }
    
        private void postOrder(Node node) {
            if (node == null) return;
            postOrder(node.left);
            postOrder(node.right);
            System.out.print(node.e+"  ");
        }
    
    
    • 层序遍历(广度优先遍历)--更快的找到问题的解(常用于算法设计中-最短路径)


      editByWpp.png
       public void levelOrder (){
            LinkedListQueue <Node> queue = new LinkedListQueue();
            queue.enqueue(root);
            while (!queue.isEmpty()) {
                Node cur = queue.dequeue();
                System.out.println(cur.e);
                if (cur.left != null)   queue.enqueue(cur.left);
                if (cur.right != null)  queue.enqueue(cur.right);
    
            }
        }
    
    • 删除操作
      删除二分搜索树的某一个节点可能不是很好实现,我们可以先实现删除最小节点和最大节点。可以发现最小节点就是二分搜索树最左边的节点,最大节点是二分搜索书最右边的节点;代码如下
    // 得到最小节点值
        public E miniMum(){
            if (size == 0)
                throw new IllegalArgumentException("BST is empty");
           return miniMum(root).e;
        }
        private Node miniMum(Node node) {
            if (node.left == null)
                return node;
            return miniMum(node.left);
        }
    // 得到醉倒节点值
        public E maxMum(){
            if (size == 0)
                throw new IllegalArgumentException("BST is empty");
            return maxMum(root).e;
        }
        private Node maxMum(Node node) {
            if (node.right == null)
                return node;
            return maxMum(node.right);
        }
     public E removeMin(){
            E  ret = miniMum();
            root = removeMin(root);
            return ret;
        }
        // 删除掉以node为根的二分搜索树中的最小节点
        // 返回删除节点后新的二分搜索树的根
        private Node removeMin(Node node) {
            if (node.left == null) {
                Node rightNode = node.right;
                node.right = null;
                size--;
                return rightNode;
            }
            node.left = removeMin(node.left);
            return node;
        }
       public E removeMax(){
            E  ret = maxMum();
            root = removeMax(root);
            return ret;
        }
        // 删除掉以node为根的二分搜索树中的最大节点
        // 返回删除节点后新的二分搜索树的根
        private Node removeMax(Node node) {
            if (node.right == null) {
                Node leftNode = node.left;
                node.left = null;
                size--;
                return leftNode;
            }
            node.right = removeMax(node.right);
            return node;
        }
    

    当我们试图删除某一个节点时,如果这个节点只有左节点,没有右节点,我们可以以删除最大节点的方式去处理(即使该节点未必是最大节点),如果只有右节点没有左节点时,我们同样可以将它以删除最小节点的方式去处理,问题是如果这个要删除的节点既有左节点又有右节点时,怎么处理呢?
    我引入一句话,给出了方案

    1962年,Hibgard提出- Hibgard Deletion
    其实也很简单,就是找到待删除节点的后置最小节点替代待删节点;

    比如下图中,d节点是待删节点,根据二分搜索树的性质,d节点的右子叶都比左子叶要打,那么我们只要找到右子叶的最小值替代当前待删节点就可以了。


    editByWpp.png

    另一种方案:也可以找到待删节点的前驱最大节点替代待删节点。如上图其实也可以用53所在的节点替换d节点;

    写在后面

    好了,现在二分搜索树的分析暂时可以告一段落了,

    相关文章

      网友评论

        本文标题:二分搜索树

        本文链接:https://www.haomeiwen.com/subject/nkjrsctx.html