1. 程式人生 > >二分查詢演算法(左閉右開區間)

二分查詢演算法(左閉右開區間)

二分查詢演算法是一個基本但用處十分廣泛的演算法,但要寫出一個沒有bug的二分查詢演算法也不容易,《程式設計珠璣》一書中提到僅有百分之十的人可以第一次就寫出沒有bug的二分查詢演算法,主要原因在於尋找中間區間時資料有可能溢位,以及區間的選擇不正確導致死迴圈,陣列越界等等。二分查詢演算法一共有64種形式,由於在計算機中左閉右開區間非常普遍(比如迭代器中就是使用左閉右開區間),我將劃分範圍劃分為左閉右開區間。如下圖所示
(1)左閉右開區間
這裡寫圖片描述

也就是將區間分為三部分:
這裡寫圖片描述

int searchInsert(vector<int>& nums, int target) {
        int
left = 0; int right = nums.size(); //這裡指向最後一個元素的後一個元素 int mid = 0; while (left < right) { mid = left + ((right - left) >> 1); if(target < nums.at(mid)) //如果小於num[mid],那麼target在區間[left,mid)之間 right = mid; else
if (target > nums.at(mid)) //如果大於num[mid],那麼target在區間[mid+1,right)之間 left = mid + 1; else //如果等於num[mid],那麼直接返回 return mid; } return left; //如果未找到則返回最接近的位置
}

對應的一種在演算法中提到的對稱區間的情況,和上面的思想一樣,只不過區間換成了左閉右閉區間:
(2)左閉右閉區間:
這裡寫圖片描述

int searchInsert(vector<int>& nums, int target) {
        int left = 0;
        int right = nums.size()-1;
        int mid = 0;
        while (left <= right) {
            mid = left + ((right - left) >> 1);
            if(target < nums.at(mid))
                right = mid - 1;
            else if (target > nums.at(mid))
                left = mid + 1;
            else
                return mid;
        }
        return left;
    }

一下是二分查詢演算法在表驅動法的階梯表法中的應用

#include <iostream>
#include <string>
using namespace std;

const int LEVEL_LEN = 5;

const string g_degreeLevel[] = {
    "E","D","C","B","A"
};

const int g_degreeLimit[]={
    60,70,80,90,100
};

//一般階梯訪問法
string FindDegreeLevel(int degree)
{
    int index = 0;
    while (g_degreeLevel[index] != "A")
    {
        if(degree > g_degreeLimit[index])
           ++index;
        else
            break;
    }
    return g_degreeLevel[index];
}

//階梯訪問表的二分法應用
string BinaryFindDegreeLevel(int degree)
{
    //利用二分法查詢確定區間(理解迴圈不變式以便寫出正確的二分查詢演算法)1、保證尋找的值一定在尋找區間內,2、保證尋找區間一直縮小
    int midIndex = 0;
    int lowIndex = 0;
    int highIndex = LEVEL_LEN - 1;
    while (lowIndex <= highIndex)   //保證查詢值在[lowIndex,highIndex]之間
    {
        midIndex = lowIndex + ((highIndex-lowIndex) >> 1);  //防止溢位
        if(degree > g_degreeLimit[midIndex])    //degree大於區間[lowIndex,midIndex]的值
            lowIndex = midIndex + 1;            //那麼degree必然在區間[midIndex+1,highIndex]之間
        else if(degree < g_degreeLimit[midIndex])   //degree小於區間[midIndex,highIndex]的值
            highIndex = midIndex - 1;               //那麼degree必然在區間[lowIndex,midIndex-1]之間
        else
            return g_degreeLevel[midIndex];
    }
    return g_degreeLevel[lowIndex];
}

//非遞迴二分查詢
int BinarySearch(int *searchObject,int searchSize,int key)
{
    if(searchObject == NULL || searchSize == 0)
        return -1;
    int lowIndex = 0;
    int highIndex = searchSize - 1;
    int midIndex = 0;
    while(lowIndex <= highIndex)    //保證在閉區間[lowIndex,highIndex]中存在
    {
        midIndex = lowIndex + ((highIndex-lowIndex) >> 1);
        if(key > searchObject[midIndex])    //key大於區間[lowIndex,midIndex]之間的所有值
            lowIndex = midIndex + 1;
        else if(key < searchObject[midIndex]) //key小於區間[midIndex,highIndex]之間的所有值
            highIndex = midIndex - 1;
        else
            return midIndex;
    }
    return -1;
}

//遞迴二分查詢(左閉右閉區間為初始值並保持)
int RecrusionBinarySearch(int *searchObject,int lowIndex,int highIndex,int key)
{
    if(searchObject == NULL || lowIndex > highIndex)
        return -1;
    else
    {
        int midIndex = lowIndex + ((highIndex-lowIndex) >> 1);
        if(key == searchObject[midIndex])
            return midIndex;
        else if(key > searchObject[midIndex])
            RecrusionBinarySearch(searchObject,midIndex+1,highIndex,key);
        else
            RecrusionBinarySearch(searchObject,lowIndex,midIndex-1,key);
    }
}

//遞迴二分查詢(左閉右開區間為初始值並保持)
int BinarySearch2(int *searchArray, int searchSize, int key)
{
    if(searchArray == NULL || searchSize == 0)
        return -1;
    int midIndex = 0;
    int leftIndex = 0;
    int rightIndex = searchSize - 1;
    while (leftIndex < rightIndex - 1)  //保證陣列長度減少1
    {
        midIndex = leftIndex + ((rightIndex-leftIndex) >> 1);
        if(key >= searchArray[midIndex])     //key在區間[midIndex,rightIndex)之間
            leftIndex = midIndex;
        else                                 //key在區間[leftIndex,midIndex)之間
            rightIndex = midIndex;
    }
    //此時leftIndex >=rightIndex-1
    if(searchArray[leftIndex] == key)
        return leftIndex;
    else if (searchArray[rightIndex] == key)
        return rightIndex;
    else
        return -1;
}

//遞迴二分查詢(左閉右開區間為初始值並保持)
int BinarySearch2(int *searchObject,int searchSize,int key)
{
    if(searchObject == NULL || searchSize == 0)
        return -1;
    int lowIndex = 0;
    int highIndex = searchSize - 1;
    int midIndex = 0;
    while(lowIndex < highIndex)    //保證在區間[lowIndex,highIndex)中存在
    {
        midIndex = lowIndex + ((highIndex-lowIndex) >> 1);
        if(key > searchObject[midIndex])    //也就是對於searchObject[0-midIndex]均小於key,那麼key在區間[midIndex+1,highIndex)之間,資料減少mid-low+1
            lowIndex = midIndex + 1;
        else                                //也就是key<=searchObject[midIndex],保證key在區間[lowIndex,midIndex]之間,也在[lowIndex,midIndex+1)之間
            highIndex = midIndex;
    }
    if(key == searchObject[lowIndex])
        return lowIndex;
    return -1;
}

int main(int argc,char** argv)
{
    //階梯訪問表
    int degree = 0;
    while(cin >> degree)
    {
        string result = BinaryFindDegreeLevelRec(degree);
        cout << result;
    }

    //二分查詢法
    int array[10];
        for (int i=0; i<10; i++)
            array[i] = i+1;

        cout<<"No recursive:"<<endl;
        cout<<"position:"<<RecrusionBinarySearch(array, 0,9, 6)<<endl;

    return 0;
}