1. 程式人生 > >順序查詢演算法(原理、實現及時間複雜度)

順序查詢演算法(原理、實現及時間複雜度)

一提到查詢,比如從一個數列中查詢第1個值為k的數,那麼我們最先想到的肯定是一個一個地找。從數列的第 1 個數開始對比,直到找到值為k的數。

順序查詢的定義為:在一個已知無序(或有序)的佇列中找出與給定的關鍵字相同的數的具體位置。其原理是讓關鍵字與佇列中的數從開始一個一個地往後逐個比較,直到找到與給定的關鍵字相同的數。

當然,順序查詢絕不僅限於對數字、字元的查詢,也適用於字首、物件資訊的關鍵資訊的匹配等。

順序查詢的原理與實現

順序查詢對數列是否有序沒有要求,也就是說是否有序對查詢效能來說無關緊要。一般是從數列的一端開始查詢,找到則返回對應的元素,沒有找到則返回一個無意義的結果。

演算法非常簡單,我們來看看程式碼實現:
public class SequentialSearch {
    private int[] array;
    public SequentialSearch(int[] array) {
        this.array = array;
    }
   
    /**
     * 直接順序查詢
     * @param key
     * @return
     */
    public int search(int key) {
        for (int i = 0; i < array.length; i++) {
            if (array[i] == key) {
                return i;
            }
        }
        return -1;
    }
}
其實這個簡單的演算法也有可優化的地方,優化方法就是把k的值放在陣列中下標為 0 的元素上,然後從後往前比較,直到 k 與陣列中的某個值相等,這時結束;如果陣列中沒有與其相等的值,則也會與陣列下標為 0 的值相等,這時結束。這樣可以避免對於陣列越界的比較。

優化後的程式碼如下:
/**
* 哨兵方式順序查詢
* @param key
* @return
*/
public int search2(int key) {
    // 先判斷是否等於下標為0的元素
    if (key == array[0]) {
        return 0;
    }
    // 臨時儲存array[0]的值
    int temp = array[0];
    // 賦值給下標為0的元素
    array[0] = key;
    int i = array.length - 1;
    // 倒序比較
    while(array[i] != key) {
        i --;
    }
    // 把array[0]原本的值賦回去
    array[0] = temp;
    // 比較到最後了也沒有找到返回-1
    if (i == 0) {
        return -1;
    }
    // 找到了的話返回陣列下標
    return i;
}
這樣,迴圈部分的比較操作與之前相比少了一半。

順序查詢的特點及效能分析

順序查詢沒有難度和技術含量,是我們誰都能想到並且最先會想到一種最直接的方法。比如在玩撲克牌的時候,若不需要大小王,則我們會拿過來所有的牌,從頭到尾地一張一張地找。

當然,順序查詢也可以結合併發來處理,比如我們把待查詢的數列分為前後兩個部分,開啟兩個執行緒去查詢,這就是利用了多核CPU去更快地完成任務。在上面撲克牌的例子中,我們如果有兩個人,就會把牌的一半分給另一個人來一起找,這樣顯然會更快一些。

在介紹查詢演算法的效能之前,讓我們先來了解一個詞 ASL(Average Search Length,平均查詢長度)。需要和要查詢的 key 進行比較的期望次數,被稱為查詢演算法的平均查詢長度。查詢成功時的 ASL 的計算公式為 ASL=Pi×Ci,其中,Pi 為查詢表中第 i 個元素的概率,Ci 為找到第 i 個元素時已經比較過的次數。

其實我們在很多時候沒必要太過糾結上面的公式,ASL 只是輔助我們瞭解查詢效能的,其實和時間複雜度類似。針對順序查詢,在能夠找到的情況下,ASL 為 1/n×(1+2+3+…+n),也就是 (1+n)/2,這裡假設每個元素的查詢概率相等。在最壞的情況下就是沒有找到,近似比較 n+2 次(在我們的優化版裡需要比較 n+2 次,在普通版順序查詢裡則需要比較 2n 次)。

現在我們來看看順序查詢的效能,平均時間複雜度為 O(n)
,n 是待查數列的長度,這其實沒什麼好解釋的,因為順序查詢是從頭到尾查詢,而且我們可以看到查找了整個陣列。當然最好的情況是陣列的第 1 個元素就是我們要找的元素,這樣可以直接找到,最壞的情況是到最後一個元素時才找到或者沒有找到要找的元素。

這裡我們額外分析一種併發查詢的情況,實際上我們在併發查詢時查詢的元素可能更多,比如兩個執行緒把待查數列分成兩部分進行查詢,如果元素恰巧在第 1 個執行緒要查的列中,那麼第 2 個執行緒的查詢就白做了。但是通常併發還是能夠更快地查詢的,除了這裡提到的特殊情況,元素如果在後面的執行緒中,則會快很多,尤其是在大數列、更多的執行緒時。

順序查詢是對數列順序的比較,沒有額外的空間,所以空間複雜度為常數 O(1)

順序查詢的適用場景

順序查詢就是這麼簡單,以至於我們在一般的簡單場景下根本不想用那些複雜的查詢演算法。

我們有時需要顯示一些人的詳細資訊,比如榜單,我們往往在排行榜中只儲存了使用者id與得分數,這時可以根據得分數獲取前 10 名用於展示,可是我們不知道這些 id 對應哪個人,所以往往還需要使用者的一些其他資訊如暱稱、頭像等。

這時我設計了這樣一段程式碼邏輯:先找出前 10 名的 id,再查出這 10 個人的詳細資訊列表,然後迴圈這 10 個人的 id,內層迴圈這 10 個人的詳細資訊,發現 id 一致時,我們就可以找到對應的某個人的詳細資訊了。這裡就是典型的順序查詢。

順序查詢由於其簡單的特點,在元素並不多的很多情況下,運用還是很廣泛的。因為沒有必要為了有限數量的元素使用複雜的演算法。