簡體   English   中英

有可能比線性搜索更快的算法?

[英]Can there be an algorithm faster than linear search?

我聽說沒有比線性搜索更快的算法(對於未排序的數組),但是,當我運行這個算法(線性)時:

public static void search(int[] arr, int value){
    for(int i = 0; i < arr.length; i++){
        if(arr[i] == value) return;
    }
}

使用長度為1000000的隨機數組,查找值的平均時間為75ns,但使用此算法:

public static void skipSearch(int[] arr, int value){
    for(int i = 0; i < arr.length; i+=2){
        if(arr[i] == value) return;
    }
    for(int i = 1; i < arr.length; i+=2){
        if(arr[i] == value) return;
    }
}

我得到一個更短的平均值,68ns?

編輯:很多人說我沒有做適當的基准測試,這是僥幸的,但我運行了1000000次這些功能並得到了平均值。 每次運行1000000次函數時,第一個算法得到75-76ns,第二個算法得到67-69ns。

我使用java的System.nanoTime()來測量它。

碼:

int[] arr = new int[1000];
Random r = new Random();
for(int i = 0; i < arr.length; i++){
    arr[i] = r.nextInt();
}
int N = 1000000;
long startTime = System.nanoTime();
for(int i = 0; i < N; i++){
    search(arr, arr[(int) Math.floor(Math.random()*arr.length)]);
}
System.out.println("Average Time: "+(System.nanoTime()-startTime)/(float)N+"ns");
startTime = System.nanoTime();
for(int i = 0; i < N; i++){
    skipSearch(arr, arr[(int) Math.floor(Math.random()*arr.length)]);
}
System.out.println("Average Skip Search Time: "+(System.nanoTime()-startTime)/(float)N+"ns");

很可能,因為你的search()方法沒有返回任何東西,並且循環中沒有任何動作,JVM中的JIT編譯器會 優化代碼 - 換句話說,在加載之前修改字節代碼JVM,這樣你的search()方法很可能不會(幾乎)做任何事情 哪個是最重要的,它可能也完全消除了循環。 JIT優化是非常聰明,它可以找出很多的情況下,當它不需要加載任何代碼轉換成JVM(但該代碼在字節碼.class文件)。

然后你只測量隨機數 - 而不是你的方法的實時復雜性。

閱讀例如如何確保沒有jvm和編譯器優化發生 ,應用它並再次運行您的基准測試。

同時更改search()方法,使它們返回索引 - 從而使優化器的生命更難。 但是,有時創建一個無法優化的代碼卻非常困難:)關閉優化(如上面的鏈接)更可靠。


通常,對未經優化的代碼進行基准測試是沒有意義的。 然而,在這種情況下,OP想要測量理論算法。 他想測量實際的傳球次數。 他必須確保實際執行循環。 這就是他應該關閉優化的原因。

OP認為他所測量的是算法的速度,而事實上算法根本沒有機會運行。 在這種特殊情況下關閉JIT優化可以修復基准。

這就是為什么我們不關心字面上計算執行時間需要多長時間,以及隨着輸入復雜性的增加,事物的規模增長。 看看漸近運行時分析:

https://en.wikipedia.org/wiki/Analysis_of_algorithms

什么是value統計? 在你的情況下,它很可能甚至是值。 很明顯,對於這兩種情況,算法O(n)O(n/2) + O(n/2)復雜度幾乎相同 - 線性時間

它只是偶然的“更快”。 您可能注意到的是,您的值更常出現在偶數索引上,而不是奇數索引上。

理論上,兩種算法的時間復雜度是相同的O(n) 一個猜測為什么skipSearch在運行時更快的是你正在搜索的元素恰好位於偶數索引處,因此它將在第一個循環中找到,而在最壞的情況下,它將執行一半的迭代次數線性搜索。 在這些基准測試中,您不僅需要考慮數據的大小,還需要考慮數據的外觀。 嘗試搜索不存在的元素,存在於偶數索引處的元素,存在於奇數索引處的元素。

