Binary Search(一)

本文深入探讨二分查找算法,从基本概念出发,结合实例详细解析其实现方式与应用场景,并对比不同实现方法的优劣。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

版权声明:欢迎转载,但请注明出处,若有什么不对的地方,欢迎指正,https://blog.youkuaiyun.com/wutenglong123/article/details/82746650

提起二分查找,许多人都会想:这是一个简单问题,我只用一个while或者递归就能解决,三行代码最多。再把上下限背过,就没有什么能难得住的二分查找问题了。但事实究竟是这样吗?
我们一起从头看一遍二分查找,其中穿插各个OJ的二分查找问题,同时时不时的我们会加入算法导论中对于这个问题的引申。
##什么是二分查找?
二进制搜索是计算机科学中最基本和最有用的算法之一。 它描述了在有序集合中搜索特定值的过程。我们需要对二进制搜索中的名词赋予一个简单的定义:

target - 要搜索的值
index - 搜索的当前位置
left ,right - 我们用来维持搜索空间的指标
mid- 用来应用条件来确定我们是应该向左还是向右搜索的索引
##二分查找的过程
  在二分查找的基本形式中,二分搜索在具有指定左右索引的连续序列(搜索空间)上运行。维护搜索空间的左,右和中间标记,并比较搜索目标; 如果条件不满足或者值不相等,则消除目标不可能存在的一半(有序序列),并继续搜索剩下的一半,直到成功为止。 如果搜索以空的数字集合部分结束,则无法满足条件并且未找到目标。
给定n个元素的排序(按升序排列)整数数组nums和目标值,编写一个函数来搜索nums中的目标。 如果target存在,则返回其索引,否则返回-1
Leetcode Binary Search
此代码只有 34.4% 为AC,甚至低于许多高级算法!

 class Solution {
 public:
 int search(vector<int>& nums, int target) {
     if(nums.size() == 0)return -1;
        int left =0;
        int right = nums.size()-1;
        while(left<=right){
           int mid = (left+right) >>1;
            if(nums[mid]<target)left = mid+1;
            else if(nums[mid]>target)right =mid-1;
            else return mid;
        }return -1;
    }
    };

算法的应用

由于二进制搜索是一种算法,在每次比较后将搜索空间划分为2。 所以每次需要搜索集合中的索引或元素时,都应考虑二进制搜索。 如果集合是无序的,我们可以在应用二进制搜索之前对其进行排序1

总的来说,我们可以将此部分分为:

Created with Raphaël 2.2.0 开始 若集合未排序则排序 在两部分搜索空间中确定其一 在搜索空间中查找target 结束

Markdown画图,冒号后面必须空一格,不然报错。我的天呐。对萌新一点都不友好。

我们总希望能够研究的二分查找问题越多越好,虽然我们拥有的思路相同,但每次我们查看不同大佬代码时,它的实现似乎都略有不同。 尽管每个实现在每个步骤中将问题空间划分为1/2,但其中一个有许多问题:

  1. 为什么它的实现略有不同?
  2. 大佬在想什么?
  3. 哪种方式更容易?
  4. 哪种方式更好?

###经过多次失败的尝试,和资料的阅读查找,附上几个二分查找的主要模板,并且附上几个相似的例子。

1、同上面的基本算法相同的写法,我们的谭浩强、严蔚敏老师就是这么教我们的

int binarySearch(vector<int>& nums, int target){
  if(nums.size() == 0)
    return -1;
	int left = 0;
	int right = nums.size() - 1;
  while(left <= right){
    int mid = left + (right - left) / 2;
    if(nums[mid] == target){ return mid; }
    else if(nums[mid] < target) { left = mid + 1; }
    else { right = mid - 1; }
  }
  return -1;
}
  1. 初始条件: left = 0, right = length-1(数组终点下标)
  2. 终止条件: left > right
  3. 查找左搜索区间:right = mid -1
  4. 查找右搜索区间:left = mid+1

Ex1:计算并返回x的平方根,其中x保证为非负整数。

由于返回类型是整数,因此将截断十进制数字,并仅返回结果的整数部分。

循环体中的判断条件 if(x/mid >= mid) 若为乘法if(mid*mid <=x)时会**TLE**,感兴趣的话可以查阅相关资料。

class Solution {
public:
    int mySqrt(int x) {
        if (x <2)return x;
        int left = 0;
        int right = x ;
        while(left< right){
            int mid = (right + left) /2;
            if (x/mid >= mid) left = mid+1;
            else right = mid;
        }return right-1;
        
    }           
};

当然我写完了我的代码后也会出来看神仙:

