一.二分查找基本思想
在有序的序列里,先将目标和中间的数值比较,如果大于中间数值,则在后半段的中间继续比对;如果小于中间数值,则在前半段的中间继续比对。以此类推,直至找到目标,或者结束查找没有找到。
二.关键条件
(1)有序序列
(2)顺序存储结构
三.时间复杂度
O(logn)
四.优点和不足
优点是比较次数少,查找速度快,平均性能好;
缺点是要求待查表为有序表,且插入删除困难
五.代码示例:(下面例子皆以升序为例)
1.最基本的二分查找:
int binarySearch(int arr[],int arr_L,int N)
{
int min=0;
int max=arr_L-1;
int mid=0;
while(min<=max)//注意这里有等于=,不然会丢失边界的情况
{
//mid=int(1.0/2*(min+max));//这里有可能会产生溢出,而且需要强制类型转换,不建议使用
mid=min+(max-min)/2;
if(N==arr[mid])
{
return mid;
}
else if(N<arr[mid])
{
max=mid-1;
}
else
{
min=mid+1;
}
}
return -1;
}
这里有几个问题需要注意:
1.循环判定的条件:min<=max(如果直接是min<max就会丢失边界)
2.为了防止数据溢出:mid=min+(max-min)/2
3.当N!=arr[mid]时,min=mid+1,max=mid-1
2.递归实现二分查找
//递归二分查找
int binarySearch2(int arr[],int high,int low,int N)
{
//int mid=int(1.0/2*(high+low));
int mid=low+(high-low)/2;
if(low>high)
{
return -1;
}
if(N==arr[mid])
{
return mid;
}
else if(N>arr[mid])
{
low=mid+1;
return binarySearch2(arr,high,low,N);
}
else
{
high=mid-1;
return binarySearch2(arr,high,low,N);
}
}
需要注意的问题同上。
3.二分法的几种变形
(1)查找目标值区域的左边界(查找与目标值相等的第一个位置)(查找第一个不小于目标值数的位置)
//查找目标值区域的左边界(查找与目标值相等的第一个位置)(查找第一个不小于目标值数的位置)
int binarySearch(int arr[],int arr_L,int N)
{
int min=0;
int max=arr_L-1;
int mid=0;
while(min<=max)//注意这里有等于=,不然会丢失边界的情况
{
//mid=int(1.0/2*(min+max));//这里有可能会产生溢出,而且需要强制类型转换
mid=min+(max-min)/2;
if(N<=arr[mid])
{
max=mid-1;
}
else
{
min=mid+1;
}
}
if(min<arr_L&&arr[min]==N)
{
return min;
}
else
{
return -1;
}
}
(2)查找目标值区域的右边界(查找与目标值相等的最后一个位置)(查找最后一个不大于目标值数的位置)
//查找目标值区域的右边界(查找与目标值相等的最后一个位置)(查找最后一个不大于目标值数的位置)
int binarySearch(int arr[],int arr_L,int N)
{
int min=0;
int max=arr_L-1;
int mid=0;
while(min<=max)//注意这里有等于=,不然会丢失边界的情况
{
//mid=int(1.0/2*(min+max));//这里有可能会产生溢出,而且需要强制类型转换
mid=min+(max-min)/2;
if(N>=arr[mid])
{
min=mid+1;
}
else
{
max=mid-1;
}
}
if(max>0&&arr[max]==N)
{
return max;
}
else
{
return -1;
}
}
(3)查找第一个大于目标值的数/查找比目标值大但是最接近目标值的数的位置(第二题变形)
int binarySearch(int arr[],int arr_L,int N)
{
int min=0;
int max=arr_L-1;
int mid=0;
while(min<=max)//注意这里有等于=,不然会丢失边界的情况
{
//mid=int(1.0/2*(min+max));//这里有可能会产生溢出,而且需要强制类型转换
mid=min+(max-min)/2;
if(N>=arr[mid])
{
min=mid+1;
}
else
{
max=mid-1;
}
}
return low<arr_L?low:-1;
}
注意此处就是大于目标值的最后一位,也就是max+1,也就是退出循环的 min
(4)查找最后一个小于目标值的数/查找比目标值小但是最接近目标值的数的位置(第(1)题变形)
int binarySearch(int arr[],int arr_L,int N)
{
int min=0;
int max=arr_L-1;
int mid=0;
while(min<=max)//注意这里有等于=,不然会丢失边界的情况
{
//mid=int(1.0/2*(min+max));//这里有可能会产生溢出,而且需要强制类型转换
mid=min+(max-min)/2;
if(N<=arr[mid])
{
max=mid-1;
}
else
{
min=mid+1;
}
}
return max>0?max:-1;
}
(6).旋转数组(无重复项)
(什么是旋转数组:将一组有序的序列左移,将移出来的部分依次连接到原序列后边(1 2 3 4 5--------->4 5 1 2 3))
int findMin(int arr[],int N)
{
int left=0;
int right=N-1;
int mid=0;
if(N==0)
{
return -1;
}
while(left<right)
{
mid=left+(right-left)/2;
if(arr[mid]>arr[right])
{
left=mid+1;
}
else
{
right=mid;
}
}
return arr[left];
}
和二分法的区别:(1)条件是right<left
(2)如果arr[mid]<arr[right],说明前半部分有序,最小值在后半部分;反之,说明最小值在前半部分,最后left会指向最小项
(7)旋转数组(有重复项)
int findMin(int arr[],int N)
{
int left=0;
int right=N-1;
int mid=0;
if(N==0)
{
return -1;
}
while(left<right)
{
mid=left+(right-left)/2;
if(arr[mid]>arr[right])
{
left=mid+1;
}
else if(arr[mid]<arr[right])
{
right=mid;
}
else
{
right--;
}
}
return arr[left];
}
区别就是当arr[mid]==arr[right]时,不知道最小值在右边还是左边,就让right-1;
(8)旋转序列中搜索(不含重复项)
int search(int arr[],int arr_L,int N)
{
int left=0;
int right=arr_L-1;
int mid=0;
int offset,remid;
if(arr_L==0)
{
return -1;
}
while(left<right)
{
mid=left+(right-left)/2;
if(arr[mid]>arr[right])
{
left=mid+1;
}
else
{
right=mid;
}
}
offset=left;
left=0,right=arr_L-1;
while(left<=right)
{
mid=left+(right-left)/2;
remid=(mid+offset)%arr_L;
if(N==arr[remid])//arr[remid]为有序序列的中点值
{
return remid;
}
else if(N>arr[remid])
{
left=mid+1;//这里需要特别注意
}
else
{
right=mid-1;
}
}
return -1;
}
先找到最小的值,即找到分界点,然后根据remid=(mid+offset)%len求得真实的mid位置,即arr[remid]是有序序列的中点,arr[mid]为当前序列的中点(旋转数组),因此特别需要注意二分查找时left=mid+1,而不是left=remid+1;
不需要找分界点的方法:
int search(int arr[],int arr_L,int N)
{
int left=0;
int right=arr_L-1;
int mid=0;
if(arr_L==0)
{
return -1;
}
while(left<=right)
{
mid=left+(right-left)/2;
if(N==arr[mid])
{
return mid;
}
else if(arr[left]<=arr[mid])//从这里直接判断
{
if(N<arr[mid]&&N>=arr[left])
{
right=mid-1;
}
else
{
left=mid+1;
}
}
else if(arr[mid]<=arr[right])
{
if(N>arr[mid]&&N<=arr[right])
{
left=mid+1;
}
else
{
right=mid-1;
}
}
}
return -1;
}
(9)旋转序列中搜索(有重复项)
int search(int arr[],int arr_L,int N)
{
int left=0;
int right=arr_L-1;
int mid=0;
if(arr_L==0)
{
return -1;
}
while(left<=right)
{
mid=left+(right-left)/2;
if(N==arr[mid])
{
return mid;
}
else if(arr[mid]>arr[left])
{
if(N<arr[mid]&&N>=arr[left])
{
right=mid-1;//
}
else
{
left=mid+1;
}
}
else if(arr[mid]<arr[right])
{
if(N>arr[mid]&&N<=arr[right])
{
left=mid+1;
}
else
{
right=mid-1;
}
}
else
{
right--;
}
}
return -1;
}
待补充:二维数组中的查找
练习题目:
https://leetcode.com/problems/search-insert-position/description/
https://weiweiblog.cn/getnumberofk/
https://leetcode.com/problems/find-minimum-in-rotated-sorted-array/
https://leetcode.com/problems/find-minimum-in-rotated-sorted-array-ii
https://weiweiblog.cn/minnumberinrotatearray/
https://leetcode.com/problems/search-in-rotated-sorted-array
https://leetcode.com/problems/search-in-rotated-sorted-array-ii/