朴素二分查找
思想及实现
这是学算法的入门算法,先举个最简单的例子:
猜数字1:我心中默念一个数字,范围在1~100,你每次可以猜一个数,我会回答你大了,小了,直到猜中为止。
答:比如假设要猜的数字是38,整个过程如下:
猜测范围(1,100),猜50,大了。所以比50大的都不用考虑了,包括50。
猜测范围(1,49),猜25,小了。所以比25小的也不用考虑了,以此推论。
猜测范围(26,49)猜37,小了。
猜测范围(38,49)猜44,大了。
猜测范围(38,43)猜40,大了。
猜测范围(38,39)猜38,猜中了。
我们可以把这个过程抽象成代码:
int bin_search(){
int num[100],L=0,R=100;//要猜的目标范围,左侧边界,右侧边界
int obj=10,mid; //目标值,划分点
while(L<=R){
mid = (L+R) / 2;
if(mid<obj) L = mid + 1; //小了,左侧变大
else if (mid > obj) R = mid - 1; //大了,右侧变小
else if (mid == obj) return mid; //找到了
}
return -1;//没找到
}
几个重点:
二分查找必须应用于有序数据,默认为从小打大。朴素二分查找的数据不能重复,但可以不连续。
看到这里也许有人会说,如果我的obj都提前知道了还有什么意义呢?但是有时候,我们需要在一堆数据里面找到第一个满足要求的数据,比如这道题:
实战
原题:http://oj.haizeix.com/problem/386
吃瓜问题:有M堆瓜和N个群众,需要尽量满足每位群众吃瓜需求,但是为了避免浪费,分配的瓜不能多也不能少,如果没能找到就不分配。
输入:
输入共 3 行。
第一行两个整数 M,N。
第二行 M 个整数分别表示 M堆瓜的数量。(保证各不相同)
第三行 N 个整数分别表示 N个群众想吃的数量。(保证各不相同)
输出:
对于每个 Yi 输出一行一个整数为对应数量的瓜的编号,若没有对应数量的瓜,则输出 0。
输入样例:
5 3
1 3 26 7 15
26 99 3
输出样例:
3
0
2
需要注意的是,每堆瓜我们不仅需要保存瓜的数量,也需要保存他的编号,因此可以用结构体来存储。
排序的时候也对整个结构体进行排序。
这道题可以分为3个步骤:
接收数据,排序,二分查找。直接上代码:
#include <iostream>
#include <string>
#include <algorithm>
#include <cstdio>
using namespace std;
typedef struct node {
int num; //编号
int val; //瓜数量
} ;
int M, N;
node wm[100005];
//比较函数
bool cmp(const node &a, const node &b){
return a.val < b.val;
}
//注意我们要比较瓜的数量,返回瓜的编号
int bin_search(int obj, int L, int R) {
while (L <= R) {
int mid = (L + R) / 2;
if (wm[mid].val < obj) L = mid + 1;
else if (wm[mid].val > obj) R = mid - 1;
else if (wm[mid].val == obj) return wm[mid].num;
}
return 0;
}
int main() {
cin >> M >> N; //M堆瓜,N个人
for (int i = 1; i <= M; i++){
cin >> wm[i].val;
wm[i].num = i;
}
sort(wm + 1, wm + M + 1, cmp);
int obj,L = 1, R = M;
for (int i = 0; i < N; i++){
cin >> obj;
cout << bin_search(obj, L, R) << endl;
}
return 0;
}
特殊二分查找之左边界问题
思路及实现
还是上面得吃瓜问题,如果此时,我要求每个人分配的西瓜数量要大于等于他所需要的数量,那应该如何是好?
其实此类问题可以抽象为以下问题:
在000000111111中寻找第一个1。 0表示不满足条件的值,1表示满足条件的值。比如说我要吃20个西瓜,那你给我21个或者22个都是满足条件,但是为了节约,应该找出最小的。也就是21的编号。如果找不出来就返回0.
直接上题目吧:
原题:http://oj.haizeix.com/problem/387
吃瓜问题升级版:有M堆瓜和N个群众,需要尽量满足每位群众吃瓜需求,但是为了避免浪费,分配的应该是刚好大于等于需要的瓜的第一堆,如果所有数量都不能满足就不分配。
输入:
输入共 3 行。
第一行两个整数 M,N。
第二行 M 个整数分别表示 M堆瓜的数量。(保证各不相同)
第三行 N 个整数分别表示 N个群众想吃的数量。(保证各不相同)
输出
对于每个 Yi 输出一行一个整数为大于等于需要数量的第一堆瓜的编号,若所有瓜堆的数量均小于需要数量,则输出 0。
输入样例:
5 5
1 3 26 7 15
27 10 3 4 2
输出样例:
0
5
2
4
2
有了上题的基础应该很好理解这题,需要注意的是,此题中,要求找不到返回0,因此我们可以在N中增加一堆瓜,数值设为超级大,序号设为0。这样其它条件都不满足,就一定会找到这堆瓜,返回的编号自然也就是0了。可以减少代码量。直接上代码:
#include <iostream>
#include <string>
#include <algorithm>
#include <cstdio>
using namespace std;
typedef struct node {
int num; //编号
int val; //瓜数量
} ;
int M, N;
node wm[100005];
bool cmp(const node &a, const node &b){
return a.val < b.val;
}
int binLeft_search(int obj, int L, int R) {
while (L != R) {
int mid = (L + R) / 2;
//mid不满足条件
if (wm[mid].val < obj) L = mid + 1 ;
//mid满足条件,mid也可能是最终解,因此需要保留
else if (wm[mid].val >= obj) R = mid ;
}
//此时L=R
return wm[L].num;
}
int main() {
cin >> M >> N; //M堆瓜,N个人
for (int i = 1; i <= M; i++){
scanf("%d",&wm[i].val);
//cin >> wm[i].val;
wm[i].num = i;
}
wm[0].num=0; //增加一堆超多的瓜
wm[0].val=2100000000;
sort(wm , wm + M + 1, cmp);
int obj,L = 0, R = M;
for (int i = 0; i < N; i++){
scanf("%d",&obj);
cout << binLeft_search(obj, L, R) << endl;
}
return 0;
}
小试牛刀:
https://leetcode-cn.com/problems/first-bad-version/submissions/
特殊二分查找之右边界问题
右边界问题:11110000问题。见下方总结
关于左右边界的总结:
00001111问题(找左侧边界):
若二分找到的是0,说明此时要找的答案在右侧,所以左侧增大,L = mid + 1
若二分找到的是1,说明此时找到的答案在左侧或者自身,所以右侧减少,但是需要保护mid,R = mid.
1111000(找右侧问题):
若二分找到的是0,说明此时要找的答案在左侧,所以右侧减少,R = mid - 1
若二分找到的是1,说明此时找到的答案在右侧或者自身,所以左侧增大,但是需要保护midL = mid.
还但有一点不同:mid=(L+R+1)/2; 为什么要+1呢,这是为了mid的位置向右偏,否则可能会导致死循环:
若 L 指针指向 1 ,R 指向 0 时,若按照原来的公式mid = (L+R) /2,mid还是L,没有变化,因此会死循环。若+1,则此时mid 指向0,便可以收缩R。
记忆口诀:
朴素二分:
循环条件:左右可相等: L<=R
划分条件:取其正中间:(L+R)/ 2
左侧收缩:若小则升左: <,L=mid+1
右侧收缩:若大则降右: >,R=mid-1
终止条件:相等则退出: =,return mid
while(L<=N){
mid=(L+R)/2;
if(N[mid]<t) L=mid + 1 ;
else if(N[mid]<t) R=mid -1;
else return mid;
}
右侧二分:00001111问题
循环条件:左右不相等 L != R
划分条件:取其正中间:(L+R)/ 2
先左收缩:若0则升左: <,L=mid+1
其他情况:右侧需保护。 R=mid
while(L!=R){
mid = (L+R) / 2;
if (N[mid] < t) L = mid + 1; //找右,升左
else R=mid; //保护右侧
}
左侧二分:111110000问题
循环条件:左右不相等 L != R
划分条件:加一再除二:(L+R+1)/ 2
先右收缩:若0则降右: >,R=mid-1
其他情况:左侧需保护。 L=mid
while(L!=R){
mid=(L+R+1)/2; //防止死循环
if(N[mid] < t) R = mid - 1; //小则降右
else L=mid; //保护左侧
}

本文介绍了二分查找算法在解决吃瓜问题中的应用,包括基本的朴素二分查找、寻找满足条件的第一个瓜堆的左边界二分查找,以及针对右边界问题的二分查找策略。通过实例解析算法思想,并给出代码实现,帮助读者深入理解二分查找的变种及其在实际问题中的应用。
835

被折叠的 条评论
为什么被折叠?



