当前位置:   article > 正文

二分查找(折半查找)算法_二分查找的asl分析

二分查找的asl分析

二分查找(折半查找)算法

查找也是有特殊情况的,比如数列本身是有序的。这个有序数列是怎么产生的呢?有时它可能本身就是有序的,也有可能是我们通过之前所学的排序算法得到的。

不管怎么说,我们现在已经得到了有序数列了并需要查找。这时二分查找该出场了。

二分查找(Binary Search)也叫作折半查找。二分查找有两个要求,一个是数列有序,另一个是数列使用顺序存储结构(比如数组)。

二分查找的原理及实现

二分查找的实现原理非常简单,首先元素得是有序的。

以升序数列为例,比较一个元素与数列中的中间位置的元素的大小,如果比中间位置的元素大,则继续在后半部分的数列中进行二分查找;如果比中间位置的元素小,则在数列的前半部分进行比较;如果相等,则找到了元素的位置。

每次比较的数列长度都会是之前数列的一半,这样会大大缩短寻找的耗时。

我们先来想象一下,如果数列中有 3 个数,则先与第 2 个数进行比较,如果比第 2 个数大,则与第 2 个数右边的数列进行二分查找,这时这个数列就剩下一个数了,直接比较是否相等即可。所以在 3 个数的时候最多比较两次。

同理,在有 4 个数的时候,我们与中间数进行比较,一般中间数是首加末除以 2 算出来的,这时我们算出来的中间数是 (1+4)/2 等于 2,所以我们把要查找的数与第 2 个数比较,若比第 2 个数小,则直接与第 1 个数比较;否则与后面两个数进行二分查找,这时的中间数是 (3+4)/2 等于 3,也就是后半部分的第 1 个数。再接着进行比较,相等则找到相应的元素,小于则没有这个数(因为左边所有的数都已经判断过了),大于则继续向右查找。所以在 4 个数的时候最多比较 3 次。

以此类推,在 5 个数的时候最多查找 3 次,在 6 个数的时候也是最多查找 3 次。

下面我们以一个实际的例子来看看二分查找的操作过程。假设待查找数列为 2、3、5、6、7、11、17,我们要找的元素为 17,下面进行二分查找。首先待查数列如 1 所示,我们找到中间的元素 7( (1+7)/2=4,第 4 个位置上的元素)。

2

3

5

6

7

11

17

图 1 在待查序列中找到中间元素

中间元素为 6,我们要找的元素比 6大,于是在后半部分查找,现在后半部分数列为7、11、17,我们找到中间元素,如图 2 所示。

2

3

5

6

7

11

17

 

 

图 2 在待查序列的后半部分找到中间元素

中间元素为 11,与 11 比较,比 11 大,则继续在后半部分查找,后半部分只有一个元素 17 了,这时直接与 17比较,若不相等,则说明在数列中没有找到元素,结束查找。

7

11

17

对于这 7 个元素的数列,我们只查找并比较了 3 次,很大的提高了效率。

下面我们来看看二分查找的实现。其实我们通过二分查找的操作步骤,可以很轻易地想出二分查找使用递归实现也很方便。下面我们用递归来实现二分查找。

  1. class Solution {
  2. public:
  3. int search(vector<int>& nums, int target)
  4. {
  5. return search_n(nums,target,0,nums.size()- 1);//调用函数
  6. }
  7. int search_n(vector<int>&nums,int target,int start,int end)
  8. {
  9. int n = (int)nums.size();
  10. if (!n)
  11. {
  12. return -1;
  13. }
  14. if (n == 1) {
  15. return nums[0] == target ? 0 : -1;//
  16. }
  17. if(start>end)//判断条件
  18. {
  19. return -1;
  20. }
  21. int mid = start + (end-start) / 2;
  22. if(nums[mid]==target)//当找到数字时返回下标
  23. {
  24. return mid;
  25. }
  26. else if(target <nums[mid])
  27. {
  28. return search_n(nums,target,start,mid -1);//递归
  29. }
  30. else
  31. {
  32. return search_n(nums,target,mid +1,end);//递归
  33. }
  34. }
  35. };

当然,除了递归实现,二分查找也可以使用非递归实现,代码如下:

  1. class Solution {
  2. public:
  3. int search(vector<int>& nums, int target)
  4. {
  5. int n = (int)nums.size();
  6. if (!n)
  7. {
  8. return -1;
  9. }
  10. if (n == 1) {
  11. return nums[0] == target ? 0 : -1;
  12. }
  13. int l = 0, r = n - 1;
  14. while (l <= r) {
  15. int mid = (l + r) / 2;
  16. if (nums[mid] == target) return mid;//如果一次刚好找到需要的数
  17. if (nums[0] <= nums[mid]) //当中点值大于起点时
  18. {
  19. if (nums[0] <= target && target < nums[mid]) //需要找的数在起点和中点之间
  20. {
  21. r = mid - 1;//对r重新定义
  22. }
  23. else {
  24. l = mid + 1;//对l重新定义
  25. }
  26. }
  27. else {//当中点小于终点时
  28. if (nums[mid] < target && target <= nums[n - 1])//当需要找的数在中点和终点之间
  29. {
  30. l = mid + 1;//把l定义为中点后的一个数
  31. }
  32. else
  33. {
  34. r = mid - 1;//把r定义为中点前的一个数
  35. }
  36. }
  37. }
  38. return -1;
  39. }
  40. };

这种对二分查找的优化其实有个名字,叫作插值查找,插值查找对于数列比较大并且比较均匀的数列来说,性能会好很多;但是如果数列极不均匀,则插值查找未必会比二分查找的性能好。

二分查找的特点及性能分析

二分查找的平均查找长度 ASL 为 ((n+1)log2(n+1))/n-1,有的书上写的是 log2(n+1)-1,或者是 log2n,具体计算比较麻烦,这里就不讨论了。

 

二分查找有个很重要的特点,就是不会查找数列的全部元素,而查找的数据量其实正好符合元素的对数,正常情况下每次查找的元素都在一半一半地减少。所以二分查找的时间复杂度为 O(log2n) 是毫无疑问的。当然,最好的情况是只查找一次就能找到,但是在最坏和一般情况下的确要比顺序查找好了很多。

二分查找的适用范围

二分查找要求数列本身有序

二分查找适合元素稍微多一些的数列,如果元素只有十几或者几十个,则其实可以直接使用顺序查找。

一般对于一个有序列表,如果只需要对其进行一次排序,之后不再变化或者很少变化,则每次进行二分查找的效率就会很高;但是如果在一个有序列表中频繁地插入、删除数据,那么维护这个有序列表会让人很累。

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/528182
推荐阅读
相关标签
  

闽ICP备14008679号