八、二分查找
二分查找常用与一个有序序列或一个具有单调性问题中。
8.1 编程思路:
- 设有一数组 \(a[n]\) ,数组中的元素按值从小到大排列有序。用变量 \(low, high\) 和 \(mid\) 分别指示待查元素所在区间的下界、上界和中间位置。初始时,\(low=0,high=n-1\) ,查找 \(x\) 是否在序列中。
- 令 \(mid = (low+ high) /2\) 。(建议写成 \(mid = low+(high-low)/2\),可以避免数过大越界)
- 比较给定值 \(x\) 与 \(a[mid]\) 值的大小:
- 若 \(a[mid] == x\) ,则查找成功,结束查找;
- 若 \(a[mid]> x\) ,则表明给定值 \(x\) 只可能在区间 \(low \sim mid-1\) 内,修改检索范围。令 \(high=mid-1\),\(low\) 值保持不变;
- 若 \(a[mid]< x\) ,则表明给定值 \(x\) 只可能在区间 \(mid+1\sim high\) 内,修改检索范围。令 \(low=mid+1,high\) 值保持不变。
- 比较当前变量 \(low\) 和 \(high\) 的值,若 \(low≤high\) ,重复执行第1、2两步,若 \(low>high\) ,表明数组中不存在待查找的元素,查找失败。
- eg : a[]={10,14,21,38,45,47,53,81,87,99},查找47
8.2 代码实现:
int binarysearch(int a[],int num,int x){
int left=0,right=num-1;//查找区间[left,right]
while(left<=right){//跳出循环时left=right+1
int mid = left+(right-left)/2;//避免(left+right)溢出
if(a[mid] == x)return mid;
if(a[mid] < x)
left = mid + 1;
else
right = mid - 1;
}
return -1;//执行到这一步说明个序列中不存在x
}
- 上面算法在查找一个数是否存在很高效,但存在局限性。
- 比如说给你有序数组\(a[]={1,2,2,2,3}, x=2\),此算法返回的索引是 \(2\),没错。但是如果我想得到 \(x\) 的左侧边界,即索引 \(1\),或者我想得到 \(x\) 的右侧边界,即索引 \(3\) ,那我们该如何处理。
8.2.1 左侧边界代码实现
int binarysearch(int a[],int num,int x){//查找数组a,[0,num-1]中比x小的个数
int left=0,right=num;//查找区间[left,right)
while(left<right){//跳出循环时left=right
int mid = left+(right-left)/2;//避免(left+right)溢出
if(a[mid] >= x) //求左边界我们尽量遇到==x的时候右边界往左移
right = mid;//注意是左闭右开区间右区间缩小不需要减一
else
left = mid + 1;//新的区间不包含a[mid]
}
return left;//跳出循环时left指向第一个x的位置
}
8.2.2 右侧边界代码实现
int binarysearch(int a[],int l,int r,int x){//查找数组a,[l,r]中元素x的位置
int left=0,right=num;//查找区间[left,right)
while(left<right){//跳出循环时left=right
int mid = left+(right-left)/2;//避免(left+right)溢出
if(a[mid] <= x) //求右边界我们尽量遇到==x的时候左边界往右移
left = mid+1;//注意是左闭右开区间
else
right = mid;//新的区间不包含a[mid]
}
return left-1;//注意left要减一,跳出循环时left指向最右x的下一个位置
}
hzoi