二分法学习分享

二分法

介绍

定义

二分查找算法也称折半搜索算法,对数搜索算法,是一种在有序数组中查找某一特定元素的搜索算法。

​ 搜索从数组中间元素开始,直到中间元素为目标元素停止。

复杂度

空间复杂度:

​ 在整个二分搜索的过程中,只需要额外存储三个变量:最大值 ,最小值 和 中点,也因此,空间复杂度是常量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;
}

浮点数二分

浮点数二分不需要考虑边界,往往是给定一个精度范围,让你在精度范围中去找到这个数

例题:

P1024 [NOIP2001 提高组] 一元三次方程求解

在这里插入图片描述

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;
}

最后再分享一道我本周做的题:

P2249 【深基13.例1】查找

在这里插入图片描述

因为数组可以重复,当时第一想法就是先用二分法找到目标数字(不管是不是第一个),然后再去寻找第一次出现的地方,然后结果就是超时

所以必须要一次找对地方

代码如下:

#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;
}

二分法的学习分享就到这里了。

已经到底啦!!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值