第三章 查找(一) - 《算法》读书笔记

第三章 查找(一)

3.1 符号表

符号表是一种存储键值对的数据结构,支持两种操作:插入(put),即将一组新的键值对存入表中;查找(get),即根据给定的键得到相应的值

3.1.1 API

3.1.1.2 重复的键

  • 每个键值对应着一个值
  • 当存入的键值和已有的键冲突时,新的值会代替旧的值

3.1.1.3 空(null)键

  • 键不能为空

3.1.1.4 空(null)值

  • 通过get()方法是否返回null来测试给定的键是否存在于符号表中
  • 将null作为put()方法的第二个参数存入表中来实现删除

3.1.1.5 删除操作

  • 延时删除:将键对应的值置为空

  • 及时删除:即delete()方法所采取的方案

  • put()方法开头有防御性代码:

    • if(val == null){ delete(key); return;}
      
    • 保证符号表中任何键的值都不为空

3.1.2 有序符号表

  • 符号表都会保持键的有序并大大扩展它的API

3.1.2.8 成本模型

查找的成本模型:在学习符号表的实现时,我们会统计比较的次数(等价性测试时键的相互比较)。在内循环不进行比较(极少)的情况下,我们会统计数组的访问次数。

3.1.4 无序链表中的顺序查找

  • 使用链表作为数据结构,每个结点存储一个键值对,具体实现如下:
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个不同的键需要~N2次比较。

3.1.5 有序数组中的二分查找

  • 使用的数据结构是一对平行的数组,一个存储键,一个存储值,具体实现如下:
public class BinarySearchST<Key extends Comparable<key>, Value>{
    private Key[] keys;
    private Values[] val;
    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);
    
    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()方法,它返回表中小于给定键的键的数量
  • 对于get()方法,只要给定的键存在于表中,rank()方法就能精确地告诉我们在哪里能够找到它
  • 对于put()方法,只要给定的键存在于表中,rank()方法就能精确地告诉我们到哪里取更新它的值,以及当键不在表中时将键存到表的何处。
    • 我们将所有更大的键向后移动一格来腾出位置,并将给定的键值对分别插入到各自数组中的合适位置

3.1.5.1 二分查找

  • 基于有序数组的二分查找(迭代)实现如下:
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;
}

3.1.6 对二分查找的分析

在N个键的有序数组中进行二分查找最多需要(lgN+1)次比较(无论是否成功)。

向大小为N的有序数组中插入一个新的元素在最坏情况下需要访问2N次数组,因此向一个空符号表中插入N个元素在最坏情况下需要访问N2次数组。

3.1.7 预览

  • 简单的符号表实现的成本总结:
算法(数据结构) 最坏查找成本 最坏插入成本 平均查找成本 平均插入成本 是否高效地支持有序性相关的操作
顺序查找(无序链表) N N N/2 N
二分查找(有序数组) lgN 2N lgN N
posted @ 2021-01-28 23:19  一天到晚睡觉的鱼  阅读(82)  评论(0)    收藏  举报