查找算法-二分查找

Posted 夜忙猪

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了查找算法-二分查找相关的知识,希望对你有一定的参考价值。

算法思想

二分查找也称折半查找(Binar Search),是一种在 有序数组中查找某一特定元素的搜索算法。
搜索过程:

  • 从数组的中间开始搜索,如果中间的元素正好是要查找的元素,则搜索过程结束。

  • 如果该值大于中间元素,则在数组大于中间的那一半查找。

  • 如果该值小于中间元素,则在数组小于中间的那一半查找。

  • 而且再次查找的过程回到第一步,在该部分的中间元素开始。直到找到该元素或者数组为空。

其时间的复杂度为O(logn),空间复杂度o(1)

二分法很容易理解,但是细节是魔鬼,要写出一个正确的二分法不是一件容易的事情。

算法实现

首先我们了解一下其基本的实现,nums为有序数组,len为其长度,target为要寻找的目标

 
   
   
 
  1. // 寻找一个数,如果存在返回其索引,否则返回-1。

  2. int binarySearch(int* nums, int len, int target)

  3. {

  4. int left = 0;

  5. int right = len - 1;


  6. while (left <= right) {

  7. int mid = left + (right - left) / 2;

  8. if (nums[mid] == target) {

  9. return mid;

  10. } else if (nums[mid] < target) {

  11. left = mid + 1;

  12. } else if (nums[mid] > target) {

  13. right = mid - 1;

  14. }

  15. }

  16. return -1;

  17. }

当然我们也要学会拒绝造轮子嘛 O(∩_∩)O

包含这个头文件 我们就可以使用库函数了。

void bsearch(const void *key, const void *base, size_t nitems, size_t size, int (compare)(const void , const void));
key -- 要查找的元素
base -- 指向要排序的数组的第一个元素的指针。
nitems -- 由 base 指向的数组中元素的个数。
size-- 数组中每个元素的大小,以字节为单位。
compare -- 用来比较两个元素的函数,即函数指针(回调函数)

 
   
   
 
  1. #include <stdlib.h>

  2. #define ARRAY_SIZE 5

  3. int values[ARRAY_SIZE] = { 32, 32, 32, 32, 32 };


  4. // compare函数

  5. int cmpfunc(const void* a, const void* b)

  6. {

  7. return (*(int*)a - *(int*)b);

  8. }

  9. int main()

  10. {

  11. int* item;

  12. int res;

  13. int key = 32;


  14. item = (int*)bsearch(&key, values, ARRAY_SIZE, sizeof(int), cmpfunc);

  15. printf("lib index =%d\n", (int)(item - values) < 0 ? -1 : (int)(item - values)); //如果存在输出其索引,否则输出-1


  16. return (0);

  17. }

有时候我们可能想要最先出现的那个值,那么就需要我们把左值找出来

寻找左侧边界

 
   
   
 
  1. // 寻找左侧边界

  2. int leftBoundSearch(int* nums, int len, int target)

  3. {

  4. if (len == 0) {

  5. return -1;

  6. }

  7. int left = 0;

  8. int right = len;

  9. while (left < right) {

  10. int mid = left + (right - left) / 2;

  11. if (nums[mid] == target) {

  12. right = mid;

  13. } else if (nums[mid] < target) {

  14. left = mid + 1;

  15. } else if (nums[mid] > target) {

  16. right = mid;

  17. }

  18. }

  19. if (left == len) {

  20. return -1;

  21. } else {

  22. return nums[left] == target ? left : -1;

  23. }

  24. }

有时候我们可能想要最后出现的那个值,那么就需要我们把右值找出来

寻找右侧边界

 
   
   
 
  1. // 寻找右侧边界

  2. int rightBoundSearch(int* nums, int len, int target)

  3. {

  4. if (len == 0) {

  5. return -1;

  6. }

  7. int left = 0;

  8. int right = len;

  9. while (left < right) {

  10. int mid = left + (right - left) / 2;

  11. if (nums[mid] == target) {

  12. left = mid + 1;

  13. } else if (nums[mid] < target) {

  14. left = mid + 1;

  15. } else if (nums[mid] > target) {

  16. right = mid;

  17. }

  18. }

  19. if (left == 0) {

  20. return -1;

  21. } else {

  22. return nums[left - 1] == target ? (left - 1) : -1;

  23. }

  24. }

