說說你對二分查詢的理解?如何實現?應用場景?

林恒發表於2024-04-25

一、是什麼

在電腦科學中,二分查詢演算法,也稱折半搜尋演算法,是一種在有序陣列中查詢某一特定元素的搜尋演算法

想要應用二分查詢法,則這一堆數應有如下特性:

  • 儲存在陣列中
  • 有序排序

搜尋過程從陣列的中間元素開始,如果中間元素正好是要查詢的元素,則搜尋過程結束

如果某一特定元素大於或者小於中間元素,則在陣列大於或小於中間元素的那一半中查詢,而且跟開始一樣從中間元素開始比較

如果在某一步驟陣列為空,則代表找不到

這種搜尋演算法每一次比較都使搜尋範圍縮小一半

如下圖所示:

相比普通的順序查詢,除了資料量很少的情況下,二分查詢會比順序查詢更快,區別如下所示:

二、如何實現

基於二分查詢的實現,如果資料是有序的,並且不存在重複項,實現程式碼如下:

function BinarySearch(arr, target) {
    if (arr.length <= 1) return -1
    // 低位下標
    let lowIndex = 0
    // 高位下標
    let highIndex = arr.length - 1

    while (lowIndex <= highIndex) {
        // 中間下標
        const midIndex = Math.floor((lowIndex + highIndex) / 2)
        if (target < arr[midIndex]) {
            highIndex = midIndex - 1
        } else if (target > arr[midIndex]) {
            lowIndex = midIndex + 1
        } else {
            // target === arr[midIndex]
            return midIndex
        }
    }
    return -1
}

如果陣列中存在重複項,而我們需要找出第一個制定的值,實現則如下:

function BinarySearchFirst(arr, target) {
    if (arr.length <= 1) return -1
    // 低位下標
    let lowIndex = 0
    // 高位下標
    let highIndex = arr.length - 1

    while (lowIndex <= highIndex) {
        // 中間下標
        const midIndex = Math.floor((lowIndex + highIndex) / 2)
        if (target < arr[midIndex]) {
            highIndex = midIndex - 1
        } else if (target > arr[midIndex]) {
            lowIndex = midIndex + 1
        } else {
            // 當 target 與 arr[midIndex] 相等的時候,如果 midIndex 為0或者前一個數比 target 小那麼就找到了第一個等於給定值的元素,直接返回
            if (midIndex === 0 || arr[midIndex - 1] < target) return midIndex
            // 否則高位下標為中間下標減1,繼續查詢
            highIndex = midIndex - 1
        }
    }
    return -1
}

實際上,除了有序的陣列可以使用,還有一種特殊的陣列可以應用,那就是輪轉後的有序陣列

有序陣列即一個有序數字以某一個數為軸,將其之前的所有數都輪轉到陣列的末尾所得

例如,[4, 5, 6, 7, 0, 1, 2]就是一個輪轉後的有序陣列

該陣列的特性是存在一個分界點用來分界兩個有序陣列,如下:

分界點有如下特性:

  • 分界點元素 >= 第一個元素
  • 分界點元素 < 第一個元素

程式碼實現如下:

function search (nums, target) {
  // 如果為空或者是空陣列的情況
  if (nums == null || !nums.length) {
    return -1;
  }
  // 搜尋區間是前閉後閉
  let begin = 0,
    end = nums.length - 1;
  while (begin <= end) {
    // 下面這樣寫是考慮大數情況下避免溢位
    let mid = begin + ((end - begin) >> 1);
    if (nums[mid] == target) {
      return mid;
    }
    // 如果左邊是有序的
    if (nums[begin] <= nums[mid]) {
      //同時target在[ nums[begin],nums[mid] ]中,那麼就在這段有序區間查詢
      if (nums[begin] <= target && target <= nums[mid]) {
        end = mid - 1;
      } else {
        //否則去反方向查詢
        begin = mid + 1;
      }
      //如果右側是有序的
    } else {
      //同時target在[ nums[mid],nums[end] ]中,那麼就在這段有序區間查詢
      if (nums[mid] <= target && target <= nums[end]) {
        begin = mid + 1;
      } else {
        end = mid - 1;
      }
    }
  }
  return -1;
};

對比普通的二分查詢法,為了確定目標數會落在二分後的哪個部分,我們需要更多的判定條件

三、應用場景

二分查詢法的O(logn)讓它成為十分高效的演算法。不過它的缺陷卻也是比較明顯,就在它的限定之上:

  • 有序:我們很難保證我們的陣列都是有序的
  • 陣列:陣列讀取效率是O(1),可是它的插入和刪除某個元素的效率卻是O(n),並且陣列的儲存是需要連續的記憶體空間,不適合大資料的情況

關於二分查詢的應用場景,主要如下:

  • 不適合資料量太小的數列;數列太小,直接順序遍歷說不定更快,也更簡單
  • 每次元素與元素的比較是比較耗時的,這個比較操作耗時佔整個遍歷演算法時間的大部分,那麼使用二分查詢就能有效減少元素比較的次數
  • 不適合資料量太大的數列,二分查詢作用的資料結構是順序表,也就是陣列,陣列是需要連續的記憶體空間的,系統並不一定有這麼大的連續記憶體空間可以使用

參考文獻

  • https://zh.wikipedia.org/wiki/%E4%BA%8C%E5%88%86%E6%90%9C%E5%B0%8B%E6%BC%94%E7%AE%97%E6%B3%95#javascript_%E7%89%88%E6%9C%AC
  • https://www.cnblogs.com/ider/archive/2012/04/01/binary_search.html

如果對您有所幫助,歡迎您點個關注,我會定時更新技術文件,大家一起討論學習,一起進步。

說說你對二分查詢的理解?如何實現?應用場景?

相關文章