查詢演算法總結(順序、折半、分塊、二叉排序樹)
1、 列表:待搜尋的資料集合。
2、 關鍵字:要查詢的那個資料。
3、 查詢,檢索:一種演算法過程。給出一個key值(關鍵字),在含有若干個結點的序列中找出它。
4、 查詢表:同一型別的資料元素的集合。
5、 靜態查詢表:查詢某個元素、檢索指定元素的屬性。
6、 動態查詢表:查詢後插入、刪除。
7、 查詢成功:當某個元素的key值等於給定值k,返回該元素的位置。
8、 查詢失敗:所有元素的key值均不等於給定的值k,返回表示失敗的標識。
二、基於線性表的查詢:
1、 順序查詢:<適合物件——無序或有序佇列>
(1)思想:逐個比較,直到找到或者查詢失敗。
(2)時間複雜度:T(n) = O(n)。
(3)空間複雜度:S(n) = O(n)。
(4)程式:
Int SeqSearch(RecordList l, KeyType key)
{
l.r[0].key = k; //k作哨兵,減少用for迴圈判斷下標是否越界的操作
I = l.length;
while(l.r[i].key != k)
i--;
return I;
}
(5)缺點:當n較大時,平均查詢長度較大,效率低。
2、 折半查詢:<適合物件——只是適用於有序表,且限於順序儲存結構(線性連結串列無法進行折半查詢)>
(1)思想:又稱二分查詢,對於已經按照一定順序排列好的列表,每次都用關鍵字和中間的元素對比,然後判斷是在前部分還是後部分還是就是中間的元素,然後繼續用關鍵字和中間的元素對比。
(2)時間複雜度:T(n) =O(logn)。具有n 個結點的完全二叉樹的深度為,在這裡儘管折半查詢判定二叉樹並不是完全二叉樹,但同樣相同的推導可以得出,最壞情況是查詢到關鍵字或查詢失敗的次數也如此。其時間複雜度遠好於順序查詢的時間複雜度O(n)了。
(3)程式:
Int BinSrch(RecordList l, KeyType k)
{
low = 1;
high = l.length;
while(low <= high)
{
mid = (low + high) / 2;
if(k == l.r[mid].key)
return mid;
else if(k < l.r[mid].key)
high = mid -1;
else
low = mid + 1;
}
return 0;
}
(4)小結:由於折半查詢的前提條件是需要有序表順序儲存,對於靜態查詢表,一次排序後不再變化,折半查詢比較適合。但對於需要頻繁執行插入或刪除操作的資料集來說,維護有序的排序會帶來不小的工作量,則不建議使用。
3、 分塊查詢:<適合物件——塊內的元素可以無序,但塊之間是有序的>
(1)思想:把無序的列表分成若干子塊(子表),然後建立一個索引表,記錄每個子塊中的某個關鍵字(最大的數或是最小的數),然後用關鍵字和這個索引表進行對比。該索引表還儲存子塊的起始位置,所以可以使用折半查詢或者順序查詢確定關鍵字所在的子塊位置。進入子塊後,使用順序查詢查詢。
(2)結構:分塊索引的索引項結構分三個資料項,最大關鍵碼,它儲存每一塊中的最大關鍵字,這樣的好處就是可以使得在它之後的下一塊中的最小關鍵字也能比這一塊最大的關鍵字要大;塊長,便於迴圈時使用;首資料元素指標,便於開始對這一塊中記錄進行遍歷。
(3)時間複雜度:O(logn)
(4)平均查詢長度:設n 個記錄的資料集被平均分成m塊,每個塊中有t 條記錄,顯然n=mt,或者說m=n/t。再假設Lb為查詢索引表的平均查詢長度,因最好與最差的等概率原則,所以Lb的平均查詢長度為(m+1)/2。 Lw為塊中查詢記錄的平均查詢長度,同理可知它的平均查詢長度為(t+1)/2。這樣分塊索引查詢的平均查詢長度為:
由此可知,平均長度不僅僅取決於資料集的總記錄數n,還和每一個塊的記錄個數t相關。 最佳的情況就是分的塊數m與塊中的記錄數t相同,此時意味著n=mt=t^2。即
可見,分塊索引的效率比順序查詢的O(n)是高了不少,不過顯然與折半查詢的O(logn)相比還有不小的差距。因此在確定所在塊的過程中,由於塊間有序,所以可以應用折半等手段來提高效率。
(5)程式:
/**
* 分塊查詢
*
* @param index 索引表,其中放的是各塊的最大值
* @param st 順序表,
* @param key 要查詢的值
* @param m 順序表中各塊的長度相等,為m
* @return
*/
private int BlockSearch(int[ ] index, int[ ] st, int key, int m)
{
// 在序列st陣列中,用分塊查詢方法查詢關鍵字為key的記錄
// 1.在index[ ] 中折半查詢,確定要查詢的key屬於哪個塊中
int i = partSearch(index, key);
if(i >= 0)
{
int j = i > 0 ? i * m : i;
int len = (i + 1) * m;
// 在確定的塊中用順序查詢方法查詢key
for(int k = j; k < len; k++)
{
if(key == st[k])
{
System.out.println("查詢成功");
return k;
}
}
}
System.out.println("查詢失敗");
return -1;
}
public int partSearchs(int[] data, int tmpData)
{
int mid;
int low = 0;
int high = data.length - 1;
while(low <= high)
{
mid = (low + high) / 2; // 中間位置
if(tmpData == data[mid])
{
return mid;
}
else if(tmpData < data[mid])
{
high = mid - 1;
}
else
{
low = mid + 1;
return low;
}
}
return -1; // 沒有查詢到
}
三、基於樹的查詢:
1、 二叉排序樹:
(1)思想:二叉排序樹:①若它的左子樹非空,則左子樹上所有節點的值均小於它的根節點的值;②若它的右子樹非空,則右子樹上所有結點的值均大於(或大於等於)它的根節點的值;③它的左、右子樹也分別為二叉排序樹。查詢的時候,中序遍歷二叉樹,得到一個遞增有序序列。查詢思路類似於折半查詢。
(2)時間複雜度:插入一個節點演算法的O(㏒n),插入n個的總複雜度為O(n㏒n)。
(4)程式:
BSTree SearchBST(BSTree bst, KeyType key)//遞迴演算法
{
If(!bst)
return NULL;
else if(bst->key == key)
return bst;
else if(bst->key > key)
return SearchBST(bst->lchild, key);
else
return SearchBST(bst->rchild, key);
}
BSTree SearchBST(BSTree bst, KeyType key)//非遞迴演算法
{
BSTree q;
q = bst;
while(q)
{
If(q->key == key)
return q;
else if(q->key > key)
q = q->lchild;
else
q = q->rchild;
}
return NULL;
}
2、 平衡二叉排序樹:(略)
3、 B_樹:(略)
四、計算式查詢:
1、 雜湊查詢:
(1)思想:首先在元素的關鍵字k和元素的儲存位置p之間建立一個對應關係H,使得p=H(k),H稱為雜湊函式。建立雜湊表時,把關鍵字為k的元素直接存入地址為H(k)的單元;以後當查詢關鍵字為k的元素時,再利用雜湊函式計算出該元素的儲存位置p=H(k),從而達到按關鍵字直接存取元素的目的。難點在於處理衝突的方式:①開放定址法②再雜湊法③鏈地址法④建立公共溢位區。
(2)時間複雜度:O(1)
(3)程式:
#define m <雜湊表長度>
#define NULLKEY <代表空記錄的關鍵字值>
typedef int KeyType;
typedef struct
{
KeyType key;
}RecordType;
typedef RecordType HashTable[m];
int HashSearch(HashTable ht, KeyType K)
{
h0 = hash(K);
if(ht[h0].key == NULLKEY)
return -1;
else if(ht[h0].key == K)
return h0;
else
{
for(i = 1; i <= m - 1; i++)
{
hi = (h0 + 1) % m;
if(ht[hi].key == NULLKEY)
return -1;
else if(ht[hi].key == K)
return hi;
}
return -1;
}
}