那么我们放到一起来体验一下把

 
   
   
 
  1. /* 使用 二分法 在数组中查找值 32 */


  2. #include <stdio.h>

  3. #include <stdlib.h>


  4. #define ARRAY_SIZE 5


  5. int values[ARRAY_SIZE] = { 32, 32, 32, 32, 32 };


  6. // compare函数

  7. int cmpfunc(const void* a, const void* b)

  8. {

  9. return (*(int*)a - *(int*)b);

  10. }


  11. // 寻找一个数,如果存在返回其索引,否则返回-1。

  12. int binarySearch(int* nums, int len, int target)

  13. {

  14. int left = 0;

  15. int right = len - 1;


  16. while (left <= right) {

  17. int mid = left + (right - left) / 2;

  18. if (nums[mid] == target) {

  19. return mid;

  20. } else if (nums[mid] < target) {

  21. left = mid + 1;

  22. } else if (nums[mid] > target) {

  23. right = mid - 1;

  24. }

  25. }

  26. return -1;

  27. }

  28. // 寻找左侧边界

  29. int leftBoundSearch(int* nums, int len, int target)

  30. {

  31. if (len == 0) {

  32. return -1;

  33. }

  34. int left = 0;

  35. int right = len;

  36. while (left < right) {

  37. int mid = left + (right - left) / 2;

  38. if (nums[mid] == target) {

  39. right = mid;

  40. } else if (nums[mid] < target) {

  41. left = mid + 1;

  42. } else if (nums[mid] > target) {

  43. right = mid;

  44. }

  45. }

  46. if (left == len) {

  47. return -1;

  48. } else {

  49. return nums[left] == target ? left : -1;

  50. }

  51. }

  52. // 寻找右侧边界

  53. int rightBoundSearch(int* nums, int len, int target)

  54. {

  55. if (len == 0) {

  56. return -1;

  57. }

  58. int left = 0;

  59. int right = len;

  60. while (left < right) {

  61. int mid = left + (right - left) / 2;

  62. if (nums[mid] == target) {

  63. left = mid + 1;

  64. } else if (nums[mid] < target) {

  65. left = mid + 1;

  66. } else if (nums[mid] > target) {

  67. right = mid;

  68. }

  69. }

  70. if (left == 0) {

  71. return -1;

  72. } else {

  73. return nums[left - 1] == target ? (left - 1) : -1;

  74. }

  75. }


  76. // main

  77. int main()

  78. {

  79. int* item;

  80. int res;

  81. int key = 112;


  82. item = (int*)bsearch(&key, values, ARRAY_SIZE, sizeof(int), cmpfunc);

  83. printf("lib index =%d\n", (int)(item - values) < 0 ? -1 : (int)(item - values));


  84. res = binarySearch(values, ARRAY_SIZE, key);

  85. printf("std index =%d\n", res);


  86. res = leftBoundSearch(values, ARRAY_SIZE, key);

  87. printf("lft index =%d\n", res);


  88. res = rightBoundSearch(values, ARRAY_SIZE, key);

  89. printf("rgt index =%d\n", res);

  90. return (0);

  91. }

 
   
   
 
  1. lib index =2

  2. std index =2

  3. lft index =0

  4. rgt index =4

光说不练假把式

我们在leetcode上来找两个例题试试手。

34. 在排序数组中查找元素的第一个和最后一个位置

给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。

你的算法时间复杂度必须是 O(log n) 级别。

如果数组中不存在目标值,返回 [-1, -1]。

示例 1:

输入: nums = [5,7,7,8,8,10], target = 8 输出: [3,4] 示例 2:

输入: nums = [5,7,7,8,8,10], target = 6 输出: [-1,-1]

这个很明显的就是要用二分法嘛,虽然官方给了两个方法

1:线性扫描方法

2:二分查找

但是方法一线性扫描时间复杂度肯定不满足嘛,相信用上面的例子很容易改出答案来。

875. 爱吃香蕉的珂珂

我们再看一个不是特别明显的例子。

珂珂喜欢吃香蕉。这里有 N 堆香蕉,第 i 堆中有 piles[i] 根香蕉。警卫已经离开了,将在 H 小时后回来。

珂珂可以决定她吃香蕉的速度 K (单位:根/小时)。每个小时,她将会选择一堆香蕉,从中吃掉 K 根。如果这堆香蕉少于 K 根,她将吃掉这堆的所有香蕉,然后这一小时内不会再吃更多的香蕉。  

珂珂喜欢慢慢吃,但仍然想在警卫回来前吃掉所有的香蕉。

返回她可以在 H 小时内吃掉所有香蕉的最小速度 K(K 为整数)。

 

示例 1:

输入: piles = [3,6,7,11], H = 8 输出: 4 示例 2:

输入: piles = [30,11,23,4,20], H = 5 输出: 30 示例 3:

输入: piles = [30,11,23,4,20], H = 6 输出: 23

题解

 
   
   
 
  1. int cmp(const void* a, const void* b) {

  2. return *(int*)a - *(int*)b;

  3. }

  4. int minEatingSpeed(int* piles, int pilesSize, int H){


  5. qsort(piles, pilesSize, sizeof(int), cmp);//先排成有序数组

  6. / * 二分法查找左值 */

  7. int left = 1; ;

  8. int right = piles[pilesSize-1];

  9. int mid;

  10. int time=0;

  11. while (left < right) {

  12. mid = (left + right) / 2;

  13. time = 0;

  14. for (int i=0; i<pilesSize; i++) {

  15. time += (piles[i] + (mid - 1)) / mid;

  16. }

  17. if (time > H)

  18. left = mid + 1;

  19. else

  20. right = mid;

  21. }

  22. return left;

  23. }


以上是关于查找算法-二分查找的主要内容,如果未能解决你的问题,请参考以下文章

「算法笔记」一文摸秃二分查找

PHP实现二分查找算法(代码详解)

二分查找算法讲解及其C++代码实现

leetcode查找算法(顺序查找,二分法,斐波那契查找,插值查找,分块查找)

算法——二分查找

二分查找--整理