算法与数据结构之美—二分查找
开篇思考
如何用最省内存的方式实现快速查找功能?
思考题:
对于1000万个整数数据,每个数据占8个字节,如何快速的判断某个整数是否出现在这1000万数据中呢? 内存空间不超过100M;
二分查找(Binary Search)
二分查找针对于有序集合的查找方法,每次都与区间的中间数据比对大小,缩小查找区间的范围,下图是一次二分查找的过程,low和high代表的是待查找区间的下标,mid表示待查找区间的中间元素下标。
性能分析
二分查找高效,假设数据大小为n,每次查找完数据之后都会缩小为原来的一般,就是除以2,最坏的情况,直到查找区间缩小为空,才会停止。
当n/2^k=1时,k=log2(n),所以时间复杂度就是O(logn);
代码实现
二分查找非递归算法
public int bSearch(int[] a,int value){
//设定low和high指针
int low = 0;
int high = a.length - 1;
while(low<=high){
int mid = low + (high-low)>>1;
if(a[mid]==value)
{
return mid;
}else if(a[mid]<value){
low = mid + 1;
}else{
high = mid - 1;
}
}
return -1;
}
- 循环退出条件 low<=high
- mid取值,如果想要将性能优化到极致的话,可以写成mid = low+((high-low)>>1);
- low和high的更新; low = mid + 1,high = mid - 1;
二分查找递归算法
public int bSearch(int[]nums,int value,int low,int high){
if(low>high) return -1;
int mid = low + (high - low)>>1;
if(nums[mid]==value)
return mid;
else if(nums[mid]>value)
return bSerarch(nums,value,mid+1,high);
else
return bSearch(nums,value,low,mid-1);
}
应用场景
- 二分查找应用场景具有局限性,依赖顺序表结构,就是数组;
- 二分查找针对的有序数据;
- 数据量太小不适合二分查找;
二分查找变形问题
变体一:查找第一个值等于给定值的元素
代码分析:
a[mid]与要查找的value之间的关系就是:大于、小于、等于;
对于大于,只需要更新 high = mid - 1;
对于小于,只需要更新 low = mid +1;
当a[mid] = value的时候,需要判断是否是第一个值等于value:
- 当mid = 0时,肯定是第一个值;
- 当mid - 1的值不等于value,那么返回mid即可;
- 当mid - 1的值等于value,那么要找的元素肯定位于[low,mid-1]这个区间内,所以需要将high更新为mid -1 ;
//二分查找第一个值等于给定值的元素
public int bSearch(int[] nums,int value){
int low = 0;
int high = nums.length - 1;
while(low<=high){
int mid = low + ((high - low)>>1);
if(a[mid]>value)
high = mid - 1;
else if(a[mid]<value)
low = mid + 1;
else{
//判断mid-1是否等于value
if(mid == 0||a[mid-1]!=value)
return mid;
else{
high = mid - 1;
}
}
}
return -1;
}
变体二:查找最后一个值等于给定值的元素
类似于变体一,那我直接call 代码啦!
public int bSearch(int[]nums,int value){
int low = 0;
int high = nums.length - 1;
while(low <= high){
int mid = low + ((high-low)>>1);
if(a[mid]>value)
high = mid - 1;
else if(a[mid]<value)
low = mid + 1;
else{
if(mid==nums.length-1||a[mid]+1!=value)
return mid;
else
low = mid + 1;
}
}
return -1;
}
变体三:查找第一个大于等于给定值的元素
- 如果a[mid]<value,那么要查找的元素肯定在[mid+1,high]之间,更新low = mid+1;
- 如果a[mid]>=value,如果mid ==0,则a[mid]前面没有元素或者a[mid-1]<value,则直接返回mid,反之,第一个大于给定给定值的元素,存在于[low,mid-1]内,更新high为mid-1;
话不多说,直接code
public int bSearch(int[]nums,int value){
int low = 0;
iny high = nums.length - 1;
while(low<=high)
{
int mid = low + ((high-low)>>1);
if(a[mid]>=value)
{
if(mid==0||a[mid-1]<value)
return mid;
else{
high = mid - 1;
}
}
else{
` low = mid + 1;
}
}
return -1;
}
变体四:查找最后一个小于等于给定值的元素
public int bSearch(int[]nums,int value)
{
int low = 0;
int high = nums.length - 1;
while(low<=high)
{
int mid = low +((high-mid)>>1);
if(a[mid]<=value)
{
if(mid==nums.length-1||a[mid]+1>value)
return mid;
else
low = mid + 1;
}else{
high = mid - 1;
}
}
return -1;
}
解答开篇
如何在1000万个整数中快速查找某个整数?
内存限制是100M,每个数据大小是8个字节,那么所有数据存储到数组中占据80M的内存,符合内存的限制,先对1000万个数据进行排序,然后再利用二分查找进行查找;