二分查找

  • 前提是数组必须有序,比如升序,不断与中点值比较,相等则返回中点值索引,中点值较大则往左边查找,较小则往右边查找;找不到返回-1
  • 时间复杂度:对数级别

    private static int binary(int K,int[] array,int left,int right){ //left=0;right=a.length-1
        int mid=(right+left)/2;
        //找到首元素和末尾元素都不是K的情况
        if(left>right){
            return -1;
        }
        if(array[mid]==K){
            return mid;
        }
        //找左半边
        else if(array[mid]>K){
            return binary(K,array,0,mid-1); //用return的方式调用函数也就是说函数到这一步结束,出栈后不会执行后面代码
        }
        //找右半边
        else{
            return binary(K,array,mid+1,right);
        }
    }
    
  • 代码实例:DataStructure/binarySearch/FindK

    二叉查找树

  • 二分查找基于数组实现,因此要找任意子数组的中点元素都十分简单,但是如果找到后下一步就是要插入一个元素的话,那么需要线性级别的时间。便于插入的数据结构应该是链表,而为了方便查找,我们应该让每个结点都指向下一个结点
  • 二叉查找树很好的结合了插入的灵活性和查找的高效性。
  • 在二叉查找树中,每个结点都指向它的左右结点,左结点比父结点小,右结点比父结点大。
  • 构建二叉查找树用的即是插入方法,新元素进来时发现树为空,它即成为根结点,如果比根结点小,它就去到左子树跟那些结点比较,否则去到右子树。根结点的左儿子的右儿子不会比根结点大,因为如果比它大的话插入的时候就会被分到右子树去了,不会来左子树这边,所以左子树全部比父结点小,右子树全部比父结点大。
  • 所以如果第一个进来的元素太大或者太小,构成的树都会失去平衡。
  • 最小的元素永远在最最左边,最大的则在最最右边。
  • 将树的所有结点投影到一条直线上,可以得到一条升序的排列。

    public class BST<Key extends Comparable,Value> {
        //根结点
        private Node root;
    
        private class Node{
            //键
            private Key key;
            //值
            private Value value;
            //左右儿子
            private Node left,right;
            //子树的结点数
            private int size;
    
            //构造器:生成一个新的结点
            public Node(Key key,Value value,int size){
                this.key=key;
                this.value=value;
                //此结点所在子树的结点数目
                this.size=size;
            }
        }
    
        public BST() {
        }
    
        public boolean isEmpty(){
            return size()==0;
        }
    
        public int size(){
            return size(root);
        }
    
        /**
         * @Author haien
         * @Description 以x为根结点的子树所含结点数目
         * @Date 2018/11/6
         * @Param [x]
         * @return int
         **/
        public int size(Node x){
            if(x==null) return 0;
            return x.size;
        }
    
        public boolean contains(Key key){
            if(key==null) throw new IllegalArgumentException("Argument to contains() is null!");
            return get(key)!=null;
        }
    
        public Value get(Key key){
            return get(root,key);
        }
    
        /**
         * @Author haien
         * @Description 以某个结点为根结点开始查找某个元素
         * @Date 2018/11/6
         * @Param [node, key]
         * @return Value
         **/
        private Value get(Node x,Key key){
            if(key==null) throw new IllegalArgumentException("Calls get() with a null key!");
            if(x==null) return null;
            int cmp=key.compareTo(x.key);
            if(cmp==0) return x.value;
            else if(cmp<0) return get(x.left,key);
            else return get(x.right,key);
        }
    
        public void put(Key key,Value value){
            if(key==null) throw new IllegalArgumentException("Calls put() with a null key!");
            //若值为空则删去此结点
            if(value==null){
                delete(key);
                return;
            }
            root=put(root,key,value);
        }
    
        private Node put(Node x, Key key,Value value){
            //树为空,将结点设置为根结点
            if(x==null) return new Node(key,value,1);
            int cmp=key.compareTo(x.key);
            if(cmp<0) x.left=put(x.left,key,value);
            else if(cmp>0) x.right=put(x.right,key,value);
            //若key已存在则更新其值
            else x.value=value;
            x.size=size(x.right)+size(x.left)+1;
            return x;
        }
    
        public void deleteMin(){
            if(isEmpty()) throw new NoSuchElementException("Symbol table underflow!");
            deleteMin(root);
        }
    
        /**
         * @Author haien
         * @Description 以最小结点的右结点覆盖之(没有右结点的话就是null了)
         * @Date 2018/11/6
         * @Param [x]
         * @return com.haien.search.BST<Key,Value>.Node
         **/
        private Node deleteMin(Node x){
            //最小结点一定在最左边;若左边没有元素,说明已经找到了最小结点
            if(x.left==null) return x.right;
            //当前结点的左结点的左结点为空,则当前结点的左结点为最小结点,返回当前结点的左结点的右结点,覆盖当前结点的左结点
            x.left=deleteMin(x.left);
            x.size=size(x.left)+size(x.right)+1;
            //最终返回一开始传进来的赋给根结点,因为如果根结点就是最小结点,那么根结点就要改成原根结点的右结点
            return x;
        }
    
        public void deleteMax(){
            if(isEmpty()) throw new NoSuchElementException("Symbol table underflow!");
            root=deleteMax(root);
        }
    
        private Node deleteMax(Node x){
            if(x.right==null) return x.left;
            x.right=deleteMax(x.right);
            x.size=size(x.left)+size(x.right)+1;
            return x;
        }
    
        public void delete(Key key){
            if(key==null) throw new IllegalArgumentException("Calls delete() with a null key!");
            root=delete(root,key);
        }
    
        private Node delete(Node x,Key key){
            if(x==null) return null;
            int cmp=key.compareTo(x.key);
            if(cmp<0) x.left=delete(x.left,key);
            else if(cmp>0) x.right=delete(x.right,key);
            /**
             * 找到目标结点,如果它只有一边的子树则直接把子树的根结点赋给要目标结点
             * 如果它两边都有,由于覆盖上来的结点必须大于左子树,小于右子树,
             * 直接把左子树的根结点提上来是符合,但是它的左右子树也已接好了,那目标结点原来的右子树该接到它哪里 ?
             * 所以我们要选一个叶结点放上来,那当然就是右子树中最小的一个啦(左子树的最大结点应该也可以)
             **/
            else{
                if(x.right==null) return x.left;
                if(x.left==null) return x.right;
                Node t=x;
                //把右子树中最小的结点放到x处
                x=min(t.right);
                //删除右子树中最小结点,并把右子树接到x上
                x.right=deleteMin(t.right);
                //左子树还是原来的左子树
                x.left=t.left;
            }
            x.size=size(x.left)+size(x.right)+1;
            return x;
        }
    
        public Key min(){
            if (isEmpty()) throw new NoSuchElementException("Call min() with empty symbol table!");
            return min(root).key;
        }
    
        private Node min(Node x){
            if(x.left==null) return x;
            else return min(x.left);
        }
    
        /**
         * @Author haien
         * @Description 小于等于key的最大结点
         * @Date 2018/11/6
         * @Param [key]
         * @return Key
         **/
        public Key floor(Key key){
            if(key==null) throw new IllegalArgumentException("Argument to floor() is null!");
            if(isEmpty()) throw new NoSuchElementException("Calls floor() with empty symbol table!");
            Node x=floor(root,key);
            if(x==null) return null;
            return x.key;
        }
    
        private Node floor(Node x,Key key){
            if(x==null) return null;
            int cmp=key.compareTo(key);
            if(cmp<0) return floor(x.left, key); //父结点比key大,往左边找
            Node t = floor(x.right,key);
            if(t!=null) return t;
            else return x;
        }
    
        /**
         * @Author haien
         * @Description 查找排名为第k位的结点(排名从0开始)
         * @Date 2018/11/6
         * @Param [k]
         * @return Key
         **/
        public Key select(int k){
            if(k<0||k>=size()){
                throw new IllegalArgumentException("Argument to select() is invalid: "+k);
            }
            Node x=select(root,k);
            return x.key;
        }
    
        private Node select(Node x,int k){
            if(x==null) return null;
            int t=size(x.left);
            if(k<t) return select(x.left,k);
            else if(t<k) return select(x.right,k-t-1);
            else return x;
        }
    
        /**
         * @Author haien
         * @Description 计算元素的排名(从0开始)
         * @Date 2018/11/6
         * @Param [key]
         * @return int
         **/
        public int rank(Key key){
            if(key==null) throw new IllegalArgumentException("Argument to rank() is null!");
            return rank(key,root);
        }
    
        private int rank(Key key,Node x){
            if(x==null) return 0;
            int cmp=key.compareTo(x.key);
            if(cmp<0) return rank(key,x.left);
            else if(cmp>0) return 1+size(x.left)+rank(key,x.right);
            else return size(x.left);
        }
    
        /**
         * @Author haien
         * @Description 把左右键集合按顺序收集到一个队列中
         * @Date 2018/11/6
         * @Param [lo, hi] 最小、最大结点
         * @return java.lang.Iterable<Key>
         **/
        public Iterable<Key> keys(Key lo,Key hi){
            if(lo==null) throw new IllegalArgumentException("First argument to keys() is null!");
            if(hi==null) throw new IllegalArgumentException("Second argument to keys() is null!");
    
            Queue<Key> queue=new Queue<>();
            keys(root,queue,lo,hi);
            return queue;
        }
    
        //其实就是中序遍历
        private void keys(Node x, Queue<Key> queue,Key lo,Key hi){
            //查到了最后一个结点的下一个结点null了,出栈
            if(x==null) return;
            keys(x.left,queue,lo,hi); //往左边找,找到最左边的元素才可以出栈
            queue.enQueue(x.key); //发现左结点是null,出栈,打印这个结点
            keys(x.right,queue,lo,hi); //往右边找
        }
    
        /**
         * @Author haien
         * @Description 计算树的高度
         * @Date 2018/11/7
         * @Param []
         * @return int
         **/
        public int height(){
            return height(root);
        }
    
        private int height(Node x){
            if(x==null) return 0; //结点为空,高度为0
            return 1+Math.max(height(x.left),height(x.right)); //最终树的高度=当前结点高度1+左子树高度+右子树高度
        }
    
        /**
         * @Author haien
         * @Description 按层级顺序把结点收集到队列keys中
         * @Date 2018/11/7
         * @Param []
         * @return java.lang.Iterable<Key>
         **/
        public Iterable<Key> levelOrder(){
            Queue<Key> keys=new Queue<>();
            Queue<Node> queue=new Queue<>();
            queue.enQueue(root);
            while(!queue.isEmpty()){
                Node x=queue.deQueue();
                if(x!=null){
                    keys.enQueue(x.key);
                    queue.enQueue(x.left);
                    queue.enQueue(x.right);
                }
            }
            return keys;
        }
    
    }
    
  • 代码实例:DataStructure/search/BST

    红黑树

  • 如果能保持树的平衡性,那么即使在最坏情况下也能保证在对数时间内找到元素。
  • 为了平衡,我们需要一些灵活性,因此我们允许一个结点可以保存多个键。因为这样我们在新插入键的时候就可以把它插到最后的叶结点里,而不是生成一个新的结点增加树的高度。
  • 3-结点:含有2个键和3个链接。左链接指向比它小的结点,中链接指向大小位于本身的俩键的结点,右链接指向比它大的结点。
  • 我们将3-结点定义为由一条红色链接相连的两个2-接结点,且统一为左链接,右链接只能是黑色的,这样它就还是一棵二叉树。
  • 红黑树:包含2-结点和3-结点,红是指3-结点中的俩键实际是通过红色链接相连的2结点,黑是指其他普通的链接。
  • 插入的时候我们把新结点插到最后一层的结点x上,并用红色的链接与它相连,如果x原本是个2-结点,也就是它还没有过红色链接,那么检查这个链接是否左链接,不是的话就把两个结点层次调换一下;如果x原本是个3-结点,那么现在就变成了4-结点,我们可以把中键提上去,拆成3个2-结点。

    public class RedBlackBST<Key extends Comparable<Key>,Value> { //表示Key可以是任何继承自Comparable的类型,并且这个Comparable必须是指定类型为Key的;写Key extends Comparable没什么问题,就相当于Key extends Comparable<Object>,但是 指定一下Key会更明确
        private static final boolean RED=true;
        private static final boolean BLACK=false;
    
        private Node root;
    
        private class Node{
            private Key key;
            private Value val;
            private Node left,right;
            private boolean color; //由其父结点指向它的链接的颜色
            private int size; //子树中的结点总数
    
            public Node(Key key, Value val, boolean color, int size) {
                this.key = key;
                this.val = val;
                this.color = color;
                this.size = size;
            }
        }
    
        private boolean isRed(Node x){
            if(x==null) return false;
            return x.color;
        }
    
        public int size(){
            return size(root);
        }
    
        private int size(Node x){
            if(x==null) return 0;
            return x.size;
        }
    
        /**
         * @Author haien
         * @Description 出现红色右连接,把它旋转到左边
         *              h原本揽着x的肩膀,x两手提着东西,现在h松手去提x左手的东西,x腾出手去揽h,变成了x在上
         * @Date 2018/11/13
         * @Param [h]
         * @return com.haien.search.RedBlackBST<Key,Value>.Node
         **/
        private Node rotateLeft(Node h){
            Node x=h.right;
            h.right=x.left;
            x.left=h;
            x.color=h.color;
            h.color=RED;
            x.size=h.size;
            h.size=1+size(h.left)+size(h.right);
            return x;
        }
        /**
         * @Author haien
         * @Description 出现错误的红色左链接,错误的情况就是这个结点及其左结点的左链接都是红色的,形成了4-结点,
         *              需要把这个结点和它的左结点互换,这样新的父结点左右链接就都是红色的,再通过flipColors来调整好
         * @Date 2018/11/13
         * @Param [h]
         * @return com.haien.search.RedBlackBST<Key,Value>.Node
         **/
        private Node rotateRight(Node h){
            Node x=h.left;
            h.left=x.right;
            x.right=h;
            x.color=h.color;
            h.color=RED;
            x.size=h.size;
            h.size=1+size(h.left)+size(h.right);
            return x;
        }
    
        /**
         * @Author haien
         * @Description 把一个左右链接都是红色的结点变成左右链接都是黑色的,但由父结点指向它的链接是红色的
         *              即,把一个4-结点的中建挤到父结点的键区;由于父结点也可能已经是3-结点,所以调用者需要递归调用此法
         * @Date 2018/11/13
         * @Param [x]
         * @return void
         **/
        private void flipColors(Node x){
            x.color=RED;
            x.left.color=BLACK;
            x.right.color=BLACK;
        }
    
        public void put(Key key,Value val){
            //查找key,找到则更新其值,否则创建新结点;返回root,当原树为空时就有用
            root=put(root,key,val);
            root.color=BLACK; //当原树为空时,root就是刚创建的结点了,它的颜色是red,应该改成black
        }
    
        private Node put(Node x,Key key,Value val){
            if(x==null) return new Node(key,val,RED,1);
    
            int cmp=key.compareTo(x.key);
            if(cmp<0) x.left=put(x.left,key,val);
            else if(cmp>0) x.right=put(x.right,key,val);
            else x.val=val;
    
            if(isRed(x.right)&&!isRed(x.left)) x=rotateLeft(x); //新插入的键比父结点大,出现了红色的右链接
            if(isRed(x.left)&&isRed(x.left.left)) x=rotateRight(x); //未命中查找终止于一个3-结点,并且新结点比它小,出现了连续两个左链接,形成了4-结点
            if(isRed(x.left)&&isRed(x.right)) flipColors(x);//出现两链接都是红色的结点
    
            x.size=1+size(x.left)+size(x.right);
            return x;
        }
    }
    
  • Key extends Comparable: 表示Key可以是任何继承自Comparable的类型,并且这个Comparable必须是指定类型为Key的;写Key extends Comparable没什么问题,就相当于Key extends Comparable,但是 指定一下Key会更明确
  • 《算法》269页
  • 代码实例:DataStructure/search/RedBlackBST