美文网首页
二叉查找树

二叉查找树

作者: 怀念小兔 | 来源:发表于2018-12-09 18:25 被阅读50次

    二叉查找树,字排序树,号搜索树,嘛,总之都是一个东西。

    定义

    二叉排序树或者是一棵空树,或者是具有下列性质的二叉树
    (1)若左子树不空,则左子树上所有结点的值均小于它的根结点的值;
    (2)若右子树不空,则右子树上所有结点的值均大于它的根结点的值;
    (3)左、右子树也分别为二叉排序树;
    (4)没有键值相等的节点。
    这么整齐的格式一看就是百度百科粘贴过来的。

    一些分析

    从名字可知,它能给元素排序,因为元素都是有序的,所以搜索特别方便,搜索效率一般情况下远胜链表和顺序表,但是还得看这个树的平衡程度,时间复杂度在O(Log2n)和O(n)之间。
    查找二叉树按中序遍历得到的一定是有序数组,可以用这一点来验证我们写的查找二叉树是否合格。
    所谓排序,必然是建立在元素可比较大小的基础上,所以这里面能放的元素,必然实现了Comparable接口。

    插入元素的实现

    如果没有根节点,那直接当作根节点,否则从根节点开始比较大小,目标节点值大于元素,则向左走,反之向右走。

    删除元素的实现

    删除元素,其实无非就是断开这个节点和外界的关系,再给他找个替代品补位,这个替代品在大小关系上需要满足查找二叉树的性质。根据查找二叉树的性质,对于任意节点p都有p左侧的元素全部小于p右侧的元素,所以如果要删除节点p,有两种选择:
    1.用其左子树中最大的元素补位。
    2.用其右子树中最小的元素补位。
    当他同时有左右子树时,无论选哪种都行,如果只有一种那就没得选了。如果没有,那就省力气了。
    什么?你问他的父节点怎么办?嗯,替代品当然直接继承了这种关系。
    但是这里有一点需要特别注意,替代品继承他的关系时可以导致某个关系的消失,这种情况只会发生在替代品跟他本来就有直接关系时,所以我们在替换的时候判断一下即可,如果出现这种情况,会导致替代品节点根这个关系的目标节点是同一个(引用相等),这一块我的具体代码里有标注。
    最后,别忘了如果要删除的元素是根节点,需要把根节点置空。

    具体实现

    这一篇直接用代码收尾了,代码经过了我的大(ji)量(ci)测试,暂时没发现什么问题。如果哪位朋友觉得有问题欢迎随时交流。

    /**
     * 二叉查找树
     * Created by sylva on 2018/12/9.
     */
    
    public class BinarySearchTree<T extends Comparable<T>> {
        public static class Node<T extends Comparable<T>>{
            private T data;
            private Node<T> parent;
            private Node<T> leftChild;
            private Node<T> rightChild;
    
            private Node(T data) {
                this.data = data;
            }
    
            public T getData() {
                return data;
            }
    
            public Node<T> getParent() {
                return parent;
            }
    
            public Node<T> getLeftChild() {
                return leftChild;
            }
    
            public Node<T> getRightChild() {
                return rightChild;
            }
        }
        private Node<T> root;
        private int size;
    
        public int size() {
            return size;
        }
    
        public Node<T> getRoot() {
            return root;
        }
    
        /**
         * 放入元素
         * @param data 元素值
         * @return 是否重复,true代表已经有这个元素
         * */
        public boolean put(T data){
            Node<T> node = new Node<>(data);
            if(root == null){
                size++;
                root = node;
                return false;
            }
            Node<T> start = root;
            Node<T> prev = null;
            while(start != null){
                prev = start;
                if(start.data.compareTo(data) == 0){
                    return true;
                }else if(start.data.compareTo(data) > 0){
                    start = start.leftChild;
                }else{
                    start = start.rightChild;
                }
            }
            size++;
            node.parent = prev;
            if(prev.data.compareTo(data) > 0){
                prev.leftChild = node;
                return false;
            }else{
                prev.rightChild = node;
                return false;
            }
        }
        /**
         * 删除数据
         * @param data 要删除的数据
         * @return true代表有这个数据而且已经删除,false代表没有这个数据
         * */
        public boolean remove(T data){
            Node<T> node = find(data);
            if(node == null)
                return false;
            //如果有右子树
            if(node.rightChild != null) {
                Node<T> right = node.rightChild;
                Node<T> minInRight = findMinNode(right);
                replaceNode(node, minInRight);
            }else if(node.leftChild != null){
                //如果只有左子树
                Node<T> left = node.leftChild;
                Node<T> maxInLeft = findMaxNode(left);
                replaceNode(node, maxInLeft);
            }else{
                //断子绝孙
                replaceNode(node, null);
            }
            size--;
            return true;
        }
        /**
         * 查找(查找二叉树)中最小的节点
         * @param root 根节点
         * */
        private Node<T> findMinNode(Node root){
            for(; root.leftChild != null; root = root.leftChild){
    
            }
            return root;
        }
        /**
         * 查找(查找二叉树)中最大的节点
         * @param root 根节点
         * */
        private Node<T> findMaxNode(Node root){
            for(; root.rightChild != null; root = root.rightChild){
            }
            return root;
        }
        /**
         * 把老节点替换成新节点(这真是一个悲伤的故事)
         * @param node 老节点
         * @param newNode 新节点
         * */
        private void replaceNode(Node node, Node newNode){
            //原节点的关系就是父节点和左右子
            Node parent = node.parent;
            Node leftChild = node.leftChild;
            Node rightChild = node.rightChild;
            //让他六亲不认
            node.parent = null;
            node.leftChild = null;
            node.rightChild = null;
            //如果他爸活着,他爸心里新节点已经代替了他的位置
            if(parent != null) {
                if (parent.data.compareTo(node.data) > 0) {
                    parent.leftChild = newNode;
                } else {
                    parent.rightChild = newNode;
                }
            }
            //如果小儿子活着,小儿子把新节点当成爸爸
            if(leftChild != null) {
                leftChild.parent = newNode;
            }
            //如果大儿子活着,大儿子也抛弃了他
            if(rightChild != null) {
                rightChild.parent = newNode;
            }
            //新节点要么是叶子,要么只有左子或右子
            Node nParent = newNode.parent;
            Node nLeft = newNode.leftChild;
            Node nRight = newNode.rightChild;
            Node toLink = nLeft == null ? nRight : nLeft;
            if(nParent.data.compareTo(newNode.data) > 0){
                nParent.leftChild = toLink;
                if(toLink != null)
                    toLink.parent = nParent;
            }else{
                nParent.rightChild = toLink;
                if(toLink != null)
                    toLink.parent = nParent;
            }
            //如果新节点活着,让新节点承认这些关系
            if(newNode != null){
                //以下三个if是用来判断上文提到的替代品节点根关系目标节点相同的情况
                //这种情况直接放弃继承这种关系
                if(newNode != parent)
                    newNode.parent = parent;
                if(newNode != leftChild)
                    newNode.leftChild = leftChild;
                if(newNode != rightChild)
                    newNode.rightChild = rightChild;
            }
            //root其实是个跟树本身无关的外部引用,前面已经把新节点的关系处理完毕
            //这里只需要把外部引用指向新节点即可
            if(node == root){
                root = newNode;
            }
        }
        /**
         * 孤立一个节点,断开它跟外界的关系
         * @param node 被孤立的节点
         * */
        private void isolateNode(Node node){
            replaceNode(node, null);
        }
        private Node<T> find(T data){
            Node<T> result = root;
            while(result != null){
                int compareResult = result.data.compareTo(data);
                if(compareResult == 0){
                    return result;
                }else if(compareResult > 0){
                    result = result.leftChild;
                }else{
                    result = result.rightChild;
                }
            }
            return result;
        }
        /**
         * 中序遍历
         * @param root 相对根节点
         * */
        public void ldrTraverse(Node root){
            if(root == null)
                return;
            ldrTraverse(root.leftChild);
            System.out.print(root.data + " ");
            ldrTraverse(root.rightChild);
        }
        @Test
        public void test(){
            BinarySearchTree<Integer> bst = new BinarySearchTree<>();
    //        int[] arr = new int[20];
            int[] arr = new int[]{1, 63, 49, 8, 58, 73, 21, 74, 70, 4, 72, 41, 71, 82, 40, 89};
            Random random = new Random();
            for (int i = 0; i < arr.length; i++) {
    //            arr[i] = random.nextInt(99);
                bst.put(arr[i]);
            }
            Arrays.sort(arr);
            println(String.format("数组长度:%s,树长度:%s", arr.length, bst.size()));
            println("数组:" + Arrays.toString(arr));
            print("树:");
            bst.ldrTraverse(bst.root);
            println("");
            for (int num : arr) {
                println("删除数字:" + num);
                bst.remove(num);
                println("删除后二叉树内容:");
                bst.ldrTraverse(bst.root);
                println(String.format("数组长度:%s,树长度:%s", arr.length, bst.size()));
                println("");
                bst.put(num);
                println("再放回去后二叉树内容:");
                bst.ldrTraverse(bst.root);
                println("");
            }
    
        }
        private void println(String str){
            System.out.println(str);
        }
        private void print(String str){
            System.out.print(str);
        }
    }
    

    相关文章

      网友评论

          本文标题:二叉查找树

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