此外,即使skipSearch使用適當的基准測試表現更好,它仍然只能減少幾納秒,因此沒有顯着增加,並且在實踐中不值得使用它。

某人提到的問題之一是你為每種算法使用不同的索引。 所以,為了解決這個問題,我重新編寫了一些代碼。 這是我的代碼:

int[] arr = new int[1000];
Random r = new Random();
for(int i = 0; i < arr.length; i++){
    arr[i] = r.nextInt();
}
int N = 1000000;
List<Integer> indices = new ArrayList<Integer>();
for(int i = 0; i < N; i++){
    //indices.add((int) Math.floor(Math.random()*arr.length/2)*2); //even only
    indices.add((int) Math.floor(Math.random()*arr.length/2)*2+1); //odd only
    //indices.add((int) Math.floor(Math.random()*arr.length)); //normal
}

long startTime = System.nanoTime();
for(Integer i : indices)
{
    search(arr, arr[i]);
}
System.out.println("Average Time: "+(System.nanoTime()-startTime)/(float)N+"ns");

startTime = System.nanoTime();
for(Integer i : indices)
{
    skipSearch(arr, arr[i]);
}
System.out.println("Average Skip Search Time: "+(System.nanoTime()-startTime)/(float)N+"ns");

所以你會注意到我創建了一個ArrayList<Integer>來保存索引,我提供了三種不同的方法來填充該數組列表 - 一個只有偶數,一個只有奇數,你原來的隨機方法。

使用偶數運行只會產生以下輸出:

平均時間:175.609ns

平均跳過搜索時間:100.64691ns

使用奇數運行只會產生以下輸出:

平均時間:178.05182ns

平均跳過搜索時間:263.82928ns

使用原始隨機值運行會產生以下輸出:

平均時間:175.95944ns

平均跳過搜索時間:181.20367ns

這些結果中的每一個都有意義。

當僅選擇偶數索引時,您的skipSearch算法為O(n / 2),因此我們處理的索引不超過一半。 通常我們不關心時間復雜度中的常數因素,但如果我們實際上關注運行時,則重要。 在這種情況下,我們確實將問題減少了一半,這將影響執行時間。 而且我們看到真正的執行時間相應減少了一半。

當僅選擇奇數索引時,我們看到對執行時間的影響更大。 這是可以預料的,因為我們處理的指數不少於一半。

當使用原始隨機選擇時,我們看到skipSearch表現更差(正如我們所期望的那樣)。 這是因為,平均而言,我們將擁有偶數指數和奇數指數。 偶數將很快找到,但奇數將很慢找到。 線性搜索將在早期找到索引3,而skipSearch在找到索引3之前大致處理O(n / 2)個元素。

至於為什么你的原始代碼給出了奇怪的結果,就我而言是懸而未決的。 可能是偽隨機數生成器略微偏向偶數,可能是由於優化,可能是由於分支預測器的瘋狂。 但它肯定不是通過為兩種算法選擇隨機指數來比較蘋果和蘋果。 其中一些可能仍會影響我的結果,但至少這兩種算法現在試圖找到相同的數字。

兩種算法都是相同的,哪一種更快取決於地點,你正在尋找的值放在哪里是巧合,哪一種在特定情況下更快。

但無論如何,第一個是更好的編碼風格。

當人們將線性搜索稱為“最快搜索”時,這是一種純粹的學術陳述。 它與基准測試無關,而是與搜索算法的Big O復雜性無關。 為了使此測量有用,Big O僅定義給定算法最壞情況。

在現實世界中,數據並不總是符合最壞情況,因此不同數據集的基准會發生波動。 在您的示例中,兩種算法之間存在7ns的差異。 但是,如果您的數據如下所示會發生什么:

linear_data = [..., value];
skip_search_data = [value, ...];

7ns的差異會變得更大。 對於線性搜索,復雜度每次都是O(n)。 對於跳過搜索,每次都是O(1)。

在現實世界中,“最快”的算法並不總是最快的。 有時,您的數據集適用於不同的算法。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM