符号表之二分查找(基于有序数组)

每篇一句:一个真正的快乐的人,是能够享受他的创造的人。那些像海绵一样,只取不予的人,只会失去快乐。  ——《洛克菲勒留给儿子的38封信》

1.基本思想


有序符号表使用的数据结构是一对平行的数组,一个存储键一个存储值。在 put 时可以保证数组键有序,然后使用数组的索引高效实现 get() 和其他操作

2.有序符号表的 API

/ 方法 描述
void put(Key key, Value value) 将键值对存入表中
Value get(Key key) 获取键 key 对应的值
boolean contains(Key key) 键 key 是否存在于表中
boolean isEmpty() 表是否为空
int size() 表中的键值对数量
Key min() 最小的键
Key max() 最大的键
Key floor(Key key) 小于等于 key 的最大键
Key ceiling(Key key) 大于等于 key 的最小键
int rank(Key key) 小于 key 的键的数量
Key select(int k) 排名为 k
void deleteMin() 删除最小的键
void deleteMax() 删除最大的键
int size(Key lo, Key hi) [lo···hi]之间键的数量
Iterable keys(Key lo, Key hi) [lo···hi]之间键的数量
Iterable keys() 表中所有键的集合,已排序

3.代码实现

package symboltable;

import com.sun.org.apache.xpath.internal.functions.FuncFloor;

import edu.princeton.cs.algs4.Queue;

public class BinarySearchST<Key extends Comparable<Key>, Value> {

    private Key[] keys;              //这里使用两个数组来保存键和值
    private Value[] values;
    private int N;

    @SuppressWarnings("unchecked")
    public BinarySearchST(int capacity) {
        keys = (Key[]) new Comparable[capacity];
        values = (Value[]) new Object[capacity];
    }

    public void put(Key key, Value value) {
        int i = rank(key);
        if(i < N && keys[i].compareTo(key) == 0) {
            values[i] = value;                  //如果找到匹配的值则更新
        }
        for(int j = N; j > i; j--) {            //将所有较大的元素全部向后移动一位
            keys[i] = keys[j-1];
            values[j] = values[j-1];
        }
        keys[i] = key;
        values[i] = value;
        N++;
    }

    public Value get(Key key) {
        if(isEmpty())
            return null;
        int i = rank(key);                      //返回小于它的元素数量
        if(i < N && keys[i].compareTo(key) == 0)
            return values[i];
        else
            return null;
    }

    public Key delete(Key key) {
        int i = rank(key);
        if(keys[i].compareTo(key) == 0) {             //如果找到元素,则将后面的元素向前移动一位
            for(int j = i; j < N - 1; j++) {
                keys[j] = keys[j + 1];
                values[j] = values[j + 1];
            }
            N--;
            return keys[i];
        }
        return null;
    }

    public boolean contains(Key key) {
        int i = rank(key);
        return keys[i].equals(key);
    }

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

    public int size() {
        return N;
    }

    public Key min() {
        return keys[0];
    }

    public Key max() {
        return keys[N-1];
    }

    public Key floor(Key key) {
        int i = rank(key);
        for(int j = i; j >= 0; j--) {
            if(select(j).compareTo(key) != 1)
                return select(j);
        }
        return null;
    }

    public Key ceiling(Key key) {
        int i = rank(key);
        return keys[i];
    }

    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;        //如果找到该键,rank() 会返回该键 的位置,也就是表中小于它的键的数量
        }
        return lo;                 //如果不存在,lo 就是表中小于它的键的数量
    }

    public Key select(int k) {
        return keys[k];
    }

    public void deledtMin() {
        delete(min());
    }

    public void deleteMax() {
        delete(max());
    }

    public int size(Key lo, Key hi) {
        if(hi.compareTo(lo) < 0)
            return 0;
        else if(contains(hi))
            return rank(hi) - rank(lo) + 1;
        else {
            return rank(hi) - rank(lo);
        }
    }

    public Iterable<Key> keys(Key lo, Key hi){
        Queue<Key> queue = new Queue<Key>();
        for(int i = rank(lo); i < rank(hi); i++) {       //将 lo~hi(不包括hi)的元素入队
            queue.enqueue(keys[i]);
        }
        if(contains(hi))                         //判断表中是否包含 hi 
            queue.enqueue(keys[rank(hi)]);
        return queue;
    }

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

    public static void main(String[] args) {
        BinarySearchST<Integer, String> binarySearchST = new BinarySearchST<>(10);

        for(int i = 0; i < 5; i++) {
            binarySearchST.put(i, "Timber" + i);
        }
        System.out.println("size = " + binarySearchST.size());

        for(int k : binarySearchST.keys()) {
            System.out.println("key:" + k + ", value:" + binarySearchST.get(k));
        }

        binarySearchST.delete(3);
        System.out.println("删除后:");
        for(int k : binarySearchST.keys()) {
            System.out.println("key: " + k + ", value: " + binarySearchST.get(k));
        }

        System.out.println("小于等于 3 的最大键: " + binarySearchST.floor(3));

        System.out.println("大于等于 3 的最小键: " + binarySearchST.ceiling(3));
    }
}

3.结果展示

4.rank() 方法分析


这份实现的核心就是 rank() 方法,它返回表中小于给定键的数量。它首先将 key 和中间键比较,如果相等则返回其索引,如果小于中间键则在左半部分查找,大于则在右半部分查找。

public int rank(Key key){
    int lo = 0, hi = N -1;
    while(lo <= hi){
        int mic = 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;
        }
    }
}


非递归版本的二分查找的性质:

  • 如果表中存在该键,rank() 返回该键的位置,也就是表中小于该键的键的数量;
  • 如果表中不存在该键,rank() 还是应该返回表中小于它的键的数量,也就是在循环结束时 lo 的值正好等于表中小于 被查找的键 的键的数量。

在有序数组中使用二分查找排名的轨迹如下图。(图片来自 Algorithms, 4th Edition )

5.性能分析


在 N 个键的有序数组中进行二分查找最多需要 (lgN + 1) 次比较 (无论是否成功)。而向其中插入一个新的元素在最坏情况需要访问 ~2N 次数组,因此向一个空符号表中插入 N 个元素在最坏情况下需要访问 ~2N 次数组。

其具体方法的操作成本如下:

方法 运行所需时间的增长数量级
put() N
get() logN
delete() N
contains() logN
size() 1
min() 1
max() 1
floor() logN
ceiling() logN
rank() logN
select() 1
deleteMin() N
deleteMax() 1

6.顺序查找和二分查找的比较


一般来说,二分查找都比顺序查找快得多。但是,二分查找也不适合很多应用。例如,无法处理 Leipzig Corpora 数据库,因为查找和插入操作是混合进行的,而且符号表也太大。


下表列出了顺序查找和二分查找的性能特点,表中是运行时间的增长数量级(二分查找是数组的访问次数,其他的则是比较次数):

算法 最坏情况下(N次插入后) 平均情况下(N次插入后) 是否高效支持有序性操作
查找 插入 查找 查找
顺序查找 N N N/2 N
二分查找 lgN 2N lgN N

7.写在最后

如果有什么不对或建议,欢迎批评指正。

猜你喜欢

转载自blog.csdn.net/qq_38495686/article/details/80072600