06.二分搜索树

作业:
1. 二分搜索树一些方法的非递归实现
2**. 层序遍历,打印出一个二叉树

将数据使用数结构储存后,出奇的高效

二分搜索树

平衡二叉树 AVL 红黑树

堆 并查集

线段树 Trie(字典树,前缀树)

二叉树

二叉树具有唯一根节点

class Node {
E e;
Node left;
Node right
}

二叉树每个节点最多有两个孩子
二叉树节点最多只有一个父亲
二叉树具有天然递归结构
二叉树不一定是‘满’的

二分搜索树

二分搜索树是一个二叉树
二分搜索树的每个节点的值:每个节点
* 大于其左子树的所有节点的值
* 限于其右子数所有节点的值

二分搜索树的每个子数也是一个二分搜索树

存储的元素必须有可比较性

这里的二分搜索树不包含重复的元素
如果想包含重复的元素,只需要修改定义就可以了
* 左子树小于等于节点;或者右子数大于等于节点

前序遍历:递归先访问节点,然后访问左子树,然后访问右子树

中序遍历:递归先访问左子树,然后访问节点,然后访问右子树
访问的结果是从小到大的排序结果!!!顺序的

后续遍历:递归先访问左子树,然后访问右子树,然后访问节点
应用场景:为二分搜索树释放内存空间

反序遍历:我TM自己创的!,右子,节点,左子,倒叙访问

二分搜索树基本功能实现

package bannerySearchTree;

import datastructure.array.ArrayStack;

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

public class BST> {
    /*Comparable接口代表这个类型必须具有可比较性*/

    private class Node{
        public E e;
        public Node left, right;

        public Node(E e){
            this.e = e;
            this.left = null;
            this.right = null;
        }
    }

    private Node root;
    private int size;

    public BST(){
        root = null;
        size = 0;
    }

    public int size(){
        return this.size;
    }

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

    /*
    // 添加元素,根节点特殊处理
    public void add(E e){
        if(size == 0) {
            this.root = new Node(e);
            size++;
        }
        else
            this.add(root, e);
    }

    // 向以node为根的二分搜索树中插入元素e,递归算法
    private void add(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){
            add(node.left, e);
        } else {
            add(node.right, e);
        }
    }
    */

