zoukankan      html  css  js  c++  java
  • 二分查找

    刚进ACM集训队,天天刷题,然后好多题解都有二分两个字,哎呀我天!!我那个无助。还好,今天碰到了,不把你吃透,算我输!!!

    学习二分不在于表面的题目,而在于理解二分的思想。

    二分查找往往用在一些抽象的场合,没有数组A,也没有要查找的v,但是二分的思想仍然适用。

    我们来一个实际的问题:现有一个由106个数组成的数组A,现在给你一个数,比如12345,让你判断它是否在数组A中。

    解法一:遍历数组A,逐个比较。

    弊端:此解法对于“单次询问”来说运行得很好,但如果需要找105个数,即进行105次询问,那么就需要把整个数组A遍历105次。

    解法二:先将数组A排序,再逐个比较。

    优点:???此刻的我还真看不出。。。

    在有序表中查找元素常常使用二分查找。

    二分查找的基础是:数组有序。

    二分查找的基本思路:每次将范围缩小一半,直到找到目标。

    举例:饭桌上常玩的“猜数字游戏”,你在心里想一个不超过1000的正整数,我可以保证在10次之内找到它——只要你每次告诉我猜的数比你想的大一些、小一些,或者正好猜中。猜的方法就是“二分”。首先我猜500,除了运气特别好正好猜中之外, 不管你说“太大”还是“太小”,我都能把可行范围缩小一半:如果“太大”,那么答案在1~499之间;如果“太小”,那么答案在501~1000之间。只要每次选择可行区间的中点去猜,每次都可以把范围缩小一半。由于log21000<10,10次一定能猜到。

    逐步缩小范围法是一种常见的思维方法。二分查找便是基于这种思路,它遵循分治三步法,把原序列划分成元素个数尽量接近的两个子序列,然后递归查找。

    二分查找只适用于有序序列,时间复杂度为O(logn)。

    二分查找一般写成非递归形式。

    代码:二分查找(迭代实现)

    int bsearch(int *A, int l, int r, int v)
    {
    	int mid;
    	while(l < r){
    		mid = l+(r-l)/2;
    		if(A[mid] == v)	return mid;
    		else if(A[mid] > v)	r = mid;
    		else	l = mid+1;
    	}
    	return -1;
    }

    二分查找往往用在一些抽象的场合,没有数组A,也没有要查找的v,但是二分的思想仍然适用。

    算法学了有些天了,若是就这么结束二分,我觉得这就不是一个算法了。

    我们在提一个问题:如果数组A中有多个元素都是v,上面的函数返回的是哪一个的下标呢?第一个?最后一个?都不是。不难看出,如果所有元素都是要找的,它返回的是中间那一个。有时,这样的结果并不是很理想,能不能求出值等于v的完整区间呢(由于已经排好序,相等的值会排在一起)?

    下面我们编写一个这样的程序:当v存在时返回它出现的第一个位置。如果不存在,返回这样一个下标 i:在此处插入v(原来的元素A[i],A[i+1],...全部往后移动一个位置)后序列仍然有序。

    int lower_bound(int *A, int l, int r, int v)
    {
    	int mid;
    	while(l < r){
    		mid = l+(r-l)/2;
    		if(A[mid] >= v)	r = mid;
    		else	l = mid+1;
    	}
    	return l;
    }
    

    最后的返回值不仅可能是l,l+1,l+2,...,r-1,还可能是r——如果v大于A[r-1](这时只能插入这里了)。这样,尽管查找区间是左闭右开区间[l,r),返回值的候选区间却是闭区间[l,r]。

    A[mid]和v的3种关系带来的影响如下:

    • A[mid] = v:至少已经找到一个,而左边可能还有,因此区间变为[l,mid];
    • A[mid] > v:所求位置不可能在后面,但有可能是mid,因此区间变为[l,mid];
    • A[mid] < v:m和前面都不可行,因此区间变为[m+1,r]。

    整理合并一下,有:

    1. A[mid] >= v:新区间为[l,mid];
    2. A[mid] < v:新区间为[mid+1,r] 。

    注意:这里有一个潜在危险,如果[l,mid]或者[mid+1,r]和原区间[l,r]相同,将发生死循环!幸运的是,这样的情况并不会发生!!!(思考原因)

    类似地,我们也可以写一个upper_bound程序,当v存在时返回它出现的最后一个位置的后面一个位置。如果不存在,返回这样一个下标 i:在此处插入v(原来的元素A[i],A[i+1],...全部往后移动一个位置)后序列仍然有序。不难得出,只需把 “if(A[mid]>=v) r=mid; else l=mid+1;” 改成 “if(A[mid]<=v) l=mid+1; else r=mid;” 即可。

    这样,对二分查找的讨论就比较完整了:设lower_bound和upper_bound的返回值分别为L和R,则v出现的子序列为[L,R) 。这个结论当v不在时也成立:此时L=R,区间为空。

    用“上下界”函数求解范围统计问题的技巧非常有用,特别要用心体会左闭右开区间的使用方法和上下界函数的实现细节。

  • 相关阅读:
    Cardiogram
    Increasing Speed Limits HDU
    Beaver Game CodeForces
    C++LeetCode:: Container With Most Water
    C++ leetcode::Reverse Integer
    C++ leetcode::ZigZag Conversion
    C++ leetcode Longest Palindromic Substring
    C++ leetcode Longest Substring Without Repeating Characters
    Faster RCNN
    C++ Leetcode Median of Two Sorted Arrays
  • 原文地址:https://www.cnblogs.com/xzxl/p/7392583.html
Copyright © 2011-2022 走看看