一、定義
二分查詢也稱折半查詢(Binary Search),它是一種效率較高的查詢方法。但是,折半查詢要求線性表必須採用順序儲存結構,而且表中元素按關鍵字有序排列。
二、查詢過程
在一個有序序列中,取中間值把序列分成兩邊,先看中間值,如果中間值小於要查詢的值,那麼說明目標值一定在
右邊;如果中間值大於目標值,那麼目標值一定在左邊;如果中間值等於目標值,那麼查詢完成。
以猜數字遊戲為例,小明想一個0-1000的數,小紅來猜。
小明選的是623為例:
小紅:大於500嗎?
小明:yes
小紅:那麼一定在[501,1000]之間,大於750嗎?
小明:no
小紅:那麼一定在[501,750]之間,大於625嗎?
小明:no
小紅:那麼一定在[501,625]之間,大於563嗎?
小明:yes
小紅:那麼一定在[564,625]之間,大於594嗎?
小明:yes
小紅:那麼一定在[595,625]之間,大於610嗎?
小明:yes
小紅:那麼一定在[611,625]之間,大於618嗎?
小明:yes
小紅:那麼一定在[619,625]之間,大於622嗎?
小明:yes
小紅:那麼一定在[623,625]之間,大於624嗎?
小明:no
小紅:那麼一定在[623,623]之間,你想的數字是623
小明:yes。
對於1-1000之間的猜數字遊戲,最多隻需要10次就能找到。
二分查詢演算法的時間複雜度是O(logN)
三、框架
二分查詢的框架大致如下:
public int searchInsert(int[] nums, int target) {
int l = 0;
int r = nums.length-1;
int mid = l + (r-l)/2;
while(l < r){
//能找到,直接返回元素下標
if(nums[mid] == target)
return mid;
else if(nums[mid] < target){
l = mid + 1;
mid = l + (r-l)/2;
}
else{
r = mid-1;
mid = l + (r-l)/2;
}
}
//未找到
return -1;
}
注意:
1、中間值mid取法
應該用 mid = l + (r-l)/ 2 ,而不是 mid = (l + r) / 2,因為如果 l 和 r都很大,那麼(l + r)將會溢位整數範圍。
2、偶數個數的中間值
如果數字的總數是偶數,那麼中間值有兩個,按照 mid = l + (r-l)/ 2 算的話,中間值應該是左邊那個。例如對於1、2、3、4,l = 0,r = 3,
mid = 0 + (0+3)/ 2
=0 + 1
= 1。
因為mid、l、r都是int型別,如果有小數部分,會自動捨棄。
四、例題
1、搜尋插入位置
給定一個排序陣列和一個目標值,在陣列中找到目標值,並返回其索引。如果目標值不存在於陣列中,返回它將會被按順序插入的位置。
你可以假設陣列中無重複元素。
示例 1:
輸入: [1,3,5,6], 5
輸出: 2
示例 2:
輸入: [1,3,5,6], 2
輸出: 1
示例 3:
輸入: [1,3,5,6], 7
輸出: 4
示例 4:
輸入: [1,3,5,6], 0
輸出: 0
題目來源:力扣(LeetCode)
解題思路:先使用二分查詢,如果能找到,直接返回元素下標;
接下來討論不能找到的情況:
第一種情況,二分查詢結束後,目標值大於mid
以[1,3,5,6,9] 和目標值 4為例
初始狀態:
第一遍二分查詢結束:
第二遍二分查詢結束:
兩遍查詢結束之後,發現mid小於目標值4,此時,4應該插入在3後面,所以返回 mid+1。
第二種情況,二分查詢結束後,目標值小於mid
以[1,3,5,6,9] 和目標值 7為例,
初始狀態:
第一遍二分查詢後:
第二遍二分查詢後:
兩遍查詢結束之後,發現mid大於目標值7,此時,7應該插入在9前面,所以返回 mid。
題解程式碼:
public int searchInsert(int[] nums, int target) {
int l = 0;
int r = nums.length-1;
int mid = l + (r-l)/2;
while(l < r){
//能找到,直接返回元素下標
if(nums[mid] == target)
return mid;
else if(nums[mid] < target){
l = mid + 1;
mid = l + (r-l)/2;
}
else{
r = mid-1;
mid = l + (r-l)/2;
}
}
//第一種情況,二分查詢結束後,目標值大於mid
if(nums[mid] < target)
return mid+1;
//第二種情況,二分查詢結束後,目標值小於mid
else
return mid;
}
2、搜尋二維矩陣
題目來源:力扣(LeetCode)
解題思路:
既然題目提到了要編寫一個高效的演算法,那麼用兩個for迴圈暴力列舉肯定是不行的。
再看一遍題目,該矩陣具有以下特性:同一行,右邊的數要大,同一列,下面的數要大。由此我們可以發現,如果我們取一個數作為右上角,那麼比該數小的一定在其左邊的列之中,比該數大的一定在該數的下面的行之中。
例如:
對於下面矩陣,我們取7作為右上角的數,那麼比7小的一定在第0、1、2列,比7大的一定在第1、2、3行。
假設我們要找11,那麼首先取右上角7,發現7比11小,因為7是第一行最大的,所以第一行不可能再找到11,剔除第0行;從第1行開始找,同樣,從右上角20開始看,20大於11,所以第3列一定不可能找到11,因此我們剔除第3列;之後,16作為右上角,16大於11,剔除第2列;11作為右上角,找到目標數。
簡單來說就是:從右上角開始找,
右上角的數 = 目標,結束查詢,返回true;
右上角的數 < 目標,剔除該行;
右上角數 > 目標,剔除該列。
程式結束也未找到,返回false。
題解程式碼:
public boolean searchMatrix(int[][] matrix, int target) {
for(int i = 0;i < matrix.length;i++){
for(int j = matrix[0].length-1;j >=0;j--){
//從右上角開始搜尋
//如果右上角數字為目標數字,返回true
if(matrix[i][j] == target)
return true;
//如果右上角數字小於目標數字
//說明該行都小於目標數字,剔除該行
else if(matrix[i][j] < target)
break;
//如果右上角數字大於目標數字
//說明該列都大於目標數字,剔除該列
else
continue;
}
}
//沒有找到
return false;
}
原文連結:邱子林的部落格