    // 更加简洁的写法,因为null也可以看做是一个二分搜索树的节点
    public void add(E e){
        root = add(root, e);
    }
    /*将待插入的节点与当前节点比较,如果当前节点为终止条件,则直接赋值
    * 如果当前节点不为空,与当前节点比较,选择对应的子树进行递归,最终将当前节点返回并挂载
    * 到父节点对应的分支上。由于要满足将最终的插入节点挂载到父节点上,不得不对所有深度的递归
    * 都重新进行挂载*/
    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;
    }

    // 查看二分搜索树中是否包含元素e
    public boolean contains(E e){
        return contains(root, e);
    }

    // 以node为根的二分搜索树中是否包含元素e,递归算法
    private boolean contains(Node node, E e){
        if(node.e == null)
            return false;

        if(e.compareTo(node.e) == 0)
            return true;
        else if(e.compareTo(node.e) < 0)
            return contains(node.left, e);
        else
            return contains(node.right, e);
    }

    // 二分搜索树的前序遍历
    public void preOrder(){
        this.preOrder(root);
    }

    // 前序遍历,以node为根的二分搜索树,递归算法
    private void preOrder(Node node){
        if(node != null){
            System.out.print(node.e);
            System.out.print(' ');
            preOrder(node.left);
            preOrder(node.right);
        }
    }

    // 打印带深度的前序遍历
    @Override
    public String toString(){
        StringBuilder res = new StringBuilder();
        this.generateBTSString(root, 0, res);
        return res.toString();
    }

    private void generateBTSString(Node node, int depth, StringBuilder res){
        if(node != null){
            res.append(this.generateDepthGangGang(depth) + node.e + '\n');
            generateBTSString(node.left, depth + 1, res);
            generateBTSString(node.right, depth + 1, res);
        } else
            res.append(this.generateDepthGangGang(depth) + "NULL\n");
    }

    private String generateDepthGangGang(int depth){
        StringBuilder res = new StringBuilder();
        for(int i = 0; i < depth; i ++){
            res.append("- ");
        }
        return res.toString();
    }

    // 中序遍历,先遍历左子树,然后节点,然后右子树。遍历的顺序就是排序的顺序
    public void inOrder(){
        this.inOrder(root);
    }

    private void inOrder(Node node){
        if(node == null)
            return;

        inOrder(node.left);
        System.out.print(node.e);
        System.out.print(' ');
        inOrder(node.right);
    }

    // 反中序遍历,我TM自己创的!,右子,节点,左子
    public void reverseInOrder(){
        this.reverseInOrder(root);
    }

    private void reverseInOrder(Node node){
        if(node == null)
            return;

        reverseInOrder(node.right);
        System.out.print(node.e);
        System.out.print(' ');
        reverseInOrder(node.left);
    }

    // 后续遍历
    public void postOrder(){
        this.postOrder(root);
    }

    private void postOrder(Node node){
        if(node == null)
            return;

        postOrder(node.left);
        postOrder(node.right);
        System.out.print(node.e);
        System.out.print(' ');
    }


    /*二分搜索树的层序遍历
    * 利用队列,广度优先遍历!
    * 现将节点入队,
    * 出队,操作之后,将左右子节点入队
    * (每深入一层,入队的规模平均呈2^n增加)
    * 如果不明白画个图就一目了然了
    * 应用:跟快的找到问题的解答
    *       常用于算法设计中-最短路径
    *       图中的深度优先于广度优先*/
    public void levelOrder(){
        // java自带的Queue是一个接口
        // 必须指定使用队列底层使用的类型(Array or LinkedList)

        Queue q = new LinkedList<>();
        q.add(root);

        while(!q.isEmpty()){
            Node cur = q.remove();
            System.out.print(cur.e);
            System.out.print(' ');

            if(cur.left != null)
                q.add(cur.left);

            if(cur.right != null)
                q.add(cur.right);
        }

    }


    // 非递归实现前序遍历
    /*先将节点入栈,再出栈打印,在将右子树节点入栈(如果存在),再将左子树节点入栈(如果存在),再出栈*/
    public void preOrderNC(){
        ArrayStack stack = new ArrayStack<>();
        Node cur = root;
        stack.push(cur);
        while(!stack.isEmpty()){
            // 先出栈
            cur = stack.pop();
            System.out.print(cur.e);
            System.out.print(' ');

            if(cur.right != null){
                stack.push(cur.right);
            }

            if(cur.left != null){
                stack.push(cur.left);
            }
        }
    }

    // 非递归实现中序遍历
    /**
     * 使用栈
     * 1. 不断地将当前的左子树压如栈中,直到遇见null就不压入
     * 2. 从栈中取出一个节点,打印,令当前节点等于右子树节点,重复步骤1.
     * 3. 直到栈为空,当前的节点为null
     * (注意:每次操作了的节点都出栈了,不会存在重复遍历左子树的情况)*/
    public void inOrderNC(){
        Stack stack = new Stack<>();
        Node node = root;

        while(!stack.isEmpty() || node != null){

            if(node != null){
                stack.push(node);
                node = node.left;
            } else {
                node = stack.pop();
                System.out.println(node.e);
                node = node.right;
            }

        }
    }

    // 非递归实现后续遍历
    /** 递归的思想,类似于汉诺塔问题,
     * 1. 申请一个栈1,将节点压入栈1中
     * 2. 从栈1中取出一个节点放入栈2,将左孩子,右孩子依次压入栈中
     * 3. 重复2.步骤,直到栈1中为空,让后从栈2中依次取出节点并打印,就是后续遍历的顺序
     * 注意:每颗子树的头结点都是从栈1中出来,放入栈2中,然后对应的右子树节点,左子树节点从栈
     * 1出来,以中右左的方式存入的栈2,最后打印的顺序又是反着的,左右中,这种思路与解决汉诺塔递归的思想
     * 如出一辙*/

    public void postOrderNC(){
        Stack tep1 = new Stack<>();
        Stack tep2 = new Stack<>();
        Node node = root;
        tep1.push(node);

        while(!tep1.isEmpty()){
            node = tep1.pop();
            tep2.push(node);
            if(node.left != null)
                tep1.push(node.left);

            if(node.right != null)
                tep1.push(node.right);
        }

        while(!tep2.isEmpty()){
            System.out.print(tep2.pop().e);
            System.out.print(' ');
        }
    }

    /*找到数中的最小值*/
    public E minimum(){
        if(this.isEmpty())
            throw new IllegalArgumentException("Find failed, the tree is empty");

        return minimum(root).e;
    }

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

    /*找到数中的最大值*/
    public E maxmum(){
        if(this.isEmpty())
            throw new IllegalArgumentException("Find failed, the tree is empty");

        return maxmum(root).e;
    }

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

    /**删除二叉树中的最大值与最小值,
    最小值在树的最左边,有两种情况,
    没有子树,有右子树(最大值以此类推)
    * 作业:使用非递归的方式实现逻辑*/

    /* 删除最小值 */
    public E removeMin(){
        E ret = this.minimum();
        root = removeMin(root);
        return ret;
    }

    /** 每深入一次递归,就将当前节点返回给父亲。从而不用保留父父节点进行赋值这种!
    * 为了删除一个节点,代价就是每次递归,都将子树挂载给父亲*/
    private Node removeMin(Node node){
        if(node.left == null){
            size--;
//            if(node.right != null)
//                return  node.right;
//            return null;

            // 另一种方式,手动回收被删除的节点
            // 临时保存右子树
            Node rightNode = node.right;
            // 将被删除的节点与子树脱离关系,这里只考虑右子树就可以了
            node.right = null;
            // 返回原来的右子树,如果没有右子树,返回为null也是符合逻辑的
            return rightNode;
        }

        node.left =  removeMin(node.left);
        return node;
    }

    /*删除最大值*/
    public E removeMax(){
        E res = maxmum();
        root = removeMax(root);
        return res;
    }

    private Node removeMax(Node node){
        if(node.right == null) {
            size--;
//            if (node.left != null)
//                return node.left;
//            return null;
            Node leftNode = node.left;
            node.left = null;
            return leftNode;
        }

        node.right = removeMax(node.right);
        return node;
    }

    /**删除任意值
    * 一:没有子节点,将当前节点删除,返回nul
    * 二:有一个左子树或者右子树,删除该节点后将子树节点返回
    * 三:同时存在两个子树,找到右子树找离当前值最近的那个大于节点的值,
    *    在右子树的最左边,将此节点替换为当前的节点待返回,删除找到的节点*/

    public void remove(E e){
        root = remove(root, e);
    }

    private Node remove(Node node, E e){

        if(e.compareTo(node.e) < 0){
            node.left = remove(node.left, e);
            return node;
        } else if(e.compareTo(node.e) > 0) {
            node.right = remove(node.right, e);
            return node;
        } else {
            if(node.left == null){
                Node rightNode = node.right;
                node.right = null;
                size --;
                return rightNode;
            } else if(node.right == null){
                Node leftNode = node.left;
                node.left = null;
                size--;
                return leftNode;
            } else {
                /**算法 后继法:
                * 找到右子树最小值赋给临时节点
                * 将待删除节点的左子树赋给临时节点对应的子树
                * 将待删除节点的右子树删除最小值并赋值给临时节点
                * 返回临时节点
                * @@ 直接调用successor = removeMin(node.right)这种算法要考虑很多情况
                * @@ 而先找到右子树最小的,再删除该节点会自动处理那些情况*/
                Node successor = this.minimum(node.right);
                /*调用了removeMin size自行--*/
                successor.right = removeMin(node.right);
                successor.left = node.left;
                node.left = node.right = null;

                /**前驱法
                Node successor = this.maxmum(node.left);
                successor.left = this.removeMax(node.left);
                successor.right = node.right;
                node.left = node.right = null;*/
                return successor;
            }
        }
    }

    /**关于二分搜索树的更多话题+ 作业(没有@的为作业)
    * OK打印一棵树
    * OK非递归实现中序遍历
    * OK非递归实现反序遍历
    * OK某个值(可以不包含在节点中)的floor 比此值小的最大的元素
    * OK某个值(可以不包含在节点中)的ceil 比此值大的最小的那个元素
    *
    * OK维护size的二分搜索树,在节点中保存该节点包含的总结点数,包含自己,
    *   rank: 这个元素在数中排名第几?(推荐方式:)
    *   select: 排名是第几的元素是谁?(推荐方式:在节点中保存该节点包含的总结点数,包含自己)
    *
    * @OK维护depth的二分搜索树,每个节点保存自己的深度,根节点的深度为0
    * @支持重复元素的二分搜索树(推荐方式,在内部增加一个成员变量count记录个数)
    * @leetCode中关于数的习题(将所有课程学完了,没事儿的时候可以研究leetCode上面的问题)
    * */

    /**某个值(可以不包含在节点中)的floor 比此值小的最大的元素*/
    public E floor(E e){
        return floor(root, e, e);
    }

    // temp 保存遍历过的比次数小的值,初试值为自己,到终止条件可以根据此判断是否遍历过小于自己的值
    private E floor(Node node, E e, E temp){
        /**注意:
         * 当遍历当前节点的时候,如果当前节点的值大于等于自己,需要在左子树中寻找合适的节点,
         * 如果左子树存在,就继续遍历,如果不存在左子树,判断之前是否遍历过比自己小的节点,有则返回,
         * 没有没有,说明此树中不存在这样的floor,抛出异常!
         *
         * 当遍历当前节点,当前节点的值小于自己,需要在当前节点的右子树去寻找答案,同时将temp置为当前节点
         * (当前节点可能就是那个floor),如果存在右子树,继续遍历,如果不存在,则返回当前temp;
         * */

        if(e.compareTo(node.e) <= 0){
            if(node.left != null){
                return floor(node.left, e, temp);
            } else {
                if(temp.compareTo(e) < 0)
                    return temp;
                else
                    throw new IllegalArgumentException("Seek failed, no such floor!");
            }
        } else {
            temp = node.e;

            if(node.right != null){
                return floor(node.right, e, temp);
            } else {
                return temp;
            }
        }
    }

    /**某个值(可以不包含在节点中)的ceil 比此值大的最小的那个元素
     * 原理与floor相反*/
    public E ceil(E e){
        return ceil(root, e, e);
    }

    private E ceil(Node node, E e, E temp){

        if(e.compareTo(node.e) < 0){
            temp = node.e;
            if(node.left != null){
                return ceil(node.left, e, temp);
            } else {
                return temp;
            }
        } else {
            if(node.right != null){
                return ceil(node.right , e , temp);
            } else {
                if(temp.compareTo(e) > 0)
                    return temp;
                else
                    throw new IllegalArgumentException("Seek failed, no such ceil");
            }
        }
    }
}

