上一篇使用鏈表實現符號表的查找,無論是查找還是插入都需要遍歷整個鏈表,非常的低效。
今天使用數組通過二分查找來實現,相比順序循環會高效得多。
它使用的數據結構是一對平行數組,一個存儲鍵一個存儲值。
先來看代碼:
template<class T, class K> class ArraySearch
{
public:
ArraySearch(int capacity){
m_key = new T[capacity];
m_value = new K[capacity];
}
int size(){return N;}
bool isEmpty(){return size() == 0;}
//小於key的鍵的數量
int rank(T key){
int lo = 0, hi = N - 1;
while(lo <= hi){
int mid = lo + (hi - lo)/2;
if(key > m_key[mid]){
lo = mid + 1;
}
else if(key < m_key[mid]){
hi = mid - 1;
}
else{
return mid;
}
}
return lo;
}
//獲取key對應的值
K get(T key){
if(isEmpty())
return K();
int i = rank(key);
if(i < N && m_key[i] == key){
return m_value[i];
}
else{
return K();
}
}
//將鍵值對存入表中
void put(T key, K value){
int i = rank(key);
if(i < N && m_key[i] == key){
m_value[i] = value;
return;
}
for(int j = N; j > i; --j){
m_key[j] = m_key[j - 1];
m_value[j] = m_value[j - 1];
}
m_key[i] = key;
m_value[i] = value;
N++;
}
//最小的鍵
T min(){
return m_key[0];
}
//最大的鍵
T max(){
return m_key[N - 1];
}
//排名爲k的鍵
T select(int k){
return m_key[k];
}
//大於等於key的最小值
T ceiling(T key){
int i = rank(key);
return m_key[i];
}
//小於等於key的最大值
T floor(T key){
int i = rank(key);
if(m_key[i] == key){
return m_key[i];
}
else {
return m_key[i - 1];
}
}
//從表中刪去鍵key及對應的值
void deleteKey(T key){
if(isEmpty())
return;
int i = rank(key);
if(m_key[i] != key)
return;
for(int j = i; j < N - 1; ++j){
m_key[j] = m_key[j + 1];
m_value[j] = m_value[j + 1];
}
N--;
}
private:
T *m_key;
K *m_value;
int N = 0;
};
這份實現的核心是rank()方法,它返回表中小於給定鍵的鍵的數量。對於get()方法,只要給定的鍵存在於表中,rank()方法就能夠精確地告訴我們在哪裏能夠找到它(如果找不到,那它肯定不在表中)。
對於put()方法,只要給定的鍵存在於表中,rank()方法就能夠精確告訴我們去哪裏去更新它的值,以及當鍵不在是將鍵存儲到表的何處。我們將所有更大的鍵向後移動一格騰出位置,並將給定的鍵值對分別插入到各自數組中的合適位置。
上面的實現數組大小寫死了,沒有添加調整數組大小的內容。
二分查找
我們使用有序數組存儲鍵的原因是,經典的二分查找方法能夠根據數組的索引大大減少每次查找所需的比較次數。我們會使用有序索引數組來標識被查找的鍵可能存在的子數組的範圍。在查找時,我們先將被查找的鍵和子數組的中間鍵比較,如果被查找的鍵小於中間鍵,我們就在左子數組中繼續查找,如果大於我們就在右子數組中繼續查找,否則中間鍵就是我們要找得鍵。
下面是使用遞歸的rank()方法:
int rank(T key, int lo, int hi){
if(hi < lo){
return lo;
}
int mid = lo + (hi - lo)/2;
if(key > m_key[mid]){
return rank(key, mid + 1, hi);
}
else if(key < m_key[mid]){
return rank(key, lo, mid - 1);
}
else{
return mid;
}
}
這兩個版本是等價的。遞歸的方式可能會更直觀的一點,它保留了以下性質:
1.如果表中存在鍵,rank()方法返回該鍵的位置,也就是表中小於它的鍵的數量;
2.如果表中不存在該鍵,rank()還是應該返回表中小於它的鍵的數量,即第一個判斷的返回值。
小結
使用數組的方式實現符號表的查找,查找的操作由於使用二分查找比較高效,但插入的時候會調整大於該值的數組的位置,當數據量很大的時候,該操作還是很耗時的。
對比着兩種鏈表和有序數組:
使用的數據結構 | 優點 | 缺點 |
鏈表(順序查找) | 適用於小型問題 | 對於大型符號表很慢 |
有序數組(二分查找) | 最優的查找效率和空間需求,能夠進行有序性相關的操作 | 插入操作很慢 |
下一篇介紹將二分查找的效率和鏈表的靈活性結合起來的數據結構。