二分法
介绍
定义
二分查找算法
也称折半搜索算法,对数搜索算法,是一种在有序数组中查找某一特定元素的搜索算法。
搜索从数组中间元素开始,直到中间元素为目标元素停止。
复杂度
空间复杂度:
在整个二分搜索的过程中,只需要额外存储三个变量:最大值 ,最小值 和 中点,也因此,空间复杂度是常量O(1)。
时间复杂度
时间复杂度为O(logn)。
下面是时间复杂度求法:
假设数组长度为n,则每一次数组长度都会减半,最终减为1,如下:
n , n/2 , n/4 … 1
也就是 n * 1/2 的k次方
则 n=2的k次方
k=log2 n。
而在算法中计算空间复杂度和时间复杂度时,是可以忽略掉对数的底的这些常数的,因此时间复杂度也就可以缩写成log(n)。
步骤
当我们在一个升序数组中搜索一个数,先判断该数组中间一位数(mid)与目标数(target)的大小,如果mid>target
,就说明目标数在数组中间值左边,则数组由原来的[left,right]变为[left,mid];如果mid<target
,类似的,数组变为[mid+1,right];如果mid=target
,则找到了目标数。
使用条件
1.上下界确定;
2.区间内有序。
应用
示例:猜数字(1~100)
假设这个数字是55
如果我们从1开始枚举,我们需要循环判断55次,但是使用了二分法的话:
[1~100],中间数:50<55
[51~100],中间数:75>55
[51~75],中间数:63>55
[51~63],中间数:57>55
[51~57],中间数:54<55
[55~57],中间数56>55
[55~56],中间数55=55
找到了
只进行了7次
所以二分法可以使运行更快。
二分代码
中间值middle有两种表示方法
1.middle=(left+right)/2
2.middle=lift+(right-left)/2 //防止越界
1.未封装函数
1.左闭右闭
int search(int nums[], int size, int target)
{
int left = 0;
int right = size - 1;
while (left <= right) {//当left == right时,区间[left, right]仍然有效
int middle = left + ((right - left) / 2);//等同于 (left + right) / 2,防止溢出
if (nums[middle] > target) {
right = middle - 1;
}
else if (nums[middle] < target) {
left = middle + 1;
}
else {
return middle;
}
}
return -1; // 没找到就返回-1
}
2.左闭右开
int search(int nums[], int size, int target)
{
int left = 0;
int right = size;
while (left < right){ //因为left = right的时候,在[left, right)区间上无意义
int middle = left + ((right - left) / 2);
if (nums[middle] > target){
right = middle;
}
else if (nums[middle] < target){
left = middle + 1;
}
else{
return middle;
}
}
return -1; // 没找到就返回-1
}
2.封装函数(递归法)
代码如下:
int Find(int arr[], int low, int high, int target) {
if (low <= high) {
int mid = (low + high) / 2;
if (arr[mid] == target) {
return mid;
}
else if (arr[mid] > target) {
high = mid - 1;
return Find(arr, low, high, target);
}
else
low = mid + 1;
return Find(arr, low, high, target);
}
else {
return -1;
}
}
整数域二分
1.在单调递增数列 a[ ] 中查找某个数 x,如果数列中没有 x,找比它小的前一个数
a[mid] <= x时,x 在 mid 的右边,新的搜索区间是右半部分,所以 right 不变,更新 left=mid
a[mid] > x时,x 在 mid 的左边,新的搜索区间是左半部分,所以left不变,更新 right=mid-1
当 left=right 时,得到结果
代码如下:
int Find(int sums[], int size, int target)
{
int left = 0;
int right = size;
while (left < right)
{
int mid = left + (right - left) / 2;
if (sums[mid] >= target)
right = mid;
else if (target > sums[mid])
left = mid + 1;
}
return left;
}
2.在单调递增数列 a[ ] 中查找某个数 x,如果数列中没有 x,找比它小的前一个数
a[mid] <= x时,x 在 mid 的右边,新的搜索区间是右半部分,所以 right 不变,更新 left=mid
a[mid] > x时,x 在 mid 的左边,新的搜索区间是左半部分,所以left不变,更新 right=mid-1
当left=right 时,得到结果
代码如下:
int Find(int sums[], int size, int target)
{
int left = 0;
int right = size;
while (left < right)
{
int mid = left + (right - left) / 2;
if (sums[mid] > target)
right = mid - 1;
else if (target >= sums[mid])
left = mid;
}
return left;
}
浮点数二分
浮点数二分不需要考虑边界,往往是给定一个精度范围,让你在精度范围中去找到这个数
例题:
1.先判断两边(left=i,right=i+1)是否为0;
2.判断F(left)*F(right)是否为负,若为负责中间有根
3.根据精度寻找根
代码如下:
本题用了枚举加二分
#include<stdio.h>
double a, b, c, d;
double F(double m)
{
double n = a * m * m * m + b * m * m + c * m + d;
return n;
}
int main()
{
scanf("%lf %lf %lf %lf", &a, &b, &c, &d);
double i, y1, y2, left, right, mid;
for (i = -100; i < 100; i++) {
y1 = F(i);
y2 = F(i + 1);
if(y1 * y2 <= 0){//两边异号证明中间有根,再根据精度找根
if (y1 == 0)
printf("%.2lf ", i);
else if (y2 == 0) {
printf("%.2lf ", i + 1);
i++;//少算一次,避免重复
}
else {//两边都不为零且异号,证明根在该区间内
left = i;
right = i + 1;
while (right - left > 0.001) {//精度向后一位
mid = left + (right - left) / 2.0;
if (F(left) * F(mid) <= 0)
right = mid;
else
left = mid;
}
printf("%.2lf ", mid);
}
}
}
return 0;
}
最后再分享一道我本周做的题:
因为数组可以重复,当时第一想法就是先用二分法找到目标数字(不管是不是第一个),然后再去寻找第一次出现的地方,然后结果就是超时
所以必须要一次找对地方
代码如下:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int arr[1000001] = { 0 };
int Find(int arr[], int size, int target) {
int left = 1;
int right = size;
while (left < right) {
int mid = left + (right - left) / 2;
if (target > arr[mid])//当目标值大于中间值,left=mid+1
left = mid + 1;
else if (arr[mid] >= target)//当目标值小于等于中间值,left都等于mid,这样可以使得区间一直向左边靠近,直到最后只剩一个数
right = mid;
}
if (arr[left] == target)//判断答案是不是目标值
return left;
else
return -1;
}
int main() {
int m, n;
int sum = 0;
int ans = 0;
scanf("%d %d", &n, &m);
for (int i = 1; i <= n; i++) {
//输入
scanf("%d", &arr[i]);
}
for (int i = 1; i <= m; i++) {
scanf("%d", &sum);
ans = Find(arr, n, sum);
printf("%d ", ans);
}
return 0;
}
二分法的学习分享就到这里了。
已经到底啦!!