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