【二分查找】二分查找的三种实现方式以及死循环出现原因
二分的终止条件
此文章是观看BV1AP41137w7写出来的,建议没明白二分的都去看看
众所周知,二分查找是利用数据有序的特点实现的一种高效查找的方式,二分的终止条件是区别二分查找方式的重要条件。以下约定,使用蓝色表示小于x的所有数,红色表示大于等于x的所有数,数组为a[]。而l,r的最终条件和中间态则会清晰表示出来。
以下三种二分终止条件:
闭区间查询: l,r指针总是默认在闭区间查找。
我们先默认将l置于数组首位置,r置于数组尾,容易发现此时答案在区间[l,r]中 取 mid=l+(r - l)/2。//c++中有可能发生爆内存的悲剧 。
mid为6 并且a[6] = 7 < 8 那么我们先将l = mid + 1,以下会说明原因。那么此时图会变成:
还记得吗?蓝色表示小于x的区间接着更新mid = 9 并且a[mid] > 9 同样,将r设置为mid - 1那么:
我们一直进行到最终状态:
这就是最终状态,因为每次更新时L = mid + 1(a [mid] < x)那么L具有这样的性质
1,L右边的数都 < x 那么L就是第一个>= x 的数
同样R具有类似性质
1,R右边的数都 >= x
而二分查找中L,R的性质不会改变,在保证这样的条件下,闭区间[L,R]中查找会得到上图
闭区间代码:
#include<bits/stdc++.h> const int INF = 1e9 + 10; using namespace std; int main (){ ios::sync_with_stdio(false); cin.tie(0),cout.tie(0); int n; cin>>n; vector<int> a(n + 1); for(int i=1; i<=n; i++){ cin>>a[i]; } int target; cin>>target; int l = 1,r = n; //根据l,r的性质,当l == r 时,不满足l左边都 < x ,r右边都 >= x //那么l == r 时不是最终状态 while(l <= r){ int mid = l + (r - l) / 2; //在c++中int最大为1e9左右,当两个最大int数相加,会造成溢出 //故写成等价表达式mid = l + (r - l) / 2 if(a[mid] < target) l = mid + 1; else r = mid - 1; //else 表示当a[mid] >= target的情况 } //根据l,r的性质最终第一个 >= x 的数是a[l] if(a[l] == target) cout<<"Yes"<<'\n'; else cout<<"No"<<'\n'; return 0; }
左闭右开区间:
初始状态
最终状态
另外贴出左闭右开区间得最终结果图和L,R性质,
左闭右开区间代码
#include<bits/stdc++.h> const int INF = 1e9 + 10; using namespace std; int main (){ ios::sync_with_stdio(false); cin.tie(0),cout.tie(0); int n; cin>>n; vector<int> a(n + 1); for(int i=1; i<=n; i++){ cin>>a[i]; } int target; cin>>target; int l = 1,r = n + 1; //注意此时是在左闭右开区间查询[l,r) //l性质:l(不包含l)左边都 < x //r性质:r(包含r)右边都 >= x //l == r 时区间[l,r) 没有数退出 while(l < r){ int mid = l + (r - l) / 2; if(a[mid] < target) l = mid + 1; else r = mid; } //根据l,r的性质最终第一个 >= x 的数是a[l]或a[r] if(a[l] == target) cout<<"Yes"<<'\n'; else cout<<"No"<<'\n'; return 0; }
同样的开区间代码和开区间图
#include<bits/stdc++.h> const int INF = 1e9 + 10; using namespace std; int main (){ ios::sync_with_stdio(false); cin.tie(0),cout.tie(0); int n; cin>>n; vector<int> a(n + 1); for(int i=1; i<=n; i++){ cin>>a[i]; } int target; cin>>target; int l = 0,r = n + 1; //此时是在开区间查询(l,r) //l性质:l(包含l)左边都 < x //r性质:r(包含r)右边都 >= x //l == r 时区间[l,r) 没有数退出 while(l + 1 != r){ int mid = l + (r - l) / 2; if(a[mid] < target) l = mid; else r = mid; } //根据l,r的性质最终第一个 >= x 的数是a[r] if(a[r] == target) cout<<"Yes"<<'\n'; else cout<<"No"<<'\n'; return 0; }
二分会陷入死循环的原因就是由于未弄清楚l,r性质以及最终状态导致只要明白原理就没有大问题了