每日一省之——使用递归法实现二叉查找树(BST),API齐全

本来国庆该出去转转的,可是在杭州朋友不多。现在的公司规模又太大,没上一家公司凝聚力强,想找同事出来聚聚,后来发现有心无力。所以国庆还是敲代码吧。不过此刻刚写完二叉树,发现已到凌晨2点,注释也不全。改天再完善吧。之所以有勇气贴出来,是觉得对得起自己的这番苦心。并且,实现二叉搜索树一般需要实现的API我也尽可能补全了。有几个重要方法的注释其实我写的也蛮详细的,估计看方法注释也就能懂了,用不着我过多赘述。本篇虽然名为递归法实现BST,实际上非递归法的前序,中序,后序遍历也都收入其中了。若本实现有什么不对的地方,欢迎大家批评指正。


import java.util.LinkedList;
import java.util.NoSuchElementException;
import java.util.Queue;
import java.util.Stack;



public class BST<K extends Comparable<K>, V> {

    private Node root;

    /**
     * 该私有内部类用于构造二叉查找树的节点
     * 
     * @author lhever
     */
    private class Node {
        private K key;
        private V value;
        private Node left;
        private Node right;
        private int N;

        public Node(K key, V value, int n) {
            this.key = key;
            this.value = value;
            this.N = n;
        }

        /**
         * eclipse自动生成
         */
        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + getOuterType().hashCode();
            result = prime * result + N;
            result = prime * result + ((key == null) ? 0 : key.hashCode());
            result = prime * result + ((left == null) ? 0 : left.hashCode());
            result = prime * result + ((right == null) ? 0 : right.hashCode());
            result = prime * result + ((value == null) ? 0 : value.hashCode());
            return result;
        }

