二次检索算法
① 二分搜索算法
#include <stdio.h>
#include <stdlib.h>
int a[100]={1,2,3,5,12,12,12,15,29,55};//数组中的数(由小到大)
int k;
void found(int &x,int &y,int k) //在x与y之间,要找k
{
if(x>y)return;
int m=x+(y-x)/2;
if(a[m]==k)x=y=m;
else if(a[m]>k)found(x,y=m-1,k);//找左边
else found(x=m+1,y,k);//找右边
}
int main()
{ int i=0,j=9;
scanf("%d",&k);//输入要找的数字k
found(i,j,k);//从数组a[0]到a[9]中找k
if(i==j)printf("a[%d]==%d ",i,k);
else printf("a[%d]==%d a[%d]==%d, k==%d ",j,a[j],i,a[i],k);
return 0;
}
② 二分查找算法实现(图解)与实例
当我们要从一个序列中查找一个元素的时候,二分查找是一种非常快速的查找算法,二分查找又叫折半查找。
它对要查找的序列有两个要求,一是该序列必须是有序的(即该序列中的所有元素都是按照大小关系排好序的,升序和降序都可以,本文假设是升序排列的),二是该序列必须是顺序存储的。
如果一个序列是无序的或者是链表,那么该序列就不芹燃滑能进行二分查找。之所以被查找的序列要满足这样的条件,是由二分查找算法的原理决定的。
二分查找又称折半查找,优点是比较次数少,查找速度快,平均性能好;其缺点是要求待查表为有序表,且插入删除困难。因此,折半查找方法适用于不经常变动而查找频繁的有序列表。
二分查找能应用于任何类型的数据,只要能将这些数据按照某种规则进行排序。然而,正因为它依赖于一个有序的集合,这使得它在处理那些频繁插入和删除操作的数据集时不太高效。这是因为,对于插入和操作来说,为了保证查找过程正常进行,必须保证数据集始终有序。相对于查找来说,维护一个有序数据集的代价更高。此外,元素必须存储在连续的空间中。因此,当待搜索的集合是相对静态的数据集时,此时使用二分查找段棚是最好的选择。
二分查找算法的原理如下:
二分查找之所以快速,是因为它在匹配不成功的时候,每次都能排除剩余元素中一半嫌腊的元素。因此可能包含目标元素的有效范围就收缩得很快,而不像顺序查找那样,每次仅能排除一个元素。
二分查找法实质上是不断地将有序数据集进行对半分割,并检查每个分区的中间元素。
此实现过程的实施是通过变量left和right控制一个循环来查找元素(其中left和right是正在查找的数据集的两个边界值)。
二分查找的时间复杂度取决于查找过程中分区数可能的最大值。对于一个有n个元素的数据集来说,最多可以进行O(㏒₂n)次分区。对于二分查找,这表示最终可能在最坏的情况下执行的检查的次数:例如,在没有找到目标时。所以二分查找的时间复杂度为O(㏒₂n)。
参考:
https://www.html.cn/qa/other/23018.html
https://www.cnblogs.com/idreamo/p/9000762.html
③ Task 04:数组二分查找
第8-10天打卡,附上 学习链接
二分查找算法(Binary Search Algorithm),又称为折半查找、对数查找算法,是一种在有序数组中查找某一特定元素的搜索算法。
基本思想:先确定待查找元素所在的区间范围,再逐步缩小范围,直到找到或找不到该元素为止。
0704 二分查找 *:给定一个升序的数组nums和一个目标值target,返回target在数组中的位置,如果找不到,则返回-1。
样例1:输入为nums=[-1, 0, 3, 5, 9, 12],target=9,输出为4;
样例2:输入为nums=[-1, 0, 3, 5, 9, 12],target=2,输出为-1。
思路1:直接法。一旦在循环体中找到该需查找的元素,就直接返回结果。
该种思路适合解决简单题目,即查找的元素性质简单,数组中都是非重复元素,且等于不等于的情况易于比较。
思路2:排除法。在循环体中排除目标元素一定不存在的区间。
该思路适合于解决复杂题目,如查找一个数组里可能不存在的元素,找边界问题可使用该思路。
题目描述:每轮游戏,会从1到n随机选择一个数字;如果猜错了,会告诉是大了还是小了。可以通过调用一个预先定义好的接口int guess(int num)来获取猜测结果,返回值一共有3种可能的情况(-1,1或0),-1表示选出的数字比猜的数字小,即pick<num;1表示选出的数字比猜的数字大,即pick>num;0表示猜对了,即pick==num。返回我选出的数字。
样例1:输入为n=10,pick=6,输出为6;
样例2:输入为n=1, pick=1,输出为1;
样例3:输入为n=2,pick=1,输出为1;
样例4:输入为n=2,pick=2,输出为2。
解题思路:二分法。
题目描述:给定一个数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在数组中,返回它将会被按顺序插入的位置。
样例1:输入为nums=[1, 3, 5, 6],target=5,输出为2;
样例2:输入为nums=[1, 3, 5, 6],target=2,输出为1;
样例3:输入为nums=[1, 3, 5, 6],target=7,输出为4;
样例4:输入为nums=[1, 3, 5, 6],target=0,输出为0;
样例5:输入为nums=[1],target=0,输出为0。
解题思路:在一个有序数组中找到第一个大于等于target的下标。
题目描述:给一个非负整数x,计算并返回x的算术平方根。由于返回类型是整数,结果只保留整数部分,小数部分将被舍去。不允许使用任何内置指数函数和算符。 样例1:输入为x=4,输出为2; 样例2:输入为x=8,输出为2。解释:8的算术平方根是2.82842...,由于返回类型是整数,小数部分将被舍去。
解题思路:转化为寻找满足k 2 <=x的最大k值。二分查找,下界为0,上界粗略设定为x。每一步,通过比较中间元素mid的平方与x的大小关系,不断调整上下界的范围。
时间复杂度:O(log(x)); 空间复杂度:O(1)。
题目描述:给一个已按照 非递减顺序 排列的整数数组numbers,从数组中找出两个数满足相加之和等于目标数target。函数应该以长度为2的整数数组的形式返回这两个数的下标值。numbers的下标从1开始计数,所以答案数组应当满足1<=answer[0]<answer[1]<=numbers.length。可假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。 样例1:输入为numbers=[2, 7, 11, 15],target=9,输出为[2, 7]; 样例2:输入为numbers=[2, 3, 4],target=6,输出为[1, 3]; 样例3:输入为numbers=[-1, 0],target=-1,输出为[1, 2]。
解题思路:固定第一个数,在其右侧寻找第二个数,使得第二个数等于目标值减去第一个数。(之前有做过一次)。
时间复杂度:O(nlog(n)); 空间复杂度:O(1)。
题目描述:传送带上的包裹必须在D天内从一个港口运送到另一个港口。传送带上的第i个包裹的重量为weights[i]。每一天,都会按照重量的顺序往传送带上装载包裹。装载的重量不会超过船的最大运载重量。返回能在D天内将传送带上的所有包裹送达的船的最低运载能力。 样例1:输入为weights=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10],D=5,输出为15;解释:第1天是1,2,3,4,5;第2天是6,7;第3天是8;第4天是9;第5天是10,所以最低载重为15。 样例2:输入为weights=[3, 2, 2, 4, 1, 4],D=3,输出为6;解释:第1天是3,2;第2天是2,4;第3天是1,4;所以最低载重为6。 样例3:输入为weights=[1, 2, 3, 1, 1],D=4,输出为3。解释:第1天是1;第2天是2;第3天是3;第4天是1,1;所以最低载重是3。
解题思路: 二分查找转化为判定问题 。存在一个运载能力的下限x ans ,使得x>=x ans 时,可以在days天运送完,反之无法运送完所有包裹。因为有顺序运载,连续安排在同一天进行运送。当这批包裹的重量大于运载能力x时,就需要将最后一个包裹拿出来,安排在新的一天,并继续往下遍历。当遍历完整个数组后,就得到了最少需要运送的天数。使用 最少需要运送的天数 与days进行比较,小于等于days时,忽略二分的右半部分区间;当其大于days时,就忽略二分的左半部分区间。
时间复杂度:O(nlog(sum(w))); 空间复杂度:O(1)。
题目描述:每个版本的产品都是基于之前的版本开发,因此错误的版本之后的所有版本都是错误的。假设有n个版本[1, 2, ..., n],找出导致之后版本出错的第一个错误版本。可以调用bool isBadVersion(version)接口来判断版本号version是否在单元测试中出错。实现一个函数来查找第一个错误的版本。应该尽量减少对调用API的次数。 样例1:输入为n=5, bad=4,输出为4;解释:调用isBadVerison(3)->false;调用(4)->true;调用(5)->true。所以4是第一个错误的版本。 样子2:输入为n=1,bad=1,输出为1。
解题思路:左右初始化为1和n,从中间开始二分查找。
时间复杂度:O(log(n)); 空间复杂度:O(1)。
题目描述:整数数组nums按升序排列,数组中的值互不相同。在传递给函数之前,nums在预先未知的某个下标k(0<=k<nums.length)上进行了旋转,使数组变为[nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]](下标从0开始计数)。例如,[0, 1, 2, 4, 5, 6, 7]在下标3处经旋转后可能变为[4, 5, 6, 7, 0, 1, 2]。给出旋转后的数组nums和一个整数target,如果nums中存在这个目标值target,则返回它的下标,否则返回1。 样例1:输入为nums=[4, 5, 6, 7, 0, 1, 2],target=0,输出4; 样例2:输入为nums=[4, 5, 6, 7, 0, 1, 2],target=3,输出为-1; 样例3:输入为nums=[1],target=0,输出为-1。
解题思路:二分查找适用于有序数组。将数组从中间分开,肯定有一部分是有序的。首先mid分割,查看[left, mid]和[mid+1, right]哪个部分是有序的,根据有序的部分改变二分查找的上下界,根据有序的部分判断target在不在这个部分。
时间复杂度:O(log(n)); 空间复杂度:O(1)。
题目描述:已知一个长度为n的数组,预先按照 升序 排列,经由1到n次旋转后,得到输入数组。例如,原数组nums=[0, 1, 2, 4, 5, 6, 7]在变化后可能得到:若旋转4次,则可以得到[4, 5, 6, 7, 0, 1, 2];若旋转7次,则可以得到[0, 1, 2, 4, 5, 6, 7]。注意:数组[a[0], a[1], a[2], ..., a[n-1]]旋转一次的结果为数组[a[n-1], a[0], a[1], a[2], ..., a[n-2]]。给出一个元素值互不相同的数组nums,它原来是一个升序排列的数组,并进行了上述的多次旋转。找出并返回数组中的最小元素。 样例1:输入为nums=[3, 4, 5, 1, 2],输出为1; 样例2:输入为nums=[4, 5, 6, 7, 0, 1, 2],输出为0; 样例3:输入为nums=[11, 13, 15, 17],输出为11。
解题思路:考虑数组中的最后一个元素x,在最小值右侧的元素,值一定都严格小于x;左侧一定都严格大于x。二分查找,左边界为low,右边界为high,区间的中点为pivot,最小值在该区间内。将中点元素与右边界元素相比。 (1)中点元素<右边界元素,则中点在最小值的右侧,可以忽略二分查找的右半部分; (2)中点元素>右边界元素,则中点在最小值的左侧,可以忽略二分查找的左半部分; 因为数组不包含重复元素,且当前的长度不为1,则不会和high重合。如果长度为1,可以结束二分查找。
时间复杂度:O(log(n)); 空间复杂度:O(1)。