1. 程式人生 > 實用技巧 >二分查詢演算法

二分查詢演算法

背景

今天,小鄭和小明還有小紅在玩一個猜數字的遊戲,小紅做莊。遊戲的規則是參賽選手每人三次機會,如果參賽選手用完三次機會後,還沒有猜中數字,他們就要請做莊的人吃棒棒糖,如果他們猜中了,坐莊的人請他們吃棒棒糖。

現在遊戲開始,有請小紅同學寫下一個數字在便利貼上。小紅若有期待地在便利貼上寫下了97

小明:50,對嗎?

小紅:不對, 偏小

小鄭:75, 可還行?

小紅:哈哈,錯啦,偏小

小明:87,對嗎?

小紅:你又錯了,偏小

小鄭:93, 對嗎?

小紅:差一點哦,偏小

小明:是不是96?

小紅:好可惜啊,你沒有機會了,請吃棒棒糖吧。

小鄭若有所思地回答:97

小紅:哇,你好棒啊,走我們去吃棒棒糖吧。

從樓上這個例子,我們可以抽出一個模型。給定一組有序排列的陣列,每次將目標值和靠近範圍中間的值進行比較,如果偏大就把中間值給左邊範圍再從新的範圍中間值找,如果偏小就把中間值給右邊範圍再從新的範圍中間值找,如果找到了我們返回其陣列下標對應的索引,如果沒有找到我們就返回-1。形如這樣的一種查詢方法,我們將其稱之為“二分查詢”。

實現一個二分查詢演算法

leetcode上有一題關於二分查詢的題目,我們就以這個為例來實現一個二分查詢。

題目描述

給定一個 n 個元素有序的(升序)整型陣列 nums 和一個目標值 target ,寫一個函式搜尋 nums 中的 target,如果目標值存在返回下標,否則返回 -1。

示例 1:

輸入: nums = [-1,0,3,5,9,12], target = 9
輸出: 4
解釋: 9 出現在 nums 中並且下標為 4

示例 2:

輸入: nums = [-1,0,3,5,9,12], target = 2
輸出: -1
解釋: 2 不存在 nums 中因此返回 -1

示例3

輸入:nums=[2, 5], target=2

輸出:0,

解釋:2出現在nums中並且下標為0

提示:

你可以假設 nums 中的所有元素是不重複的。
n 將在 [1, 10000]之間。
nums 的每個元素都將在 [-9999, 9999]之間。

這裡的提示對審題很有幫助,第一句話,它告訴你叫你放心這些資料都是不重複的,你放心寫最簡單那種寫法,第二句話,告訴你撐死也就10000個數字,資料量還算好的,第三句話,告訴你不用擔心整型的範圍會溢位、而且這裡是有符號的整型。所以提示還是很有幫助的,雖然不一定能夠體現在程式碼上。

思路

我們先分析下二分查詢幹了件什麼事?無非就是在一個範圍內取中間那位和目標元素進行比大小,如果沒有恰好等於目標元素,我就繼續選擇兩段其中的一段繼續劈它,直到劈到還剩下最後一個元素。所以顯而易見的我們需要一個while迴圈來幫助我們做這樣一件事。第二件事我們就要思考了,這個while迴圈成立的條件是什麼?總不能搞個死迴圈吧。當左邊標誌位的小於等於右邊標誌位的時候,我們繼續迴圈。這裡小於大家可能很好理解,那麼等於是什麼情況呢。我們就舉一舉示例3的例子,當我想要找到5這個數的時候,第一次left=mid=0, right=1,這個時候我們發現偏小,然後如果不取等號的情況下,我們就退出啦,所以這裡加上等號,第二次left=right=1的時候,剛好找到它。緊接著我們思考第三件事情,我取哪一半呢?這個時候就需要對於中間那位和目標那位兩者的大小了,如果偏大我就把中間那位減去1賦值給right,如果偏小,那麼我就把中間那位加1賦值給left,然後重新計算中間那位再次進行比較。好,接下來我們一起碼一碼。

程式碼實現

/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number}
 */
var search = function(nums, target) {
  var left = 0;
  var right = nums.length - 1;
  var mid = left + Math.floor((right - left) / 2);
  while(left <= right) {
    if (target > nums[mid]) {
      left = mid + 1;
    } else if (target < nums[mid]) {
      right = mid - 1;
    } else if (target === nums[mid]) {
      return mid;
    }
    mid = left + Math.floor((right - left) / 2);
  }
  return -1;
};

問題思考

二分查詢的時間複雜度是多少?

先說答案,O(logn), 大致的推到流程是,n(1/2)^k = 1, 倒推下k = log2n, 反應到計算機上的時間複雜度就是logn

二分查詢適用的場景是什麼?

面試刷人(因為容易寫錯),資料量中等,且資料不溢位範圍,最重要的是一組排好序的數進行二分查詢。

就拿我們上面最開頭的例子講,普通的查詢要97次,而用了二分查詢的思想6次了,這不是很香嘛。

參考文獻

704.二分查詢(leetcode): https://leetcode-cn.com/problems/binary-search/