第三章 查找(一) - 《算法》读书笔记
目录
第三章 查找(一)
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 | 是 |

浙公网安备 33010602011771号