第三章 查找
3.1符号表
符号表:一种存储键值对的数据结构,支持两种操作:插入(put:将一组新的键值对存入表中)和查找(get:根据给定的键得到相应的值)
3.1.1 API
具体实现时的几个设计决策如下:
1. 每个键只对应一个值,不允许重复的键
2. 键不能为空
3. 键值不能为空,键值为空代表不存在该键
4. 延时删除:将键对应的值置为空put(key,null),在某个时候删除所有值为空的键;即时删除:立刻从表中删除制定的键
5. 确定一个键是否存在于符号表中,用键的equals()方法,自定义的键要重写equals()方法;使用不可变的数据类型作为键
3.1.2 有序符号表
键是Comparable的对象,实现了键的有序性;Keyextends Comparable<key>
检验一个新的键是否插入合适的位置:
1. 基本操作是排序rank和选择select
2. Rank:找出小于指定键的数量(该键的位置);select:找出排名为k的键(排名从0开始);
3. 确认从0到size()-1的所有i都满足i==rank(select(i));所有的键都满足key==select(rank(key))
3.1.4 无序链表中的顺序查找
顺序查找:在查找中一个一个地顺序遍历符号表中的所有键并使用equals()方法来寻找与被查找的键匹配的键。Get()和put()都要顺序查找
基于无序链表的顺序查找算法:
public class SequentialSearchST<Key,Value> {
private Nodefirst;
private class Node{
Key key;
Value val;
Node next;
public Node(Key key, Valueval, Node next){
this.key = key;
this.val = val;
this.next = next;
}
}
public Valueget(Key key){
//查找给定的键,返回相关联的值
for(Node x = first; x != null; x = x.next){
if(key.equals(x.key)) return x.val;
}
returnnull;
}
public void put(Key key,Valueval){
//查找给定的键,找到则更新其值,否则在表中新建节点
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^2/2次比较
随机命中所需要的平均比较次数为~N/2
3.1.5 有序数组中的二分查找
为键和值分别创建数组,可以动态调整数组大小,基于有序数组的二分查找算法:
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];
N = capacity;
}
public int size(){
return N;
}
public int rank(Key key){
int lo = 0;
int 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 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 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){
//略
}
}
3.1.5 对二分查找的分析
在N个键的有序数组中进行二分查找最多需要(lgN +1)次比较(无论成功与否)
向大小为N的有序数组中插入一个新元素最坏情况下需要访问~2N次数组,向一个空符号表中插入N个元素最坏情况需要访问~N^2次数组,put()的效率仍然很低
我们需要找到同时能够高效完成put()和get()操作的算法
问答:
为什么用equals(),不一直用compareTo():不是所有的数据产生的键值对都能进行比较(比如一首歌,一幅图)
将多个值关联到同一个键怎么办:用其他信息来消除重复;使用Queue类型存储所有有相同键的值