6.二分搜索模板及其变体(上)


前言

我们先来看下二分搜索维基解释

  • 在计算机科学中,折半搜索(英语:half-interval search),也称二分查找算法(binary search)、二分搜索法、二分搜索、二分探索,是一种在有序数组中查找某一特定元素的搜索算法。搜索过程从数组的中间元素开始,如果中间元素正好是要查找的元素,则搜索过程结束;如果某一特定元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半中查找,而且跟开始一样从中间元素开始比较。如果在某一步骤数组为空,则代表找不到。这种搜索算法每一次比较都使搜索范围缩小一半。

我个人比较喜欢读作“二分查找”。
这里讲点二分查找的概念准备:
二分查找主要解决“在一堆数中找出指定值的位置”这类问题。
由此我们可以得出以下结论,要想应用二分查找,这一堆数必须满足以下特征:

  • 存储在数组中
  • 有序排列

所以如果是用链表存储的,将无法应用二分查找。(有的面试官会问:二分查找用什么数据结构?数组还是链表?)
至于“有序排列”是递增还是递减,数组中是否存在相同元素,这些都不重要。不过一般情况,我们会希望数组是递增排列,且数组中的元素互不相同。

如何做二分搜索

如果您认真看了前言中附的二分查找基本概念,基本思路应该有了,而且我相信很多报班的小伙伴之前已经听过甚至自己实现过。
这里先讲个关于“二分查找”的有趣小故事

二分查找可以解决(预排序数组的查找)问题:只要数组中包含T(即要查找的值),那么通过不断缩小包含T的范围,最终就可以找到它。一开始,范围覆盖整个数组。将数组的中间项与T进行比较,可以排除一半元素,范围缩小一半。就这样反复比较,反复缩小范围,最终就会在数组中找到T,或者确定原以为T所在的范围实际为空。对于包含N个元素的表,整个查找过程大约要经过log(2)N次比较。

多数程序员都觉得只要理解了上面的描述,写出代码就不难了;但事实并非如此。如果你不认同这一点,最好的办法就是放下书本,自己动手写一写。试试吧。

我在贝尔实验室和IBM的时候都出过这道考题。那些专业的程序员有几个小时的时间,可以用他们选择的语言把上面的描述写出来;写出高级伪代码也可以。考试结束后,差不多所有程序员都认为自己写出了正确的程序。于是,我们花了半个钟头来看他们编写的代码经过测试用例验证的结果。几次课,一百多人的结果相差无几:90%的程序员写的程序中有bug(我并不认为没有bug的代码就正确)。

我很惊讶:在足够的时间内,只有大约10%的专业程序员可以把这个小程序写对。但写不对这个小程序的还不止这些人:高德纳在《计算机程序设计的艺术 第3卷 排序和查找》第6.2.1节的“历史与参考文献”部分指出,虽然早在1946年就有人将二分查找的方法公诸于世,但直到1962年才有人写出没有bug的二分查找程序。

——乔恩·本特利,《编程珠玑(第1版)》第35-36页。

下面我们来动手写一下看看这传说中干掉90%程序员的二分查找到底如何


//[参考鸣谢](http://blog.csdn.net/v_july_v/article/details/7093204)
 