        /**
         * eclipse自动生成,在这里的二叉树实现下,用于比较两个节点是否相等是完全可以的,why?大家自己思考咯
         */
        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            Node other = (Node) obj;
            if (!getOuterType().equals(other.getOuterType()))
                return false;
            if (N != other.N)
                return false;
            if (key == null) {
                if (other.key != null)
                    return false;
            } else if (!key.equals(other.key))
                return false;
            if (left == null) {
                if (other.left != null)
                    return false;
            } else if (!left.equals(other.left))
                return false;
            if (right == null) {
                if (other.right != null)
                    return false;
            } else if (!right.equals(other.right))
                return false;
            if (value == null) {
                if (other.value != null)
                    return false;
            } else if (!value.equals(other.value))
                return false;
            return true;
        }

        private BST getOuterType() {
            return BST.this;
        }

    }

    public BST() {
    }

    private int size(Node node) {
        if (node == null) {
            return 0;
        } else {
            return node.N;
        }

    }

    public boolean isEmpty() {
        return size() == 0;
    }

    public int size() {
        return size(root);
    }

    /**
     * 查找方法, 返回与置顶键关联的值
     * 
     * @param key
     * @return
     */
    public V get(K key) {
        return get(root, key);
    }

    private V get(Node node, K key) {
        if (node == null) {
            return null;
        }
        int cmp = key.compareTo(node.key);
        if (cmp < 0) {
            return get(node.left, key);
        } else if (cmp > 0) {
            return get(node.right, key);
        } else {
            return node.value;
        }

    }

    /**
     * 判断是否包含key指定的键
     * @param key
     * @return
     */
    public boolean contains(K key) {
        if (key == null) {
            throw new NullPointerException("参数不能为null");
        }
        return get(key) != null;
    }

    /**
     * 插入键值对
     * @param key
     * @param value
     */
    public void put(K key, V value) {
        if (key == null) {
            throw new NullPointerException("参数不能为null");
        }
        if (value == null) {
            delete(key);
            return;
        }

        root = put(root, key, value);
        assert check();

    }

    /**
     * 将指定的键值对插入二叉树中。
     * 
     * @param node
     * @param key
     * @param value
     * @return
     */
    private Node put(Node node, K key, V value) {
        if (node == null) {
            return new Node(key, value, 1);
        }
        int cmp = key.compareTo(node.key);
        if (cmp < 0) {
            node.left = put(node.left, key, value);
        } else if (cmp > 0) {
            node.right = put(node.right, key, value);
        } else {
            node.value = value;
        }
        node.N = size(node.left) + 1 + size(node.right);
        return node;
    }

    /**
     * 查找最小的键
     * @return
     */
    public K min() {
        if (isEmpty()) {
            throw new NoSuchElementException("二叉搜索树为空");
        }
        return min(root).key;

    }

    private Node min(Node node) {
        if (node.left == null) {
            return node;
        } else {
            return min(node.left);
        }
    }

    /**
     * 查找最大的键
     * @return
     */
    public K max() {
        if (isEmpty()) {
            throw new NoSuchElementException("二叉搜索树为空");
        }
        return max(root).key;
    }

    private Node max(Node node) {
        if (node.right == null) {
            return node;
        } else {
            return max(node.right);
        }
    }

    public void deleteMin() {
        if (isEmpty()) {
            throw new NoSuchElementException("二叉搜索树为空, 删除失败");
        }
        root = deleteMin(root);
        assert check();
    }

    /**
     * 删除以参数node为根节点的二叉搜索树的最小节点,并返回删除最小节点后的二叉搜索树。
     * 
     * @param node
     * @return
     */
    private Node deleteMin(Node node) {
        /*
         * 左节点为空,则树中最小节点就是根节点,删除后的二叉树由根节点的右子树构成
         */
        if (node.left == null) {
            return node.right;
        }
        node.left = deleteMin(node.left);
        node.N = size(node.left) + 1 + size(node.right);
        return node;
    }

    public void deleteMax() {
        if (isEmpty()) {
            throw new NoSuchElementException("二叉搜索树为空, 删除失败");
        }
        root = deleteMax(root);
        assert check();
    }

    /**
     * 删除以参数node为根节点的二叉搜索树的最大节点,并返回删除最大节点后的二叉搜索树。
     * 
     * @param node
     * @return
     */
    private Node deleteMax(Node node) {
        /*
         * 又节点为空,则树中最大节点就是根节点,删除后的二叉树由根节点的左子树构成
         */
        if (node.right == null) {
            return node.left;
        }
        node.right = deleteMax(node.right);
        node.N = size(node.left) + 1 + size(node.right);
        return node;
    }

    public void delete(K key) {
        if (key == null) {
            throw new NullPointerException("参数不能为null");
        }

        root = delete(root, key);
        assert check();
    }

    private Node delete(Node node, K key) {
        if (node == null) {
            return null;
        }
        int cmp = key.compareTo(node.key);
        if (cmp < 0) {
            node.left = delete(node.left, key);
        } else if (cmp > 0) {
            node.right = delete(node.right, key);
        } else {

            if (node.left == null) {
                return node.right;
            }
            ;
            if (node.right == null) {
                return node.left;
            }
            Node temp = node;
            node = min(temp.right);
            node.right = deleteMin(temp.right);
            node.left = temp.left;
        }

        node.N = size(node.left) + 1 + size(node.right);
        return node;

    }

    /**
     * 向下取整,这个方法的执行过程可以这样来理解(具体实现不是这样的,此处帮助理解):二叉搜索树中的键值对是键有序的, 该方法首先将树中的所有节点
     * 按照键的大小排序,排好序之后,假设参数指定的key代表一个节点(该节点在树中可能存在也可能不存在),所以我们也按排序规则将参数key指定的键插入
     * 之前已经排号序的序列中, 如果key的确是树中的某个节点,则这个虚拟的key与真实的某个节点重合。如果key不存在于树中,则这个虚拟的key会按大小
     * 处于排好序的序列中的 某个位置。该方法返回的是小于或者等于参数指定的key,并且真实存在于树中的那个key。所以,该方法的名称与大多数编程语言中
     * 提供的整数向下取整的方法语义有诸多相似之处
     * @param key
     * @return
     */
    public K floor(K key) {
        if (key == null) {
            throw new NullPointerException("参数不能为null");
        }
        if (isEmpty()) {
            throw new NoSuchElementException("二叉搜索树为空");
        }
        Node x = floor(root, key);
        if (x == null) {
            return null;
        } else {
            return x.key;
        }
    }

    private Node floor(Node node, K key) {
        if (node == null)
            return null;
        int cmp = key.compareTo(node.key);
        if (cmp == 0) {
            return node;
        }
        if (cmp < 0) {
            return floor(node.left, key);
        }
        Node temp = floor(node.right, key);
        if (temp != null) {
            return temp;
        } else {
            return node;
        }
    }

    /**
     * 向上取整。参照floor()方法的注释来理解
     * @param key
     * @return
     */
    public K ceiling(K key) {
        if (key == null) {
            throw new NullPointerException("参数不能为null");
        }
        if (isEmpty()) {
            throw new NoSuchElementException("二叉搜索树为空");
        }
        Node node = ceiling(root, key);
        if (node == null) {
            return null;
        } else {
            return node.key;
        }
    }

    private Node ceiling(Node node, K key) {
        if (node == null) {
            return null;
        }
        int cmp = key.compareTo(node.key);
        if (cmp == 0) {
            return node;
        }
        if (cmp < 0) {
            Node temp = ceiling(node.left, key);
            if (temp != null) {
                return temp;
            } else {
                return node;
            }
        }
        return ceiling(node.right, key);
    }

    /*
     * 二叉搜索树中的键值对是键有序的, 该方法返回将树中的所有节点按键的大小排序后, 键的大小排在第k位的节点. 
     * 并且: 0 <= k < size() -1
     */
    public K select(int k) {
        if (k < 0 || k >= size()) {
            throw new IllegalArgumentException();
        }
        Node x = select(root, k);
        return x.key;
    }

    /**
     * 二叉搜索树中的键值对是键有序的, 该方法返回将树中的所有节点按键的大小排序后, 键的大小排在第k位的节点.
     * 
     * @param x
     * @param k
     * @return
     */
    private Node select(Node node, int k) {
        if (node == null) {
            return null;
        }
        int t = size(node.left);
        /*
         * 如果当前节点的左子树的大小比k大, 则键的排名为k的节点一定在左子树中
         */
        if (t > k) {
            return select(node.left, k);
        }
        /*
         * 如果当前节点的左子树大小比k小,则排名为k的节点一定在右子树中,并且在右子树中的排名为k-t-1。
         * 因为k指代的是在整棵树中的排名,排名从0开始。左子树中键最大的节点排名为t-1(左子树的大小为t),当前节点排为t,
         * 所以左子树加当前节点的节点总数是t+1, 排名为k的节点一定是在右子树中排名为k-(t+1)(也即 k-t-1)的节点,该节点其
         * 实就是在右子树中大小为k-t-1的子树的根节点
         */
        else if (t < k) {
            return select(node.right, k - t - 1);
        }
        /*
         * 因为k指代的是在整棵树中的排名,排名从0开始。左子树中键最大的节点排名为t-1(左子树的大小为t),
         * 当前节点比左子树中的任何节点都大,所以恰好排名为t。所以此处返回当前节点
         */
        else {
            return node;
        }
    }

    public int rank(K key) {
        if (key == null) {
            throw new NullPointerException("参数不能为null");
        }
        return rank(key, root);
    }

    /**
     * 二叉搜索树中的键值对是键有序的, 该方法首先将树中的所有节点按键的大小排序, 然后返回参数指定的键在排好序的节点序列中的序号。
     * 若键不存在于树种,一律返回0
     * 
     * @param key
     * @param node
     * @return
     */
    private int rank(K key, Node node) {
        if (node == null) {
            return 0;
        }
        int cmp = key.compareTo(node.key);
        if (cmp < 0) {
            return rank(key, node.left);
        } else if (cmp > 0) {
            return 1 + size(node.left) + rank(key, node.right);
        } else {
            return size(node.left);
        }
    }

    public Iterable<K> keys() {
        return keys(min(), max());
    }

    /**
     * 返回键的大小在low和high两个参数指定的范围内的键
     * @param low
     * @param high
     * @return
     */
    public Iterable<K> keys(K low, K high) {
        if (low == null) {
            throw new NullPointerException("low为空null");
        }
        if (high == null) {
            throw new NullPointerException("high为null");
        }

        Queue<K> queue = new LinkedList<K>();
        keys(root, queue, low, high);
        return queue;
    }

    private void keys(Node node, Queue<K> queue, K low, K high) {
        if (node == null) {
            return;
        }
        int cmplo = low.compareTo(node.key);
        int cmphi = high.compareTo(node.key);
        if (cmplo < 0) {
            keys(node.left, queue, low, high);
        }
        if (cmplo <= 0 && cmphi >= 0) {
            queue.add(node.key);
        }
        if (cmphi > 0) {
            keys(node.right, queue, low, high);
        }
    }

    /**
     * 返回大小在low和high两个参数指定的范围内的键的总数
     * @param low
     * @param high
     * @return
     */
    public int size(K low, K high) {
        if (low == null) {
            throw new NullPointerException("low为null");
        }
        if (high == null) {
            throw new NullPointerException("high为null");
        }

        if (low.compareTo(high) > 0) {
            return 0;
        }
        if (contains(high)) {
            return rank(high) - rank(low) + 1;
        } else {
            return rank(high) - rank(low);
        }
    }

    /**
     * 返回二叉搜索树的高度
     * 
     * @return
     */
    public int height() {
        return height(root);
    }

    private int height(Node x) {
        if (x == null)
            return -1;
        return 1 + Math.max(height(x.left), height(x.right));
    }

    /**
     * 返回树中所有节点的迭代器,遍历所有节点的方式是层序遍历。
     * 
     * @return
     */
    public Iterable<K> levelOrder() {
        Queue<K> keys = new LinkedList<K>();
        Queue<Node> queue = new LinkedList<Node>();
        queue.add(root);
        while (!queue.isEmpty()) {
            Node node = queue.remove();
            if (node == null) {
                continue;
            }
            keys.add(node.key);
            queue.add(node.left);
            queue.add(node.right);
        }
        return keys;
    }

    private boolean check() {
        if (!isBST()) {
            System.out.println("经检查不符合二叉树定义");
        }
        if (!checkSizeConsistent()) {
            System.out.println("size检查异常");
        }
        if (!checkRankConsistent()) {
            System.out.println("秩检查异常");
        }
        return isBST() && checkSizeConsistent() && checkRankConsistent();
    }

    /**
     * 判断是否满足二叉查找树的定义(左子树的节点比当前节点都小, 右子树中的节点比当前节点都大)
     * 
     * @return
     */
    private boolean isBST() {
        return isBST(root, null, null);
    }

    private boolean isBST(Node node, K min, K max) {
        if (node == null) {
            return true;
        }
        // 不可能比最小节点小
        if (min != null && node.key.compareTo(min) <= 0) {
            return false;
        }
        // 不可能比最大节点大
        if (max != null && node.key.compareTo(max) >= 0) {
            return false;
        }

        /*
         * 切分后比较
         */
        return isBST(node.left, min, node.key) && isBST(node.right, node.key, max);
    }

    private boolean checkSizeConsistent() {
        return checkSizeConsistent(root);
    }

    /**
     * 判断任意节点大小是不是等于: (左节点大小 + 1 + 右节点大小)
     * 
     * @param node
     * @return
     */
    private boolean checkSizeConsistent(Node node) {
        if (node == null) {
            return true;
        }
        if (node.N != size(node.left) + size(node.right) + 1) {
            return false;
        }
        return checkSizeConsistent(node.left) && checkSizeConsistent(node.right);
    }

    /**
     * 检查各个节点的键的实际排名(秩)是否与我们通过方法计算出来的一致
     * 
     * @return
     */
    private boolean checkRankConsistent() {
        for (int i = 0; i < size(); i++)
            if (i != rank(select(i))) {
                return false;
            }
        for (K key : keys()) {
            if (key.compareTo(select(rank(key))) != 0)
                return false;
        }
        return true;
    }

    /**
     * 打印出节点的内容和值
     * 
     * @param node
     */
    public void visit(Node node) {
        StringBuffer buff = new StringBuffer();
        if (node != null) {
            buff.append(" " + node.key + "[");
            String left = node.left != null ? node.left.key + "" : "null";
            String right = node.right != null ? node.right.key + "" : "null";
            buff.append(left)
                .append(" : ")
                .append(right)
                .append("] ");
        }
        System.out.print(buff.toString());
    }

    /** 递归实现前序遍历 */
    private void recursivePreorder(Node node) {
        if (node != null) {
            visit(node);
            recursivePreorder(node.left);
            recursivePreorder(node.right);
        }
    }

    public void recursivePreorder() {
        recursivePreorder(root);
    }

    /** 递归实现中序遍历 */
    private void recursiveInorder(Node node) {
        if (node != null) {
            recursiveInorder(node.left);
            visit(node);
            recursiveInorder(node.right);
        }
    }

    public void recursiveInorder() {
        recursiveInorder(root);
    }

    /** 递归实现后序遍历 */
    private void recursivePostorder(Node node) {
        if (node != null) {
            recursivePostorder(node.left);
            recursivePostorder(node.right);
            visit(node);
        }
    }

    public void recursivePostorder() {
        recursivePostorder(root);
    }

    /** 非递归实现前序遍历 */
    private void preorder(Node node) {
        Stack<Node> stack = new Stack<Node>();
        if (node != null) {
            stack.push(node);
            while (!stack.empty()) {
                node = stack.pop();
                visit(node);
                if (node.right != null) {
                    stack.push(node.right);
                }
                if (node.left != null) {
                    stack.push(node.left);
                }
            }
        }
    }

    public void preorder() {
        preorder(root);
    }

    /**
     * 非递归实现后序遍历
     * 
     * 后序遍历的非递归算法是三种顺序中最复杂的,原因在于,后序遍历是先访问左、右子树,再访问根节点,
     * 而在非递归算法中,利用栈回退到时,并不知道是从左子树回退到根节点,还是从右子树回退到根节点,
     * 如果从左子树回退到根节点,取出的根节点应该放回去,然后去访问右子树,而如果从右子树回退到根节点,此时的确轮到访问
     * 根节点了。这个过程相比前序和后序,必须得在压栈时添加信息(此处的实现利用参数q记录已经访问过的节点,如果右节点已经访问过
     * , 则可以访问栈中弹出的根节点,如果右节点还没有访问过,则再次将弹出的根节点压回栈中,转而访问右节点,待访问右节点结束再弹出根节点进行访问),
     * 以便在退栈时可以知道是从左子树返回, 还是从右子树返回进而决定下一步的操作。
     **/
    private void postorder(Node p) {
        Node q = p;
        Stack<Node> stack = new Stack<Node>();
        while (p != null) {
            // 左子树入栈
            for (; p.left != null; p = p.left) {
                stack.push(p);
            }
            // 当前节点无右子或右子已经输出
            while (p != null && (p.right == null || p.right == q)) {
                visit(p);
                q = p;// 记录上一个已输出节点
                if (stack.empty()) {
                    return;
                }
                p = stack.pop();
            }
            // 处理右子
            stack.push(p);
            p = p.right;
        }
    }

    public void postorder() {
        postorder(root);
    }

    /** 非递归实现中序遍历 */
    private void inorder(Node p) {
        Stack<Node> stack = new Stack<Node>();
        while (p != null) {
            while (p != null) {
                if (p.right != null) {
                    stack.push(p.right);// 当前节点右子入栈
                }
                stack.push(p);// 当前节点入栈
                p = p.left;
            }
            p = stack.pop();
            while (!stack.empty() && p.right == null) {
                visit(p);
                p = stack.pop();
            }
            visit(p);
            if (!stack.empty()) {
                p = stack.pop();

            } else {
                p = null;
            }
        }
    }

    public void inorder() {
        inorder(root);
    }


    public static void main(String[] args) {
        BST<String, Integer> st = new BST<String, Integer>();
        st.put("China", 1);
        st.put("America", 2);
        st.put("Japan", 100);
        st.put("Russian", 3);
        st.put("Poland", 8);
        st.put("India", 99);

        for (String s : st.levelOrder()) {
            System.out.println(s + " ---> " + st.get(s));
        }

        for (String s : st.keys()) {
            System.out.println(s + " " + st.get(s));
        }

        /**
         *                            H
         *                           /  \
         *                          /    \
         *                         D      L
         *                        /  \   / \
         *                       /    \ I  X 
         *                      B      G    
         *                     / \    / \  
         *                    A   C  F  无
         *                          / \                               
         *                         E   无
         */
        BST<String, String> st1 = new BST<String, String>();
        st1.put("H", "H0");
        st1.put("D", "D0");
        st1.put("B", "B0");
        st1.put("G", "G0");
        st1.put("A", "A0");
        st1.put("C", "C0");
        st1.put("F", "F0");
        st1.put("E", "E0");
        st1.put("L", "L0");
        st1.put("I", "I0");
        st1.put("X", "X0");

        st1.recursivePreorder();
        System.out.println();
        st1.recursiveInorder();
        System.out.println();
        st1.recursivePostorder();
        System.out.println();
        System.out.println("--------------------------------------------------------------------");
        st1.preorder();
        System.out.println();
        st1.postorder();
        System.out.println();
        st1.inorder();

    }

}




  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值