DAY1 二分法及其应用

1.二分法定义:      

        二分法是一种常用的搜索算法,也被称为二分搜索或折半搜索。该算法通过将目标值与数组中间元素进行比较,从而确定目标值在数组的左半部分还是右半部分,然后在相应的子数组中继续搜索目标值。这个过程持续递归地执行,直到找到目标值或者确定目标值不存在。

        二分法的时间复杂度为 O(log n),效率很高,适用于有序数组或有序列表的查找。这种算法在搜索范围很大的情况下特别有用,因为它可以迅速排除大部分数据,缩小搜索范围。

2.利用二分法查找一个有序数组里的数字:

        正常来说,我们如果想要查找一个数字是否存在于一个有序数组中,那么我们会遍历数组,一个个比较,这样的时间复杂度就是O(N)

        而利用二分法,即每一次把数组长度缩小一半,然后进行比对,这样无疑能够节省一定的时间,这样的时间复杂度是O(logN)。

        那么如何去实现这样的查找流程?举个例子,现在有一个有序数组 ,含有7个元素,分别为

1 3 5 7 8 9 11,现在我们要去查找6的位置(这个数字并不存在)。

流程如下:

        (1)第一轮,此时最左侧元素,记作left,最右侧元素,记作right,中间元素,记作middle,此时left=0,right=6,middle=(left+right)/2=3,即中间元素指向第项(数组从第0项开始),arr[middle]=7,判断一下,此时中间元素大于目标值,所以应当收缩数组至左半侧,此时就可以使用right来限制右侧边界,同理,使用left限制左侧边界。此时让right=middle-1(middle自身不是目标,边界可以往内收缩一个元素)

        (2)第二轮,此时left=0,right=3-1=2,middle=(left+right)/2=1,中间元素指向第项,arr[middle]=3,判断一下,此时中间元素小于目标值,则应当收缩数组至当下的右半侧,此时令left=middle+1,限制左侧边界。

        (3)第三轮,此时left=right=middle=2,再次判断,此时指向的元素仍然不是目标值,而且不能让左侧边界大于右侧边界,所以说明目标值并不存在。

分析:

        在这个流程之中,可以发现,我们终止二分的条件就是left<=right是否成立,而且每次的循环都需要有数值的更新以防止死循环,即left=middle+1,right=middle-1注意,不要写成left=middle,right=middle,否则可能发生死循环,比如数组 1 3 5 7 9中查找6,当模拟到最后的时候,left=middle=right=3,此时也满足循环条件,则left,right的值无法更新,循环将一直走下去)

代码如下:

#include<stdio.h>
#define N 10
int main(void)
{
	int arr[N],i,target;
	printf("Insert the target:\n");
	scanf("%d",&target);
	printf("Insert the array:\n");//数组是有序的!!!
	for(i=0;i<N;i++)
		scanf("%d",&arr[i]);
	int left,right,middle,find;//find用于判断是否找到目标,以择时跳出循环
	left=0;right=N-1;find=0;//初始化
	if(arr[0]==target)//首位
		printf("Success and Location 1");
	else if(arr[N-1]==target)//末位
		printf("Success and Location N");
	else //中间项
		while(left<=right)//终止条件left>right
		{
			middle=(left+right)/2;
			if(arr[middle]==target)//相同即跳出循环
				{
					find=1;
					printf("Success and Location is %d",middle+1);//+1才是人为理解的位置
					break;
				}
			else if(arr[middle]>target)//target在左侧
					right=middle-1;//右边界左移动一位 
			else if(arr[middle]<target)//target在右侧
					left=middle+1;//左边界右移动一位 
		}
	if(!find)
		printf("Target %d Not Found",target);
	return 0;
} 

3.利用二分法寻找一个有序数组中大于等于某个数字最左侧的位置

        有序数组中的查找问题,二分法无疑是非常好用的,因为它能够节约一定的时间。这个题目与上面的是相似的,但是这个题目需要找到最左侧的位置,也就是说明,即使我们找到了符合要求的数字,也要继续进行下去,直至二分结束。

        那么如何去实现这样的查找流程?举个例子,现在有一个有序数组 ,含有7个元素,分别为

1 3 6 6 6 9 11,现在我们要去查找6的位置。

        (1)第一轮,此时最左侧元素,记作left,最右侧元素,记作right,中间元素,记作middle,此时left=0,right=6,middle=(left+right)/2=3,即中间元素指向第项(数组从第0项开始),arr[middle]=6,判断一下,此时中间元素大于等于目标值成立,因为要找最左侧的数,所以应当收缩数组至左半侧,此时就可以使用right来限制右侧边界。此时让right=middle-1(middle自身不是最左侧的值,边界可以往内收缩一个元素)

        (2)第二轮,此时left=0,right=3-1=2,middle=(left+right)/2=1,中间元素指向第项,arr[middle]=3,判断一下,此时中间元素小于目标值,则应当收缩数组至当下的右半侧,此时令left=middle+1,限制左侧边界。

        (3)第三轮,此时left=right=middle=2,再次判断,此时指向的元素大于等于目标值,而且不能让左侧边界大于右侧边界,所以此时的位置就是目标点位置,即为第三位。