int binarySearch(int *array, int left, int right, int value)  
{
    while (left<=right)          //循环条件,适时而变  
    {  
        int middle = left + (right-left)/2;  //使用“(left + right) / 2”可能会造成栈溢出  
  
        if (array[middle]>value)  
        {  
            right =middle-1;   //right赋值,适时而变  
        }   
        else if(array[middle]

乍看之下也就区区十来行代码,但是其中有很多要容易犯错的细节,童鞋们需要特别注意注释中提到的要点以及middle值的设定。

使用递归的二分搜索模板

简单粗暴,直接show code


int binarySearch(int *array, int left, int right, int value)
{
    if (left > right) return -1;
    
    int mid = left + (left + right)/2;
    if (arrary[mid] == value) {
        return mid;
    } else if (array[mid]> value) {
        return    binarySearch(array, left, mid -1, value);
    } else if (array[mid]< value) {
        return    binarySearch(array, mid+1, right, value);
    }
    
}

A generic binary search template

汗,看到这个标题以为董老师要写C++模板,结果……
还是我自己来吧


//模板源码
template
bool binarySearch(vector &array,T value)
{
    int left = 0;
    int right = array.size() -1;
    while ( left <= right )
    {
        int middle = left + ( right - left ) /2;
        if ( array[middle] == value )
        {
            return true;
        }
        else if ( array[middle] > value )
        {
            right = middle - 1;
        }
        else if ( array[middle] < value )
        {
            left = middle + 1;
        }
    }
    return false;
}


//在vs下进行简单测试,通过
//你可以改变数组的大小或者value的大小来进行更多的测试
//如有更多问题请联系我email:[email protected]

int _tmain(int argc, _TCHAR* argv[])
{   
    vector array;
    for(int i =0; i<20;i++)
        array.push_back(i);

    int value = 21;

    bool status = binarySearch(array, value);
    cout << status << endl;
    return 0;
}


Search Insert Position

leetcode原题地址
Given a sorted array and a target value, return the index
the target is found. If not, return the index where it would be if it were inserted in order.
You may assume no duplicates in the array.

没啥可说的,经过上面的洗礼,做这个题应该感觉很简单

示例代码:

 e32`123451
class Solution {
public:
    int searchInsert(vector& nums, int target) {
        int low = 0, high = nums.size()-1;

        // Invariant: the desired index is between [low, high+1]
        while (low <= high) {
            int mid = low + (high-low)/2;

            if (nums[mid] < target)
                low = mid+1;
            else
                high = mid-1;
        }

        // (1) At this point, low > high. That is, low >= high+1
        // (2) From the invariant, we know that the index is between [low, high+1], so low <= high+1. Follwing from (1), now we know low == high+1.
        // (3) Following from (2), the index is between [low, high+1] = [low, low], which means that low is the desired index
        //     Therefore, we return low as the answer. You can also return high+1 as the result, since low == high+1
        return low;    
    }
};


Search in Rotated Sorted Array

在轮转后的有序数组上应用二分查找
leetcode原题地址
Suppose a sorted array is rotated at some pivot unknown to you beforehand.

(i.e., 0 1 2 4 5 6 7 might become 4 5 6 7 0 1 2).

You are given a target value to search. If found in the array return its index, otherwise return -1.

You may assume no duplicate exists in the array.

该题要注意的地方是:判断给定值到底属于前面的区间{4,5,6,7}还是后面的区间{0,1,2}
这种数组并不是严格的递增排列,所以我们先比较M与L(也可以比较M与R,道理是一样的),然后相对应的来判断要查找的key值是否位于[L,M]区间,或是[M,R]区间,从而确定下一步究竟是减小R还是增大M


int rotated_binary_search(int A[], int N, int key) {
    
    int L = 0;
    int R = N - 1;

    while (L <= R) {
    // Avoid overflow, same as M=(L+R)/2
    int M = L + ((R - L) >> 1);
    if (A[M] == key) return M;

    // the bottom half is sorted
    if (A[L] <= A[M]) {
        if (A[L] <= key && key < A[M])
            R = M - 1;
        else
            L = M + 1;
    }

    // the upper half is sorted
    else {
        if (A[M] < key && key <= A[R])
            L = M + 1;
        else
            R = M - 1;
    }
    }
    
  return -1;
}

扩展:Search in Rotated Sorted Array II
有兴趣的童鞋可以试下。

Find the Square Root

利用二分法找到一个数的平方根。
董老师的例程就很不错,我就不自己写了


//函数功能
//输入:待求平方根的数n
//输出:误差允许范围内n的平方根

float squareRoot(float n)
{
    float x = n;
    float y = 1;
    float e = 0.000001;  //e decides the accuracy level

    while(x - y > e)
    {
        x = (x + y)/2;
        y = n/x;
    }

    return x;
}


矩阵搜索

题目描述:Check if an element is in a M*N matrix, each row and column of which is sorted.

有题目可知,这个矩阵的每行每列都是排好序的,也就是说每行每列是递增(或递减,请自行分析)的。
我们简单构造这样一个矩阵:
1 5 10 20
2 6 11 30
7 9 12 40
8 15 31 41

利用这个矩阵的特性我们可以运用二分查找的思想来简化这个问题
解决方法:不用递归,从矩阵的右上角(x,y)开始search(这样有一个好处:(x,y)的左侧所有点都小于matrxi[x][y], (x,y)的下侧所有点都大于matrix[x][y]),在每个点都做这样的判断:
示例代码:


bool isElementInMatrix(int **matrix, int M, int N, int target) {
    int row = 0;
    int column = N - 1;

    while (row < M && column >= 0) {
        if (matrix[row][column] == target) {
            return true;
        } else if (matrix[row][column] < target) {
            row++;
        } else {
            column--;
        }
    }
    return false;
}

Range Search

Given a sorted array of intergers with duplicates.Implement a function to get the start and end position of a given value.

有时我们并不是要寻找目标值,而是寻找到“刚刚”大于给定值的值或者“刚刚”小于给定值的值。
用数学方式描述就是:在原集合中寻找包含目标值区间的最小子集


void searchRangeHelper(int array[], int left, int right, int target, int &begin, int &end) {
    if (left > right) {
        return;
    }

    int mid = right - (right - left) / 2;
    if (array[mid] == target) {
        if (mid < begin || begin == -1) {
            begin = mid;
        }
        if (mid > end) {
            end = mid;
        }
        searchRangeHelper(array, left, mid - 1, target, begin, end);
        searchRangeHelper(array, mid + 1, right, target, begin, end);
    }
    else if (array[mid] < target) {
        searchRangeHelper(array, mid + 1, right, target, begin, end);
    }
    else {
        searchRangeHelper(array, left, mid - 1, target, begin, end);
    }
}

vector searchRange(int A[], int n, int target) {
    int begin = -1, end = -1;

    searchRangeHelper(A, 0, n - 1, target, begin, end);

    vector ans;
    ans.push_back(begin);
    ans.push_back(end);
    return ans;
}


你可能感兴趣的:(6.二分搜索模板及其变体(上))