打印一颗二分搜索树

package bannerySearchTree;
import java.util.LinkedList;
import java.util.Queue;

/**打印一棵树,为了满足打印需求而设计的树,训练而已!提供的功能只与打印相关。
 * 假设所有节点的数都小于10,大于0,整型;
 * 如果数中的位数不确定,打印十分困难,得找到最大数的位数,
 * 然后以此宽度为基数进行设计....总之,先做最简单的!!!
 *    以所有节点的数为一个一位数,最深度n(深度从0开始)的一层之间的空隙为基准,
 *    按照每一深度最左边的空格数为2^(n-1)-1, 每层元素与元素之间的空格数为(2^(n-1)-1)*2+1,
 *    每层元素的个数为2^n(遇到特殊节点的值打印一个空白)进行循环打印*/
public class BSTPrint>{

    private class Node {
        E e;
        Node left, right;
        int deep; // 记录深度,从0开始
        boolean printAble; // 记录当前节点是否允许打印,默认允许打印

        public Node(E e){
            this.e = e;
            this.left = this.right = null;
            this.printAble = true;
        }
    }

    //
    public Node root; // 根节点
    public int deepest; // 记录最深的深度
    public int size; // 记录数据个数

    public BSTPrint(){
        this.root = null;
        this.deepest = 0;
        this.size = 0;
    }

