二分查找算法的升级版
Posted cherish010
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了二分查找算法的升级版相关的知识,希望对你有一定的参考价值。
二分查找针对的是一个有序的数据集合,查找思想有点类似分治思想。每次都通过跟区间的中间元素对比,将待查找的区间缩小为之前的一半,直到找到要查找的元素,或者区间被缩小为0。
时间复杂度:O(logn)
一、二分查找容易出错的3个地方:
1、循环退出条件
注意是low <= high.
2、mid的取值
mid = (low + high)>>2 这种写法有问题,因为如果low和high比较大的话,两者之和就有可能溢出。因为改进的方法mid = low + ((high - low )>>2).
3、low和high的更新
low = mid + 1,high = mid -1.如果直接写成low = mid或者high = mid,就可能发生死循环。
二、二分查找的几种变体的代码实现
最简单的情况:有序数组中不存在重复元素
循环:
public static int binarySearch(int[] nums, int value) { if(nums == null) { return -1; } int length = nums.length; int low = 0; int high = length -1; while(low <= high) { int mid = low + ((high -low)>>2);//为什么不 (low + high)>>2 当low和high 很大时,防止溢出 if(value == nums[mid]) { return mid; }else if (value < nums[mid]) { high = mid -1; }else { low = mid + 1; } } return -1; }
递归:
public int bsearch(int[] nums, int value) { return binarySearchRecursion(nums, 0, nums.length -1 , value); } public static int binarySearchRecursion(int[] nums, int start, int end, int x) { if(start > end){//空表 return -1; } int mid = (start + end)/2; if(x == nums[mid]) { return mid; }else if(x < nums[mid]) { return binarySearchRecursion(nums, start, mid - 1, x); }else { return binarySearchRecursion(nums, mid + 1, end, x); } }
变体一:找出第一个等于给定值的元素
public static int binarySearch1(int[] nums, int value) { if(nums == null) { return -1; } int length = nums.length; int low = 0; int high = length -1; while (low <= high) { int mid = low + ((high - low)>>2); if (value < nums[mid]) { high = mid -1; }else if(value > nums[mid]) { low = mid + 1; }else { if(mid == 0 || nums[mid - 1] != value) { return mid; } high = mid -1; } } return -1; }
变体二:找出最后一个等于给定值的元素
public static int binarySearch2(int[] nums, int value) { if(nums == null) { return -1; } int length = nums.length; int low = 0; int high = length - 1; while(low <= high) { int mid = low + ((high - low)>>2); if(value < nums[mid]) { high = mid -1; }else if(value > nums[mid]) { low = mid + 1; }else { if(mid == length -1 || nums[mid + 1] != value) { return mid; } low = mid + 1; } } return -1; }
变体三:查找第一个大于等于给定值的元素
public static int binarySearch3(int[] nums, int value) { if(nums == null) { return -1; } int length = nums.length; int low = 0; int high = length - 1; while(low <= high) { int mid = low + ((high - low)>>2); if(value >= nums[mid]) { if(mid == 0 || nums[mid -1] < value) { return mid; } low = mid + 1; }else { high = mid -1; } } return -1; }
变体四:查找最后一个小于等于给定值的元素
public static int binarySearch4(int[] nums, int value) { if(nums == null) { return -1; } int length = nums.length; int low = 0; int high = length - 1; while(low <= high) { int mid = low + ((high - low)>>2); if(value <= nums[mid]) { if(mid == high - 1 || nums[mid + 1] > value) { return mid; } high = mid - 1; }else { low = mid + 1; } } return -1; }
三、二分查找应用场景的局限性
1、二分查找依赖的是顺序表结构,简单点就是数组
二分查找只能用在插入、删除操作不频繁,一次排序多次查找的场景中。针对动态变化的数据集合,二分查找不再适用。
2、二分查找针对的是有序数据
二分查找算法需要按照下标随机访问元素。下表随机访问数据的时间复杂度是O(1),而链表随机访问的时间复杂度是O(n),所以数据如果使用链表存储,二分查找的时间复杂度就会变得很高。
3、数据量太小不适合二分查找
如果数据量很小,完全没有必要二分查找,直接遍历就足够了,但有一个例外,如果数据之间的比较操作非常耗时,不管数据量大小,都推荐用二分查找。比如:数组中存储的都是长度为300的字符串,比较如此长的两个字符 串,非常耗时,我们尽可能减少比较次数,减少比较次数会提高性能,这时二分查找比顺序遍历更有优势。
4、数据量太大也不适合二分查找
二分查找底层依赖的是数组这种数据结构,数组存储需要连续的内存空间,对内存的要求比较苛刻。当太大的数据用数组存储比较吃力,就不能用二分查找了。
以上是关于二分查找算法的升级版的主要内容,如果未能解决你的问题,请参考以下文章