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]<value)  
        {  
            left=middle+1;  
        }  
        else  
            return middle;    
        //可能会有读者认为刚开始时就要判断相等,但毕竟数组中不相等的情况更多  
        //如果每次循环都判断一下是否相等,将耗费时间  
    }

    return -1;  
}  

乍看之下也就区区十来行代码,但是其中有很多要容易犯错的细节,童鞋们需要特别注意注释中提到的要点以及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<typename T>
bool binarySearch(vector<T> &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:alvinyeats@gmail.com

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

    int value = 21;

    bool status = binarySearch<int>(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<int>& 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<int> searchRange(int A[], int n, int target) {
    int begin = -1, end = -1;

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

    vector<int> ans;
    ans.push_back(begin);
    ans.push_back(end);
    return ans;
}


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容

  • 背景 一年多以前我在知乎上答了有关LeetCode的问题, 分享了一些自己做题目的经验。 张土汪:刷leetcod...
    土汪阅读 12,719评论 0 33
  • LeetCode 刷题随手记 - 第一部分 前 256 题(非会员),仅算法题,的吐槽 https://leetc...
    蕾娜漢默阅读 17,676评论 2 36
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,585评论 18 139
  • 二分查找 二分查找算法是一种在有序数组中查找某一特定元素的搜索算法。搜素过程从数组的中间元素开始,如果中间元素正好...
    JasonDing阅读 1,496评论 0 3
  • 人物:老兵 职业:快递 深夜码字不是一个好习惯,可是不在现在的感触中记录下来,不知道明天还是不是现在这个心境 我:...
    秦家女子阅读 279评论 0 1