《算法》——第三章:查找

在第三章里面,并不是一开始就讲的查找,第一节中首先介绍的是 符号表

3.1 符号表

符号表,其实就是存储了键值对的一种数据结构,键值对用于将一个键和一个值联系起来。符号表支持两种操作: 插入(put),即将一组新的键值对存入表中;查找(get),即根据给定的键得到对应的值。

下面看一下书中关于符号表的应用以及API

这里写图片描述

书中对于符号表的要求就不贴了,基本上就是set集合对于元素的要求。

无序链表中的顺序查找

符号表中使用的数据结构的一个简单选择是链表,每个节结点存储一个键值对。get()的实现即为遍历链表,用equals()方法比较需要被查找的键和每个结点中的键,匹配成功就返回相应的值,否则返回null。

这里写图片描述

算法3.1 (基于无序链表)

    public class SequentialSearchST<Key, Value>
    {
        private Node first;
        private class Node
        {
            Key key;
            Value val;
            Node next;

            public Node(Key key, Value val, Node next)
            {
                this.key = key;
                this.val = val;
                this.next = next;
            }
        }

        public Value get(Key key) {
            for(Node x = first; x != null; x = x.next) 
                if(key.equals(x.key))
                    return x.val;
            return null;
        }

        public void put(Key key, Value val) {
            for(Node x = first; x != null; x = x.next)
                if(key.equals(x.key))
                { x.val = val;   return; }      //命中,更新
            first = new Node(key, val, first);  //未命中,新建结点
        }

    }

在上面的符号表中,未命中的查找和插入操作都需要N次比较。命中的查找最坏需要N此比较,向一个空表中插入N个不同的键需要 N^2^/2(另外一种写法N 2 /2)次比较。

有序数组中的二分查找

算法3.2(BinarySearchST)可以保证数组中Comparable类型的键有序,然后使用数组的索引来高效地实现get()和其他操作。

这份实现的核心是rank()方法,它返回表中小于给定键的键的数量。对于get()方法只要给定的键存在于表中,rank()方法就能够精确地告诉我们在哪里能够找到它。

这里写图片描述

算法3.2 二分查找(基于有序数组)

    public class BinarySearchST<Key extends Comparable<Key>, Value>{
        private Key[] keys;
        private Value[] vals;
        private int N;
        public BinarySearchST(int capacity) {
            keys = (Key[])new Comparable[capacity];
            vals = (Value[]) new Object[capacity];
        }

        public int size() { return N; }

        public Value get(Key key) {
            if(isEmpty()) return null;
            int i = rank(key);
            if(i < N && keys[i].compareTo(key) == 0) return vals[i];
            else                                     return null;
        }

        public int rank(Key key) {
            int lo = 0, hi = N - 1;
            while(lo <= hi) {
                int mid = lo + (hi - lo) / 2;
                int cmp = key.compareTo(keys[mid]);
                if      (cmp < 0) hi = mid - 1;
                else if (cmp > 0) lo = mid + 1;
                else return mid;
            }
            return lo;
        }

        public void put(Key key, Value val) {
            int i = rank(key);
            if(i < N && keys[i].compareTo(key) == 0)
            { vals[i] = val; return; }
            for(int j = N; j > i; j--)
            { keys[j] = keys[j - 1]; vals[j] = vals[j - 1]; }
            keys[i] = key; vals[i] = val;
            N++;
        }

        public void delete(Key key) {}//删除操作
    }

这个二分查找的rank方法使用的不是递归,而是迭代,需要仔细地思考一下。

这里写图片描述

这里写图片描述

3.2 二叉查找树

从上面可以知道,二分查找的符号表的插入和查找操作时间复杂度级别相差很大,而二叉查找树可以保证查找和插入操作都是对数级别的。

算法3.3 二叉查找树的查找和排序方法的实现

public class BST<Key extends Comparable<Key>, Value>{
        private Node root;                  //二叉查找树的根结点

        private class Node{
            private Key key;
            private Value val;
            private Node left, right;       //指向子树的链接
            private int N;                  //以该结点为根的子树中的结点总数

            public Node(Key key, Value val, int N)
            { this.key = key; this.val = val; this.N = N; }
        }

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

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

        public Value get(Key key)
        { return get(root, key); }

        private Value get(Node x, Key key) {
            //在以x为根结点的子树中查找并返回key所对应的值
            if(x == null) return null;
            int cmp = key.compareTo(x.key);
            if      (cmp < 0) return get(x.left, key);
            else if (cmp > 0) return get(x.right, key);
            else return x.val;
        }

        public void put(Key key, Value val)
        { root = put(root, key, val); }

        private Node put(Node x, Key key, Value val) {
            if (x == null) return new Node(key, val, 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;
            x.N = size(x.left) + size(x.right) + 1;
            return x;
        }

算法 3.3 (续) 二叉查找树中max()、min()、floor()、ceiling()方法的实现

        public Key min(){
            return min(root).key;
        }

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

        //向下取整
        public Key floor(Key key){
            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.comparaTo(x.key);
            if(cmp == 0) return x;
            if(cmp < 0) return floor(x.left, key);
            Node t = floor(x.right, key);
            if(t != null) return t;
            else          return x;
        }
    }

上面方法中每个公有方法都有一个私有方法,它接收一个额外的链接作为参数指向某个节点,通过正文中描述的递归方法查找返回null或者含有指定key的节点Node。max()和ceiling的实现分别与min()和floor()方法基本相同,知识将代码中的left和right(以及>和<)调换了而已

算法 3.3(续) 二叉查找树中select()和rank()方法的实现

rank()是select()的逆方法,它会返回给定键的排名。它的实现和select()类似;

    public Key select(int k){
        return select(root, k).key;
    }

    private Node select(Node x, int k) {
        //返回排名为k的结点
        if (x == null) return null; 
        int t = size(x.left); 
        if      (t > k) return select(x.left,  k); 
        else if (t < k) return select(x.right, k-t-1); 
        else            return x; 
    }

    public int rank(Key key){
        return rank(key, root);
    }

    private int rank(Key key, Node x) {
        //返回以x为根结点的子树中小鱼x.key的键的数量
        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); 
    } 

算法 3.3(续) 二叉查找树的delete()方法的实现

二叉查找树中最难实现的就是delete()算法,即从符号表中删除一个键值对。

书中是这样去实现delete()算法的

这里写图片描述

    public void deleteMin() {
        root = deleteMin(root);
    }

    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 delete(Key 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 = min(t.right);
            x.right = deleteMin(t.right);
            x.left = t.left;
        } 
        x.size = size(x.left) + size(x.right) + 1;
        return x;
    } 

算法 3.3(续) 二叉查找树的范围查找操作

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

    public Iterable<Key> keys(Key lo, Key hi) {
        Queue<Key> queue = new Queue<Key>();
        keys(root, queue, lo, hi);
        return queue;
    } 

    private void keys(Node x, Queue<Key> queue, Key lo, Key hi) { 
        if (x == null) return; 
        int cmplo = lo.compareTo(x.key); 
        int cmphi = hi.compareTo(x.key); 
        if (cmplo < 0) keys(x.left, queue, lo, hi); 
        if (cmplo <= 0 && cmphi >= 0) queue.enqueue(x.key); 
        if (cmphi > 0) keys(x.right, queue, lo, hi); 
    } 

上面这些算法一下子看完的话头还是挺晕的。。。

—————————————————————

未完待续…

猜你喜欢

转载自blog.csdn.net/asjqkkkk/article/details/79457926