class Solution {
public:
    int mySqrt(int x) {
    long r = x;
    while (r*r > x)
        r = (r + x/r) / 2;
    return r;

啧啧啧。由于此思路与本文主体无关,但既然看到了神仙,我们刚还在说要揣摩大佬的思路,所以特放在附录里2

值得注意的是,上述方法当输入为
但是一个题目的办法多种多样,除了上面的两个,比如还有Shifting nth root algorithm3

class Solution:
    def mySqrt(self, x):
        res = 0
        bit = 1 << 30
         while bit > x:
            bit >>= 2
                while bit != 0:
            if x >= res + bit:
                x -= res + bit
                res += bit << 1               
            res >>= 1
            bit >>= 2
        return res

言归正传,我们能够看到二分查找用途广且方便计算。现在我们来分析一下二分查找的时间复杂度(讨论非递归情况,递归方式见4)。
  (原谅我递归方式 C语言版原谅我在wiki上直接粘了一个,就是这个↓)

int binary_search(const int arr[], int start, int end, int khey) {
	if (start > end)
		return -1;
	int mid = start + (end - start) / 2;    //直接平均可能會溢位,所以用此算法
	if (arr[mid] > khey)
		return binary_search(arr, start, mid - 1, khey);
	else if (arr[mid] < khey)
		return binary_search(arr, mid + 1, end, khey);
	else
	    return mid;        //最後檢測相等是因為多數搜尋狀況不是大於要不就小於
}

易得 T ( n ) = ( n / 2 ) + 1 T(n) =(n/2) +1 T(n)=(n/2)+1 由主方法知 n l o g 2 1 = 1 n^{log_21}=1 nlog21=1,由于 h ( n ) = Θ ( n l o g 2 1 ) = Θ ( 1 ) h(n)=\Theta(n^{log_21}) = \Theta(1) h(n)=Θ(nlog21)=Θ(1) 考虑第二种情况: T ( n ) = Θ ( n l o g b a l g n ) T(n)=\Theta(n^{log_ba}lgn) T(n)=Θ(nlogbalgn),则 T ( n ) = Θ ( l o g 2 n ) T(n) = \Theta(log_2n) T(n)=Θ(log2n) 5

Ex2:Guess Number Higher or Lower

我们正在玩猜数字游戏。 游戏如下:
我从1到n中选择一个数字。 你猜测我选择了哪个号码。每次你猜错了,我都会告诉你这个数字是高还是低。可调用预定义的API guess(int num),它返回3个可能的结果(-1,1或0)

class Solution {
public:
    int guessNumber(int n) {
        int left = 0;
        int right = n;
        if( n<2) return n;
        while(left<=right){
            int mid = left+(right-left)/2;//否则overflow会导致超时,其他需转换。API传入值为int。
            if (guess(mid) == 0){
                return mid;
           }else if (guess(mid)==-1){
                right = mid-1;
            }
            else  left = mid +1;
        }return -1;
    }
};

没有工作量,多调一个API而已。


  1. 有关排序的算法见(暂空)。 ↩︎

  2. 此为牛顿法:
    r r r f ( x ) = 0 f(x) = 0 f(x)=0的根,选取 x 0 x_0 x0作为 r r r初始近似值,过点 ( x 0 , f ( x 0 ) ) (x_0,f(x_0)) (x0,f(x0))做曲线 y = f ( x ) y = f(x) y=f(x)的切线 L L L L L L的方程为 y = f ( x 0 ) + f ′ ( x 0 ) ( x − x 0 ) y = f(x_0)+f&#x27;(x_0)(x-x_0) y=f(x0)+f(x0)(xx0),求出 L L L x x x轴交点的横坐标 x 1 = x 0 − f ( x 0 ) f ′ ( x 0 ) x_1 = \frac{x_0-f(x_0)}{f&#x27;(x_0)}\qquad x1=f(x0)x0f(x0),称 x 1 x_1 x1 r r r的一次近似值。
    过点 ( x 1 , f ( x 1 ) ) (x1,f(x1)) (x1,f(x1))做曲线 y = f ( x ) y = f(x) y=f(x)的切线,并求该切线与x轴交点的横坐标 x 2 = x 1 − f ( x 1 ) f ′ ( x 1 ) x_2 = \frac{x_1-f(x1)}{f&#x27;(x1)}\qquad x2=f(x1)x1f(x1),称 x 2 x_2 x2 r r r的二次近似值。重复以上过程,得 r r r的近似值序列,其中 x ( n + 1 ) = x ( n ) - f ( x ( n ) ) f ′ ( x ( n ) ) x(n+1)=x(n)-\frac{f(x(n))}{f&#x27;(x(n))}\qquad x(n+1)=x(n)f(x(n))f(x(n)), 称为 r r r n + 1 n+1 n+1次近似值,上式称为牛顿迭代公式。

    ↩︎
  3. Shifting nth root algorithm:
    除了en_wiki,好像没有太细致的中文讲解,见我的另一篇blog吧。

    ↩︎
  4. 复杂度分析见. ↩︎

  5. 有关于如何求算法的 Ω , Θ , O , ω , o \Omega,\Theta,O,\omega,o ΩΘOωo请参考另一篇blog。 ↩︎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值