【基础算法】关于二分查找你不知道的事

一般来说,二分查找在学校里都会教过。但是写一个bug free的二分查找真的那么容易么,曾经有人统计过 就算是CMU的博士生写出来的二分查找也大多存在着各种各样的问题。

1. 简单的二分查找竟然有这么多坑!

// 最简单的二分查找
int binarySearch(vector<int>& nums, int target)
{
	int l = 0, r = nums.size()-1;  // 1. 坑1
	while(l<=r){
		int mid = l + (r-l)/2;   // 2.  坑2
		if(nums[mid] == target){
			return mid;
		}else if(nums[mid] < target){
			l = mid + 1;
		}else if(nums[mid > target){
			r = mid - 1;
		}
	}
	return r;              // 3. 坑3
}

上面一段简单的二分查找存在着3个坑,
坑1: 最右侧 r 是 nums.size() 和 nums.size()-1, 傻傻分不清。这涉及到二分的一些寻址规则,到底是前闭后闭,还是前闭后开。

坑2: m i d = l + ( r − l ) / 2 mid = l + (r-l)/2 mid=l+(rl)/2 还是 m i d = ( l + r ) / 2 mid = (l +r)/2 mid=(l+r)/2。注意一些标准库也是直接用 m i d = ( l + r ) / 2 mid = (l +r)/2 mid=(l+r)/2, 这是非常错误的写法。当数据量特别大的时候, (l +r) 如果会出现溢出。举个例子 l,r 如果声明是整型, 那么 l+r 很有可能超过 int的最大值,从而出现溢出。

**坑3:**返回到底是l 和 r, 一般来说,对于数组没有重复值的时候,返回哪个都没关系。但是如果有重复值的时候,会涉及到返回target第一次出现的坐标,或者target最后一次出现的坐标。

2. 关于数组中出现重复数值的二分查找

C++标准库中提供了 lower_bound 和 upper_bound 的写法

  • lower bound是指找到 >=目标数的第一个数
  • upper bound是指找到 > 目标数的第一个数

2.1 前闭后开的写法 [low, high)

这种写法也是图灵将获得者Dijsktra老前辈所推崇的写法
下面提供标准的写法

// lower bound + 前闭后开
// 找到第一次出现目标数的位置,否则返回end
int lower_bound(vector<int>&nums, int target){
	int low = 0, high = nums.size();
	while(low < high){
	    int mid = low + (high-low) / 2;
	    if(nums[mid] >= target){
	        high = mid;
	    }else{
	        low = mid+1;
	    }
	}
	return low;
}
// upper bound + 前闭后开
// 找到第一次大于目标数的位置,否则返回end
int upper_bound(vector<int>&nums, int target){
	low = 0, high = nums.size();
	while(low < high){
	    int mid = low + (high-low) / 2;
	    if(nums[mid] > target){
	        high = mid;
	    }else{
	        low = mid+1;
	    }
	}
	return high;
}

注意: 上面写法是前闭后开 [low, high) 的写法。
有几个点需要注意:

  • high的初始化赋值是从 开区间 开始。
  • ±1的位置只出现一次: 只更新low = mid+1,更新high = mid
  • 最重要的一点,上述写法最终结束的时候,low=high, 无需纠结返回哪个
  • upper_bound终止条件是num[mid] > target的时候,因此返回的是第一个大于target的位置。

2.2 前闭后闭的写法 [low, high]

// lower bound + 前闭后闭
// 找到第一次出现目标数的位置,否则返回end
int lower_bound(vector<int>&nums, int target){
	int low = 0, high = nums.size()-1;
	while(low <= high){
	     int mid = low + (high-low) / 2;
	     if(nums[mid] >= target){
	         high = mid-1;
	     }else{
	         low = mid+1;
	     }
	 }
	return low;
}
// upper bound + 前闭后闭
// 找到第一次大于目标数的位置,否则返回end
int upper_bound(vector<int>&nums, int target){
	int low = 0, high = nums.size()-1;
	while(low <= high){
	     int mid = low + (high-low) / 2;
	     if(nums[mid] > target){
	         high = mid-1;
	     }else{
	         low = mid+1;
	     }
	 }
	return low;
}

这种写法则需要注意返回的是low和high

  • 观察while终止条件,low在大于high的时候进行终止,因此最终low = high+1
  • 观察判断时候的等号,等号是 查找到target的最后进入条件,但是进入后,low和high会进行更新。

2.3STL 源码中的lower bound写法

  template<typename _ForwardIterator, typename _Tp, typename _Compare>
    _ForwardIterator
    __lower_bound(_ForwardIterator __first, _ForwardIterator __last,
		  const _Tp& __val, _Compare __comp)
    {
      typedef typename iterator_traits<_ForwardIterator>::difference_type
	_DistanceType;

      _DistanceType __len = std::distance(__first, __last);

      while (__len > 0)
	{
	  _DistanceType __half = __len >> 1;
	  _ForwardIterator __middle = __first;
	  std::advance(__middle, __half);
	  if (__comp(__middle, __val))
	    {
	      __first = __middle;
	      ++__first;
	      __len = __len - __half - 1;
	    }
	  else
	    __len = __half;
	}
      return __first;
    }

这个看起来就比较复杂了。
哈哈,这就跟写茴香豆的茴字一样,掌握适合自己的写法才是最好的。
不过本质上这种写法也是前闭后开式写法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值