    public void add(E e){
        root = this.add(root, -1, e);
    }

    private Node add(Node node, int deep, E e){
        if(node == null){
            Node newNode = new Node(e);
            newNode.deep = deep + 1;
            this.deepest = newNode.deep > this.deepest? newNode.deep: this.deepest;
            size ++;

            return newNode;
        }

        if(e.compareTo(node.e) < 0){
            node.left = add(node.left, node.deep, e);
            return node;
        } else {
            node.right = add(node.right, node.deep, e);
            return node;
        }
    }

    /**打印节点*/
    public void printTree(){
        /**将树补全, 在父节点操作子节点*/
        if(root != null && this.deepest != 0)
            addFullTree(root);

        /** 对树进行层序遍历,是完整二叉树*/
        Queue q = new LinkedList<>();
        q.add(root);
        int currentLayer = root.deep - 1; // 保存当前所打印的深度,初始化为一个不存在的层
        char line = '|';
        String nextLine = ""; // 保存待打印的值

        while (!q.isEmpty()){
            Node node = q.remove();

            if(node.deep != currentLayer){

                // 转换到下一行
                currentLayer = node.deep;

                // 打印上次保存的一行信息
                System.out.println(nextLine);
                nextLine = "\n";

                // 打印每一行前面的空白并将空白赋值给缓存
                String leftSpace = "";
                for(int i = 0; i < Math.pow(2, this.deepest - node.deep) - 1; i++)
                    leftSpace += ' ';
                System.out.print(leftSpace);
                nextLine += leftSpace;

                // 判断是否能够打印,打印连接线并将该值加入缓存
                if(node.printAble){
                    System.out.print(line);
                    nextLine += node.e;
                } else {
                    System.out.print(' ');
                    nextLine += ' ';
                }
            } else {
                // 打印每个元素之间的间隔空格
                String betweenSpace = "";
                for(int i = 0; i < (Math.pow(2, this.deepest - node.deep) - 1) * 2 + 1; i++)
                    betweenSpace += ' ';
                System.out.print(betweenSpace);
                nextLine += betweenSpace;

                // 判断是否能够打印,打印连接线并将该值加入缓存
                if(node.printAble){
                    System.out.print(line);
                    nextLine += node.e;
                } else {
                    System.out.print(' ');
                    nextLine += ' ';
                }
            }

            if(node.left != null)
                q.add(node.left);

            if(node.right != null)
                q.add(node.right);
        }
        System.out.println(nextLine);
    }

