js基本搜尋演算法實現與170萬條資料下的效能測試

徐小夕發表於2019-06-16

js基本搜尋演算法實現與170萬條資料下的效能測試

前言

今天讓我們來繼續聊一聊js演算法,通過接下來的講解,我們可以瞭解到搜尋演算法的基本實現以及各種實現方法的效能,進而發現for迴圈,forEach,While的效能差異,我們還會了解到如何通過web worker做演算法分片,極大的提高演算法的效能。

同時我還會簡單介紹一下經典的二分演算法,雜湊表查詢演算法,但這些不是本章的重點,之後我會推出相應的文章詳細介紹這些高階演算法,感興趣的朋友可以關注我的專欄,或一起探討。

對於演算法效能,我們還是會採用上一章《前端演算法系列》如何讓前端程式碼速度提高60倍中的getFnRunTime函式,大家感興趣的可以檢視學習,這裡我就不做過多說明。

在上一章《前端演算法系列》如何讓前端程式碼速度提高60倍我們模擬了19000條資料,這章中為了讓效果更明顯,我將偽造170萬條資料來測試,不過相信我,對js來說這不算啥。。。

1.for迴圈搜尋

基本思路:通過for迴圈遍歷陣列,找出要搜尋的值在陣列中的索引,並將其推進新陣列

程式碼實現如下:

const getFnRunTime = require('./getRuntime');

 /**
  * 普通演算法-for迴圈版
  * @param {*} arr 
  * 耗時:7-9ms
  */
 function searchBy(arr, value) {
     let result = [];
    for(let i = 0, len = arr.length; i < len; i++) {
        if(arr[i] === value) {
            result.push(i);
        }
    }
    return result
 }
 getFnRunTime(searchBy, 6)
複製程式碼

測試n次穩定後的結果如圖:

js基本搜尋演算法實現與170萬條資料下的效能測試

2.forEach迴圈

基本思和和for迴圈類似:

/**
  * 普通演算法-forEach迴圈版
  * @param {*} arr 
  * 耗時:21-24ms
  */
 function searchByForEach(arr, value) {
    let result = [];
    arr.forEach((item,i) => {
        if(item === value) {
            result.push(i);
        }
    })
   return result
}
複製程式碼

耗時21-24毫秒,可見效能不如for迴圈(先暫且這麼說哈,本質也是如此)。

3.while迴圈

程式碼如下:

/**
  * 普通演算法-while迴圈版
  * @param {*} arr 
  * 耗時:11ms
  */
 function searchByWhile(arr, value) {
     let i = arr.length,
     result = [];
    while(i) {
        if(arr[i] === value) {
            result.push(i);
        }
        i--;
    }
    
   return result
}
複製程式碼

可見while和for迴圈效能差不多,都很優秀,但也不是說forEach效能就不好,就不使用了。foreach相對於for迴圈,程式碼減少了,但是foreach依賴IEnumerable。在執行時效率低於for迴圈。但是在處理不確定迴圈次數的迴圈,或者迴圈次數需要計算的情況下,使用foreach比較方便。而且foreach的程式碼經過編譯系統的程式碼優化後,和for迴圈的迴圈類似。

4.二分法搜尋

二分法搜尋更多的應用場景在陣列中值唯一併且有序的陣列中,這裡就不比較它和for/while/forEach的效能了。

基本思路:從序列的中間位置開始比較,如果當前位置值等於要搜尋的值,則查詢成功;若要搜尋的值小於當前位置值,則在數列的前半段中查詢;若要搜尋的值大於當前位置值則在數列的後半段中繼續查詢,直到找到為止

程式碼如下:

/**
   * 二分演算法
   * @param {*} arr 
   * @param {*} value 
   */
  function binarySearch(arr, value) {
    let min = 0;
    let max = arr.length - 1;
    
    while (min <= max) {
      const mid = Math.floor((min + max) / 2);
  
      if (arr[mid] === value) {
        return mid;
      } else if (arr[mid] > value) {
        max = mid - 1;
      } else {
        min = mid + 1;
      }
    }
  
    return 'Not Found';
  }
複製程式碼

在資料量很大的場景下,二分法效率很高,但不穩定,這也是其在大資料查詢下的一點小小的劣勢。

5.雜湊表查詢

雜湊表查詢又叫雜湊表查詢,通過查詢關鍵字不需要比較就可以獲得需要記錄的儲存位置,它是通過在記錄的儲存位置和它的關鍵字之間建立一個確定的對應關係f,使得每個關鍵字key對應一個儲存位置f(key)

雜湊表查詢的使用場景:

  • 雜湊表最適合的求解問題是查詢與給定值相等的記錄
  • 雜湊查詢不適合同樣的關鍵字對應多條記錄的情況
  • 不適合範圍查詢,比如查詢年齡18~22歲的同學

在這我先給出一個最簡版的hashTable,方便大家更容易的理解雜湊雜湊:

/**
 * 雜湊表
 * 以下方法會出現資料覆蓋的問題
 */
function HashTable() {
  var table = [];

  // 雜湊函式
  var loseloseHashCode = function(key) {
    var hash = 0;
    for(var i=0; i<key.length; i++) {
      hash += key.charCodeAt(i);
    }
    return hash % 37
  };

  // put
  this.put = function(key, value) {
    var position = loseloseHashCode(key);
    table[position] = value;
  }

  // get
  this.get = function(key) {
    return table[loseloseHashCode(key)]
  }

  // remove
  this.remove = function(key) {
    table[loseloseHashCode(key)] = undefined;
  }
}
複製程式碼

該方法可能會出現資料衝突的問題,不過也有解決方案,由於這裡涉及的知識點比較多,後期我會專門推出一篇文章來介紹:

  • 開放定址法
  • 二次探測法
  • 隨機探測法

使用web worker優化

通過以上的方法,我們已經知道各種演算法的效能和應用場景了,我們在使用演算法時,還可以通過web worker來優化,讓程式並行處理,比如將一個大塊陣列拆分成多塊,讓web worker執行緒幫我們去處理計算結果,最後將結果合併,通過worker的事件機制傳給瀏覽器,效果十分顯著。

總結

  1. 對於複雜陣列查詢,for/while效能高於forEach等陣列方法
  2. 二分查詢法的O(logn)是一種十分高效的演算法。不過它的缺陷也很明顯:必須有序,我們很難保證我們的陣列都是有序的。當然可以在構建陣列的時候進行排序,可是又落到了第二個瓶頸上:它必須是陣列。陣列讀取效率是O(1),可是它的插入和刪除某個元素的效率卻是O(n)。因而導致構建有序陣列的時候會降低效率。
  3. 雜湊表查詢的基本用法及使用場景。
  4. 條件允許的話,我們可以用web worker來優化演算法,讓其在後臺並行執行。

好啦,這篇文章雖然比較簡單,但十分重要,希望大家對搜尋演算法有更加直觀的認識,也希望大家有更好的方法,一起探討交流。

接下來會推出更多優秀的演算法,敬請期待哦~

最後,歡迎加入前端技術群,一起探討前端的魅力

js基本搜尋演算法實現與170萬條資料下的效能測試

更多推薦

相關文章