分析:

        在这个流程之中,可以发现,我们终止二分的条件就是left<=right是否成立,而且每次的循环都需要有数值的更新以防止死循环,即left=middle+1,right=middle-1注意,不要写成left=middle,right=middle,否则可能发生死循环,比如数组 1 3 6 7 9中查找6,当模拟到最后的时候,left=1,middle=1,right=2,此时也满足循环条件,则left,right的值无法更新,循环将一直走下去)当然,对于特殊情况的判断也是有用的,如果第一位就符合目标那么就不需要继续下去了,此时输出left+1,所以由特殊到一般,最终结果我们一律输出为left+1。

代码如下:

        

//已知一个有序数组,给定一个数组(有序),
//输入一个数字,寻找大于等于这个数字最左侧的位置(二分法)
#include<stdio.h>
#define N 10
int main(void)
{
	int target;
	int arr[N];
	int i,j,k;
	int left,right,middle;
	
	printf("Insert the target:\n");
	scanf("%d",&target);
	printf("Insert the arr:\n");
	for(i=0;i<N;i++)
		scanf("%d",&arr[i]);
	left=0;right=N-1;
	if(arr[0]>=target)
			printf("%d located in %d",target,left+1);//left+1 即为目标 
	else
		{
		while(left<=right)//执行至循环结束 ,无break语句
			{
				middle=(left+right)/2;
				if(arr[middle]>=target)//target left
					right=middle-1;
				else if(arr[middle]<target)//target right
					left=middle+1;
			}
		printf("%d located in %d",target,middle+1);//left+1 即为目标 
		}
	return 0;
 }

4.利用二分法查找无序数组之中的局部最小值

        谁说二分法只能针对于有序数组,您瞧瞧,咱们也可以利用二分法查找无序数组之中的局部最小值,简单解释一下局部最小值,局部最小值可以满足三个条件之一: 1.第一个元素小于第二个 2.最后一个元素小于倒数第一个元素 3.中间的元素小于两边的元素(前两个条件如果满足任意一个,后续就不用管了,直接结束程序)

        那也就是说,在1 2条件均不满足时,才会考虑第3个条件,那此时整体的数据是这样的。

即数组的大小图像呈现这样向内凹的趋势,那么一定可以知道的是,这个数组中,一定存在最小值,我们仿照二分法的思路,仍然取left,right,middle,只要保证arr[middle]小于两侧的数,即可结束程序,如若小于右侧的arr[middle+1],则此时可以发现,从0~middle上的图像整体也是向内凹的,那么继续二分直至二分结束,则middle两侧一定大于中间,即仍呈现向内凹的局势,此时我们就找到了这个局部最小值,不是吗?

举个小例子:

        一个数组 3 1 2 3 4 5

        第一轮,left=0,right=4,middle=(left+right)/2=2,此时middle对应的值就是第个元素arr[2]=2,此时arr[middle]<arr[middle+1],所以说明目标值在数组左半侧,收缩右边界 right=middle-1。

        第二轮,left=0,right=2-1=1,middle=0,又arr[middle]>arr[middle+1],所以此时目标值在此时数组的右半侧,收缩左边界 left=middle+1。

        第三轮,此时left=1,right=1,middle=1,此时arr[middle]=1小于左右两侧的数字,所以此时arr[middle]=1就是局部最小值,程序结束。

程序如下:

#include<stdio.h>
#define N 10
int main(void)
{
	int arr[N];
	int i,j,k;
	printf("Insert The Arr:\n");
	for(i=0;i<N;i++)
		scanf("%d",&arr[i]);
	int left,right,middle;
	left=0;right=N-1;
	if(arr[0]<arr[1])//第一位最小 
		printf("The min is %d, located %d",arr[0],0+1);
	else if(arr[N-1]<arr[N-2])//最后一位最小 
		printf("The min is %d, located %d",arr[N-1],N);
	else
		{
			while(left<=right)
				{
					middle=(right+left)/2;
					printf("M=%d R=%d L=%d\n",middle,right,left);
					if(arr[middle]>=arr[middle+1])//right 0~middle+1
							left=middle+1;
					else if(arr[middle+1]>=arr[middle])//left middle~N-1
							right=middle-1;
					else
						{
							printf("The min is %d, located %d",arr[middle],middle+1);
							break;
						}
				}
		}
	return 0;
 }

        

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值