順序查詢演算法(原理、實現及時間複雜度)
阿新 • • 發佈:2018-12-23
一提到查詢,比如從一個數列中查詢第1個值為k的數,那麼我們最先想到的肯定是一個一個地找。從數列的第 1 個數開始對比,直到找到值為k的數。
順序查詢的定義為:在一個已知無序(或有序)的佇列中找出與給定的關鍵字相同的數的具體位置。其原理是讓關鍵字與佇列中的數從開始一個一個地往後逐個比較,直到找到與給定的關鍵字相同的數。
當然,順序查詢絕不僅限於對數字、字元的查詢,也適用於字首、物件資訊的關鍵資訊的匹配等。
演算法非常簡單,我們來看看程式碼實現:
其實這個簡單的演算法也有可優化的地方,優化方法就是把k的值放在陣列中下標為 0 的元素上,然後從後往前比較,直到 k 與陣列中的某個值相等,這時結束;如果陣列中沒有與其相等的值,則也會與陣列下標為 0 的值相等,這時結束。這樣可以避免對於陣列越界的比較。
優化後的程式碼如下:
這樣,迴圈部分的比較操作與之前相比少了一半。
當然,順序查詢也可以結合併發來處理,比如我們把待查詢的數列分為前後兩個部分,開啟兩個執行緒去查詢,這就是利用了多核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 次)。
現在我們來看看順序查詢的效能,平均時間複雜度為 ,n 是待查數列的長度,這其實沒什麼好解釋的,因為順序查詢是從頭到尾查詢,而且我們可以看到查找了整個陣列。當然最好的情況是陣列的第 1 個元素就是我們要找的元素,這樣可以直接找到,最壞的情況是到最後一個元素時才找到或者沒有找到要找的元素。
這裡我們額外分析一種併發查詢的情況,實際上我們在併發查詢時查詢的元素可能更多,比如兩個執行緒把待查數列分成兩部分進行查詢,如果元素恰巧在第 1 個執行緒要查的列中,那麼第 2 個執行緒的查詢就白做了。但是通常併發還是能夠更快地查詢的,除了這裡提到的特殊情況,元素如果在後面的執行緒中,則會快很多,尤其是在大數列、更多的執行緒時。
順序查詢是對數列順序的比較,沒有額外的空間,所以空間複雜度為常數
我們有時需要顯示一些人的詳細資訊,比如榜單,我們往往在排行榜中只儲存了使用者id與得分數,這時可以根據得分數獲取前 10 名用於展示,可是我們不知道這些 id 對應哪個人,所以往往還需要使用者的一些其他資訊如暱稱、頭像等。
這時我設計了這樣一段程式碼邏輯:先找出前 10 名的 id,再查出這 10 個人的詳細資訊列表,然後迴圈這 10 個人的 id,內層迴圈這 10 個人的詳細資訊,發現 id 一致時,我們就可以找到對應的某個人的詳細資訊了。這裡就是典型的順序查詢。
順序查詢由於其簡單的特點,在元素並不多的很多情況下,運用還是很廣泛的。因為沒有必要為了有限數量的元素使用複雜的演算法。
順序查詢的定義為:在一個已知無序(或有序)的佇列中找出與給定的關鍵字相同的數的具體位置。其原理是讓關鍵字與佇列中的數從開始一個一個地往後逐個比較,直到找到與給定的關鍵字相同的數。
當然,順序查詢絕不僅限於對數字、字元的查詢,也適用於字首、物件資訊的關鍵資訊的匹配等。
順序查詢的原理與實現
順序查詢對數列是否有序沒有要求,也就是說是否有序對查詢效能來說無關緊要。一般是從數列的一端開始查詢,找到則返回對應的元素,沒有找到則返回一個無意義的結果。演算法非常簡單,我們來看看程式碼實現:
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; } }
優化後的程式碼如下:
/** * 哨兵方式順序查詢 * @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)
這裡我們額外分析一種併發查詢的情況,實際上我們在併發查詢時查詢的元素可能更多,比如兩個執行緒把待查數列分成兩部分進行查詢,如果元素恰巧在第 1 個執行緒要查的列中,那麼第 2 個執行緒的查詢就白做了。但是通常併發還是能夠更快地查詢的,除了這裡提到的特殊情況,元素如果在後面的執行緒中,則會快很多,尤其是在大數列、更多的執行緒時。
順序查詢是對數列順序的比較,沒有額外的空間,所以空間複雜度為常數
O(1)
。順序查詢的適用場景
順序查詢就是這麼簡單,以至於我們在一般的簡單場景下根本不想用那些複雜的查詢演算法。我們有時需要顯示一些人的詳細資訊,比如榜單,我們往往在排行榜中只儲存了使用者id與得分數,這時可以根據得分數獲取前 10 名用於展示,可是我們不知道這些 id 對應哪個人,所以往往還需要使用者的一些其他資訊如暱稱、頭像等。
這時我設計了這樣一段程式碼邏輯:先找出前 10 名的 id,再查出這 10 個人的詳細資訊列表,然後迴圈這 10 個人的 id,內層迴圈這 10 個人的詳細資訊,發現 id 一致時,我們就可以找到對應的某個人的詳細資訊了。這裡就是典型的順序查詢。
順序查詢由於其簡單的特點,在元素並不多的很多情況下,運用還是很廣泛的。因為沒有必要為了有限數量的元素使用複雜的演算法。