查詢演算法(一)順序表查詢
順序表查詢分為:
順序查詢
有序查詢
- 折半查詢
- 插值查詢
- 斐波那契查詢
- 線性索引查詢
- 稠密索引查詢
- 分塊索引查詢
- 倒序查詢
1.順序查詢
基本思想:順序查詢是最基本的查詢技術,查詢過程是:從表中的第一個或者最後一個元素開始,依次將元素和待查詢的值進行比較,若相同,則查詢成功;若直到表尾或表頭,元素和待查詢的值都不相同,則查詢不成功
程式碼實現:
public class SequentialSearch {
public static void main(String[] args) {
int[] a = {0,1,16,24,35,47,59,62 ,73,88,99};
System.out.println(sequential(a,62));
}
public static int sequential(int[] a, int target){
int len = a.length;
for(int i=0;i<len;i++){
if(a[i]==target){
return i;
}
}
return -1;
}
}
複雜度分析:
查詢成功的平均查詢時間為(1+2+3+…+n)/n = (n+1)/2;
查詢失敗需要進行n+1次比較;
所以順序查詢的時間複雜度為O(n)
2.有序查詢
2.1折半查詢
折半查詢又叫二分查詢,其前提是序列為有序的,若為無需序列,需要先進行排序。
基本思想: 因為序列為有序序列,首先將待查詢的資料k與序列中間元素a[mid]進行比較,如相同,則查詢成功;若k > a[mid],則表明其存在於後半部分,否則在前半部分,由此繼續遞迴查詢,直到查詢到或者查詢失敗。
程式碼實現:
import java.util.Arrays;
public class BinarySearch {
public static void main(String[] args) {
int[] a = {0 ,1,16,24,35,47,59,62,73,88,99};
System.out.println(binary(a,62));
}
public static int binary(int[] a, int target){
int len = a.length;
int i = 0;
int j = len-1;
int mid;
//首先對資料進行排序
Arrays.sort(a);
while(i<=j){
mid = (i+j)/2;
if(a[mid] == target)
return mid;
if(target > a[mid]){
i = mid+1;
}else{
j = mid - 1;
}
}
return -1;
}
}
時間複雜度分析: 每次比較可以排除掉一半的資料,所以時間複雜度為O(logn)。
注意:二分查詢的前提是元素是有序的,所以對於靜態表,一次排序後不再變化,其查詢效率是不錯的,但是對於需要頻繁插入和刪除的資料來說,單純維護其序列有序就會帶來不小的開銷,所以在這種情況下,二分查詢並不適用。
2.2 插值查詢
插值查詢是根據待查詢的資料key和序列中最大最小值比較後的查詢方法,是二分查詢的一種變形。
基本思想: 對於折半查詢來說,每次都從1/2處進行,但是例如對於1~10000中間的100數裡查詢數值5來說,首先需要考慮從陣列下標較小的開始查詢,而不是直接從1/2處,這樣才可以提供查詢的效率。
所以將查詢點的計算改為:(key - a[low]) / (a[high] - a[low]);
所以 mid = low + (key - a[low]) * (high - low) / (a[high] - a[low])
程式碼實現:
只需要將二分查詢中的mid = (i+j)/2 改為
mid = i + (j-i)*(target - a[i])/(a[j]-a[i]);
時間複雜度分析: 其平均時間複雜度O(logn),對於表較長,且數值均勻分佈的序列來說,插值查詢平均效能要比折半查詢好得多,但是對於極端不均勻分佈的資料,差值查詢不一定是是合適的選擇。
2.3斐波那契查詢
斐波那契查詢也是二分查詢的一種變形,也是一種有序查詢演算法。
首先介紹下黃金分割:是指飾物各部分之間存在的一定數學比例關係,即將整體一分為二,較大部分/較小部分 = 整體 / 較大部分,其比值約為1.618:1。
對有斐波那契數列,後面每一個數是前面兩個數字的和,並且可以發現,隨著斐波那契數列的遞增,前後兩個數字的比值越來約接近0.618,在斐波那契查詢中,便是利用到這一特性。
基本思想: 將黃金分割的概念運用在查詢點的計算上,提高查詢效率。
如下圖所示(圖片來源https://blog.csdn.net/sayhello_world/article/details/77200009,侵權刪),序列的長度為斐波那契數列中的一個數值減1,即n = F(k) - 1; 那麼插入點mid = low + (F(k-1) - 1)。
若 key == a[mid], 查詢成功;
若 key > a[mid], 則在右半部分,所以有left = mid+1,且 k = k-2;
若 key < a[mid], 則在左半部分,所以有right = mid-1,且 k = k-1;
程式碼實現:
import java.util.Arrays;
public class FibonacciSearch {
public static void main(String[] args) {
int[] a = {0,1,16,24,35,47,59,62,73,88,99};
System.out.println(Fibonacci(a,62));
}
public static int Fibonacci(int[] a, int target){
int len = a.length;
int[] F = creatFibonacci();
int k = 0;
//遍歷斐波那契數列,找到k的值
for(int i = 0;i<F.length;i++){
if(len > F[k]-1){
k++;
}
}
//當F[k]-1>n的是否,需要不全待查詢的序列,Arrays類中的copyOf方法將a陣列補長到F[k]-1
int[] temp = Arrays.copyOf(a, F[k]-1);
for(int i = len;i<F[k]-1;i++){
temp[i] = temp[len-1];
}
int left = 0;
int right = F[k]-1;
int mid;
while(left<right){
mid = F[k-1] - 1;
if(temp[mid] == target)
return mid;
if(target > temp[mid]){
left = mid+1;
k -= 2;
}else{
right = mid - 1;
k -= 1;
}
}
return -1;
}
//初始化一個斐波那契陣列,長度為20,F[19] = 6765,已經挺大的了,所以這裡定義的長度20
public static int[] creatFibonacci(){
int[] F = new int[20];
F[0] = 0;
F[1] = 1;
for(int i = 2;i<20;i++){
F[i] = F[i-1] + F[i-2];
}
return F;
}
}
複雜度分析: 時間複雜度為O(logn)
總結:順序查詢不需要序列為有序的;而折半查詢、插值查詢以及斐波那契查詢則均是在序列有序的基礎上進行的,只是查詢點上有不同,各有各的優勢,在實際使用當中,需要綜合考慮來做出選擇。
3.線性索引查詢
在現實中,資料是不斷處於動態增長且無序的,前面有序查詢演算法需要基於有序的基礎上,所以對於大量增長非常快的資料集來說,維護資料有序的代價是非常非常大的。所以出現了索引。
索引就是把一個關鍵字與它對應的記錄相關聯的過程,加快查詢速度。
線性索引查詢就是將索引項集合組織為線性結構,稱為索引表。
3.1稠密索引
稠密索引是指在現象索引中,將資料集中的每個記錄對應一個索引項。
索引項按照關鍵碼有序排列,當查詢關鍵字時,可以通過折半、插值、斐波那契等有序演算法進行查詢,大大提高效率。
對於稠密所以,意味著索引和資料集具有同樣的長度,當資料非常大時,稠密索引的查詢效率大大降低。
3.2分塊索引
為減少索引個數,將資料集進行分塊,對每塊建立索引項。
這些塊需要滿足一下兩個條件:
- 快內無序,即對每一塊內的記錄不要求有序,維護快內有序需要付出大量的時間和空間代價;
- 塊間有序,快間有序,才能在查詢中帶來效率。
分塊索引查詢步驟:
(1)在分塊索引表中查詢關鍵字所在塊,因為塊間有序,所以同樣可以使用有序查詢演算法提高效率;
(2)根據塊首指標找到相應的塊,因為塊中無序,所以在塊中順序查詢的得到關鍵碼。
分塊查詢的時間複雜度為:O(√n)
3.3倒序索引
倒序索引的典型應用就是搜尋引擎。
倒序索引是建立被搜尋的關鍵詞和含這些關鍵字的頁面之間的對映,其中倒序指的是從關鍵詞中查詢到對應的源,而不是從源中查詢到相應的關鍵詞。
將倒序索引表排序後,同樣可以通過有序查詢演算法快速查詢到關鍵字,進而查詢到包含關鍵字的頁。
例如:為了檢索關鍵詞java,首先從倒序索引表中,找到該關鍵詞,然後便可以查詢到java所在的頁了。