1. 程式人生 > >查詢(二):有序符號表

查詢(二):有序符號表

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 次陣列

結論

基於有序陣列的二分查詢符號表在查詢方便確實有很大的提高,但是插入操作還是很慢,不適用於大規模的資料處理。