    private void addFullTree(Node pre){
        if(pre.deep + 1 <= this.deepest && pre.left == null){
            pre.left = new Node(pre.e);
            pre.left.deep = pre.deep + 1;
            pre.left.printAble = false;
            size ++;
        }

        if(pre.deep + 1 <= this.deepest && pre.right == null){
            pre.right = new Node(pre.e);
            pre.right.deep = pre.deep + 1;
            pre.right.printAble = false;
            size ++;
        }

        if(pre.deep < this.deepest) {
            addFullTree(pre.left);
            addFullTree(pre.right);
        }

    }

    public static void main(String[] args) {

        BSTPrint bst = new BSTPrint<>();
        int[] numbers = {8, 10, 5, 4, 12, 7, 9, 13, 6, 3, 11};
        for (int i = 0; i < numbers.length; i++) {
            bst.add(numbers[i]);
        }

        bst.printTree();
    }

}

带有size的二分搜索树,并且可以查询排行的元素与元素的排行

package bannerySearchTree;

public class BSTSize> {
    /**
    维护size的二分搜索树,在节点中保存该节点包含的总结点数,包含自己,
        rank: 这个元素在数中排名第几?(推荐方式:)
        select: 排名是第几的元素是谁?(推荐方式:在节点中保存该节点包含的总结点数,包含自己)
     只包含添加功能,目前不包含删除元素的功能(如果做删除功能,需要先做出删除最大值,最小值,然后再做
     删除任意值复用删除最小值的方法<后继>)*/

