二分查詢是通過將遞增序列不斷減半的方式尋找目標值的下標。其看似簡單,但往往存在一些細節被忽略,即while迴圈判斷條件和區間右邊界的取值方式。另外,我們往往只知二分查詢搜尋目標值的功能,而忽略了二分查詢的另外乙個功能,即尋找最大的小於目標值的元素和最小的大於目標值的元素。
目錄
一、二分查詢演算法的基本框架
二、二分查詢之搜尋目標值
三、二分查詢之搜尋目標值的左右邊界
四、總結
二分查詢充分利用了序列元素的遞增性質,採用分治策略搜尋目標值(目標值存在於序列中),目標值的左邊界和右邊界(目標值不存在於序列中),其中左邊界指的是最大的小於目標值的元素,右邊界指的是最小的大於目標值的元素。例如,有遞增數列,如果搜尋目標值8,則返回下標3,如果搜尋目標值9,則返回-1;如果搜尋目標值9的左右邊界,則左邊界為8(下標為3),右邊界為10(下標為4)。
因此二分查詢演算法的目標可以分為三個:
1、查詢目標值,若存在返回下標;不存在,返回-1
2、查詢目標值的左邊界,若序列所有元素大於目標值,返回-1
3、查詢目標值的右邊界,若序列所有元素小於目標值,返回序列長度
給出二分查詢演算法的基本框架:
int binarysearch(vector& nums, int target)
return ...; //取決於二分查詢的目的
}
如果想要充分理解二分查詢程式**的細節,那麼強烈建議將判斷條件表示出來,而不是使用else進行省略,熟練之後再進行省略。通過上述**框架,可以看出通過比較序列中位元素和目標值之間的關係對序列進行分半操作。
首先貼出二分查詢搜尋目標值的兩段實現**。
int binarysearch(vector& nums, int target)
return -1;
}int binarysearch(vector& nums, int target)
return -1;
}
觀察上述兩段**,兩者的區別在於right的初始化取值和while迴圈中的取值方式,我們對此進行分析:
(1)right的初始化取值為序列的長度,因為下標從0開始,所以序列長度是越界的,則初始化判斷區間為[0,len),左閉右開區間,後續判斷區間為[left,right)。所以while迴圈的結束條件為left==right,判斷條件為left(2)在每次進行區間拆半操作時,即對[left,right)進行拆半,中位元素nums[mid]已經搜尋完畢,使得下次搜尋區間仍然為左閉右開區間,則左半區間為[left,mid),右半區間為[mid+1,right)。所以right=mid。
(1)right的初始化取值為序列的長度-1,則初始化判斷區間為[0,len-1],左閉右閉區間,後續判斷區間為[left,right]。所以while迴圈的結束條件為left>right,判斷條件為left<=right。
(2)在每次進行區間拆半操作時,即對[left,right]進行拆半,中位元素nums[mid]已經搜尋完畢,使得下次搜尋區間仍然為左閉右閉區間,則左半區間為[left,mid-1],右半區間為[mid+1,right]。所以right=mid-1。
綜上,可以得出right的賦值方式和while迴圈判斷條件與right的初始化取值的關係:
right初始化取值
right取值方式
while迴圈判斷條件
right=nums.size()
right=mid
leftright=nums.zie()-1
right=mid-1
left<=right
再次回顧左右邊界的概念。左邊界指的是遞增序列中最大的小於目標值的元素,右邊界指的遞增序列中最小的小於目標值的元素。
我們首先貼出尋找目標值右邊界的**如下。我們該如何得到目標元素的左邊界呢,關鍵在於對序列中位數與目標元素相等情況即nums[mid]==target時的處理,不再是像之前一樣返回mid值,而是向左收縮,對搜尋區間右邊界right進行賦值操作,不斷壓縮右邊界,直至跳出while迴圈。
/*right初始化取值為nums.size()-1,while迴圈判斷條件為left<=right*/
void binarysearchleftboundary1(vector& nums, int target)
if (right >= 0)
else
cout << "遞增序列的所有元素都比目標元素大" << endl;
}/*right初始化取值為nums.size(),while迴圈判斷條件為left& nums, int target)
if (right > 0)
else
cout << "遞增序列的所有元素都比目標元素大" << endl;
}
同樣的道理,搜尋目標值的右邊界,即不斷向右收縮,對搜尋區間的左邊界left進行賦值操作,不斷壓縮左邊界,直至while迴圈結束。**如下:
/*right初始化取值為nums.size()-1,while迴圈判斷條件為left<=right*/
void binarysearchrightboundary1(vector& nums, int target)
if (left < nums.size())
else
cout << "遞增序列中所有元素都比目標元素小" << endl;
}/*right初始化取值為nums.size(),while迴圈判斷條件為left& nums, int target)
if (left < nums.size())
else
cout << "遞增序列中所有元素都比目標元素小" << endl;
}
1、搜尋目標值
if(nums[mid]==target)
return mid;
2、搜尋目標值的左邊界
if(nums[mid]==target)
right=mid; //最後結果左邊界=right-1
或者if(nums[mid]==target)
right=mid-1;//最後結果左邊界=right
3、搜尋目標值的右邊界
if(nums[mid]==target)
left=mid+1;
1、
2、
二分查詢演算法詳解
二分查詢針對的是乙個有序的資料集合,查詢思想有點類似分治思想。每次都通過跟區間的中間元素對比,將待查詢的區間縮小為之前的一半,直到找到要查詢的元素,或者區間被縮小為 0。二分查詢是一種非常高效的查詢演算法,高效到什麼程度呢?我們來分析一下它的時間複雜度。我們假設資料大小是 n,每次查詢後資料都會縮小...
二分查詢演算法詳解
最近刷了很多二分查詢相關的題目,這裡將近期的收穫做乙個總結,包括二分查詢的變形問題。如果能掌握,我相信以後基本上二分查詢相關的問題對你來說,都不是問題。二分查詢是啥我想不用過多的說明。我們都知道二分查詢的時間複雜程度是o logn o logn 查詢速度有多快呢?我們來分析一下。我們假設資料大小是 ...
二分查詢詳解
演算法概括 二分查詢又稱折半查詢,優點是比較次數少,查詢速度快,平均效能好 其缺點是要求待查表為有序表,且插入刪除困難。因此,折半查詢方法適用於不經常變動而查詢頻繁的有序列表。首先,假設表中元素是按公升序排列,將表中間位置記錄的關鍵字與查詢關鍵字比較,如果兩者相等,則查詢成功 否則利用中間位置記錄將...