1.2 有序符号表
一个无序的符号表几乎是不可用的,因为其插入或查询的时间复杂度总是O(N)。对于大量的数据的操作性能十分低下。因此,实现一个有序的符号表十分必要。
1.2.1 一个有序符号表API
方法名称 | 功能 |
---|---|
void put(Key key,Value val) | 将键值存入表中(若值为空则将键key从表中删除) |
Value get(Key key) | 获取键key对于的值(若key不存在则返回null) |
void delete(Key key) | 从表中删去键key(及其对于的值) |
boolean contains(Key key) | 键key是否存在于表中 |
boolean isEmpty() | 表是否为空 |
int size() | 表中键值对的数量 |
void rank(Key key) | 小于key的键的数量 |
void select(int i ) | 选择排名为i的键 |
1.2.2 基于有序数组的二分查找符号表
有序数组二分查找依赖于一对平行数组Key[],Value[]。对Key进行排序时,也要对Value进行相应的移动。
二分查找符号表的核心方法是rank(Key key)。此方法返回的是比key小的元素的数量,在数组中即key应该存储的位置。
因此在插入操作时需要考虑到这种情况:
key比符号表中所有键都大,返回N,数组不用移动。
public int rank(Key key){
'''N为现在符号表内键值对的数量'''
int lo = 0, hi = N - 1;
while(lo <= hi){
int mid = lo + (hi - lo) / 2;
'''通过compareTo方法比较大小,key大返回值大于0,key小返回值小于0,相等返回0'''
int cmp = key.compareTo(keys[mid]);
if(cmp < 0) hi = mid - 1;
else if(cmp > 0) lo = mid + 1;
else return mid;
}
return lo
}
下图是基于有序数组的符号表实现的轨迹图
1.2.3 二分查找符号表部分实现
public class BinarySearchST<Key extends Comparable<Key> , Value> {
private Key[] keys;
private Value[] vals;
private int N;
public BinarySearchST(int capacity){
this.keys = (Key[]) new Comparable[capacity];
this.vals = (Value[]) new Object[capacity];
this.N = 0;
}
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 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 boolean contains(Key key){
return this.get(key) != null;
}
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 int rank(Key key){
'''已在上方实现量'''
}
//rank方法的递归实现
public int rank(Key key,int lo,int hi){
if(hi < lo) return lo;
int mid = lo + (hi - lo)/2;
int cmp = key.compareTo(keys[mid]);
if(cmp > 0) return rank(key,mid+1,hi);
else if(cmp < 0) return rank(key,lo,mid-1);
else return mid;
}
public Key select(int k){
return keys[k];
}
}
1.2.4 二分查找性能分析
二分查找方法操作成本
方法名称 | 时间复杂度 |
---|---|
void put(Key key,Value val) | N |
Value get(Key key) | logN |
void delete(Key key) | N |
boolean contains(Key key) | logN |
boolean isEmpty() | 1 |
int size() | 1 |
void rank(Key key) | lgN |
void select(int i ) | 1 |
简单符号表实现的成本总结:
算法 | 查找(最坏) | 插入(最坏) |
---|---|---|
顺序查找(无序链表) | N | N |
二分查找(有序数组) | lgN | 2N |
分析结果
1.在N个键的有序数组中进行二分查找,最多需要(lgN+1)次比较(无论是否成功)
2.在大小为N的有序数组中插入一个新的元素,在最坏情况下需要访问~2N次数组
3.在一个空表中插入N个元素在最坏情况下需要访问~ N2 次数组
结论
基于有序数组的二分查找符号表在查询方便确实有很大的提高,但是插入操作还是很慢,不适用于大规模的数据处理。