    private class Node{
        E e;
        Node left, right;
        int size;

        public Node(E e){
            this.e = e;
            this.left = this.right = null;
            this.size = 1;
        }
    }

    private Node root;
    private int totalSize;

    public BSTSize(){
        this.root = null;
        this.totalSize = 0;
    }

    /**添加一个元素,添加成功之后,将所有的父节点自增1
     * 注意:目前不支持添加已经存在的元素*/
    public void add(E e){
        root = this.add(root, e);
    }

    private Node add(Node node, E e){
        if(node == null){
            this.totalSize ++;
            return new Node(e);
        }

        if(e.compareTo(node.e) < 0){
            node.left = this.add(node.left, e);
            node.size ++;
            return node;
        } else if(e.compareTo(node.e) > 0){
            node.right = this.add(node.right, e);
            node.size ++;
            return node;
        } else {
            throw new IllegalArgumentException("Add failed, can not add same element");
        }
    }

    /**rank: 这个元素在数中排名第几
     * 算法简介:
     * 从根节点开始,与当前节点比较,如果当前节点与自己相等,返回减去右子树个数的当前size
     * 如果当前节点大于自己,遍历左子节点,返回遍历的左节点的返回值
     * 如果当前节点小于自己,遍历右子节点,返回当 前节点减去右节点的size在加上遍历的右子树的返回值*/
    public int rank(E e){
        return this.rank(root, e);
    }

    private int rank(Node node, E e){

        if(node == null){
            throw new IllegalArgumentException("Seek failed, no such element");
        } else

        if(e.compareTo(node.e) == 0){
            if(node.right == null){
                return node.size;
            } else {
                return node.size - node.right.size;
            }
        } else if(e.compareTo(node.e) < 0){
            return this.rank(node.left, e);
        } else {
            if(node.right != null)
                return node.size - node.right.size + this.rank(node.right, e);
            else
                throw new IllegalArgumentException("Seek failed, no such element");
        }
    }

    /**select: 排名是第几的元素是谁?*/
    public E select(int n){
        if(n < 1)
            throw new IllegalArgumentException("Fained failed, order is too small!");

        if(n > this.totalSize)
            throw new IllegalArgumentException("Fained failed, order is too large!");

        return select(root, 0, n);
    }

    /**node为当前节点, position遍历过的节点的位置,初始化为0,n为需要寻找的位置
     * 调用递归之前已经处理了异常值,在这里面就可以不用处理了!*/
    private E select(Node node, int position, int n){
        /**计算当前节点所处的位置*/
        int tempPosition = position;
        if(node.right != null)
            tempPosition = position + node.size - node.right.size;
        else
            tempPosition = position + node.size;

        /**判断当前的位置并分配操作!*/
        if(tempPosition == n)
            return node.e;
        else if(tempPosition < n)
            return select(node.right, tempPosition, n);
        else
            return select(node.left, position, n);
    }

    public static void main(String[] args){
        BSTSize bst = new BSTSize<>();
        int[] numbers = {8, 10, 5, 4, 12, 7, 9, 13, 6, 3, 11};
        for(int i = 0; i < numbers.length; i ++){
            bst.add(numbers[i]);
        }

        System.out.println(bst.rank(7));
        System.out.println(bst.select(4));
    }
}

你可能感兴趣的:(06.二分搜索树)