二分查找编码套路

二分查找的思想很好理解,但要写出没有bug的代码却并不是件容易的事。对于有序数组的二分查找,可以遵循一些套路快速写出无错代码。

下面先给出二分查找有序数组的一些问题,所有问题参考了《编程之美》。

1、给定非降序数组A,求任意一个i使得A[i]等于target,如不存在则返回-1。

2、给定非降序数组A,求最小的i使得A[i]等于target,如不存在则返回-1。

3、给定非降序数组A,求最大的i使得A[i]等于target,如不存在则返回-1。

4、给定非降序数组A,求最小的i使得A[i]大于target,如不存在则返回-1。

5、给定非降序数组A,求最大的i使得A[i]小于target,如不存在则返回-1。

6、给定非降序数组A和待插入元素target,返回待插入位置的下标。

二分查找的正确实现代码有多种形式,但要求以下几处达成一致:

1、初始化查找范围

2、计算中间位置的方法

3、循环结束条件

4、逼近方法

5、返回值

其中,

查找范围可以采用开区间,也可采用闭区间,或者半开半闭区间。

计算中间位置可以用mid=lo+(hi-lo)/2,也可以用mid=lo+(hi-lo+1)/2,在某一次探测过程中,如果数组长度为奇数,那么正中间恰好只有一个元素,此时这两种表达式效果是一样的,都取正中间元素;而如果数组长度为偶数,那么正中间没有元素,或者说有两个元素,前者取的是中间偏左的元素,后者取的是中间偏右的元素。

循环结束条件可以是lo<hi,也可以是lo<=hi,这与初始化、计算中间位置、逼近方法以及返回值都有关系。

逼近时有三种情况,分别是a[mid]<target、a[mid]>target和a[mid]==target。

下面是一种可行的组合,或者说是编码套路:

1、初始化采用闭区间,即lo=0, hi=size-1。

2、如果是求最小的i,则用mid=lo+(hi-lo)/2;如果是求最大的i,则用mid=lo+(hi-lo+1)/2;如果求任意的i,两者均可。

3、循环条件始终为lo<hi。

4、逼近时始终保持闭区间,三种情况分别处理,不做合并。写完之后如果代码可简化,再行合并处理。

5、返回值处需要对下标做有效性检查,即不能越界,另外还要检查该位置的数是否满足要求,只有两者同时成立才算成功找到。

下面根据上述编码套路,分别解决开始的6个问题,为简单说明起见,这里假定数组里存的都是整数。

1、给定非降序数组A,求任意一个i使得A[i]等于target,如不存在则返回-1。

 1 int FindPos(int a[], int size, int target) {
 2     int lo = 0, hi = size - 1, mid;
 3     while (lo < hi) {
 4         mid = lo + (hi - lo) / 2;
 5         if (target < a[mid])
 6             hi = mid - 1;
 7         else if (target > a[mid])
 8             lo = mid + 1;
 9         else
10             return mid;
11     }
12     return (lo >= 0 && lo < size && a[lo] == target) ? lo : -1;
13 }

2、给定非降序数组A,求最小的i使得A[i]等于target,如不存在则返回-1。

 1 int FindPos(int a[], int size, int target) {
 2     int lo = 0, hi = size - 1, mid;
 3     while (lo < hi) {
 4         mid = lo + (hi - lo) / 2;
 5         if (target < a[mid])
 6             hi = mid - 1;
 7         else if (target > a[mid])
 8             lo = mid + 1;
 9         else
10             hi = mid;
11     }
12     return (lo >= 0 && lo < size && a[lo] == target) ? lo : -1;
13 }

3、给定非降序数组A,求最大的i使得A[i]等于target,如不存在则返回-1。

 1 int FindPos(int a[], int size, int target) {
 2     int lo = 0, hi = size - 1, mid;
 3     while (lo < hi) {
 4         mid = lo + (hi - lo + 1) / 2;
 5         if (target < a[mid])
 6             hi = mid - 1;
 7         else if (target > a[mid])
 8             lo = mid + 1;
 9         else
10             lo = mid;
11     }
12     return (lo >= 0 && lo < size && a[lo] == target) ? lo : -1;
13 }

4、给定非降序数组A,求最小的i使得A[i]大于target,如不存在则返回-1。

 1 int FindPos(int a[], int size, int target) {
 2     int lo = 0, hi = size - 1, mid;
 3     while (lo < hi) {
 4         mid = lo + (hi - lo) / 2;
 5         if (target < a[mid])
 6             hi = mid;
 7         else if (target > a[mid])
 8             lo = mid + 1;
 9         else
10             lo = mid + 1;
11     }
12     return (lo >= 0 && lo < size && a[lo] > target) ? lo : -1;
13 }

5、给定非降序数组A,求最大的i使得A[i]小于target,如不存在则返回-1。

 1 int FindPos(int a[], int size, int target) {
 2     int lo = 0, hi = size - 1, mid;
 3     while (lo < hi) {
 4         mid = lo + (hi - lo + 1) / 2;
 5         if (target < a[mid])
 6             hi = mid - 1;
 7         else if (target > a[mid])
 8             lo = mid;
 9         else
10             hi = mid - 1;
11     }
12     return (lo >= 0 && lo < size && a[lo] < target) ? lo : -1;
13 }

6、给定非降序数组A和待插入元素target,返回待插入位置的下标。

 1 int FindPos(int a[], int size, int target) {
 2     int lo = 0, hi = size - 1, mid;
 3     while (lo < hi) {
 4         mid = lo + (hi - lo) / 2;
 5         if (target < a[mid])
 6             hi = mid;
 7         else if (target > a[mid])
 8             lo = mid + 1;
 9         else
10             lo = mid + 1;
11     }
12     return (lo >= 0 && lo < size && a[lo] > target) ? lo : lo + 1;
13 }

其他说明:

1、按套路写出来的代码可能会有冗余,主要表现在逼近步骤和返回值两处,写完后可做简化,不处理也能正确运行。

2、由于循环条件是lo<hi,所以退出时必有lo==hi,但是要注意循环可能根本就没有进入。

3、逼近时关于lo和hi的取值需要根据要求确定,建立循环不变式是个很好的办法。

除查找有序数组外,二分查找还有很多其他应用,其精髓在于每次都能将范围缩减至少一半,而不在乎用什么手段,或者是否有序。

时间: 2024-10-06 07:00:32

二分查找编码套路的相关文章

深入浅出数据结构C语言版(12)——从二分查找到二叉树

在很多有关数据结构和算法的书籍或文章中,作者往往是介绍完了什么是树后就直入主题的谈什么是二叉树balabala的.但我今天决定不按这个套路来.我个人觉得,一个东西或者说一种技术存在总该有一定的道理,不是能解决某个问题,就是能改善解决某个问题的效率.如果能够先了解到存在的问题以及已存在的解决办法的不足,那么学习新的知识就更容易接受,也更容易理解. 万幸的是,二叉树的讲解是可以按照上述顺序来进行的.那么,今天在我们讨论二叉树之前,我们先来讨论一种情形.一种操作:假设现在有一个数组,数组中的数据按照某

二分查找实现(Jon Bentley:90%程序员无法正确实现)

二分查找实现(Jon Bentley:90%程序员无法正确实现)作者:July出处:结构之法算法之道引言Jon Bentley:90%以上的程序员无法正确无误的写出二分查找代码.也许很多人都早已听说过这句话,但我还是想引用<编程珠玑>上的如下几段文字:“二分查找可以解决(预排序数组的查找)问题:只要数组中包含T(即要查找的值),那么通过不断缩小包含T 的范围,最终就可以找到它.一开始,范围覆盖整个数组.将数组的中间项与T 进行比较,可以排除一半元素,范围缩小一半.就这样反复比较,反复缩小范围,

(二分最大流) 最大流 + 二分查找 小结

做最大流题目的时候会遇到一种需要用二分查找的题型: (poj2455) 一张无向图中有 N 个点,M 条边,每条边都有一个权值,且每条边只能用一次,要求找出 T 条从 1 到 N 的路径,使这 T 条路径所经过的边中,权值的最大值最小. 转化为最大流模型:T就是最大流,每条边只能用一次在网络流中就是容量为1.然后二分查找(枚举),边权小于或等于mid的边就加一条容量为1的网络流的边.最后知道最大流ans与T相等的最小的mid就是所求. 在二分查找的时候,与一般的二分查找还是有区别的,一般不能有“

【转】STL之二分查找 (Binary search in STL)

Section I正确区分不同的查找算法count,find,binary_search,lower_bound,upper_bound,equal_range 本文是对Effective STL第45条的一个总结,阐述了各种查找算法的异同以及使用他们的时机. 首先可供查找的算法大致有count,find,binary_search,lower_bound,upper_bound,equal_range.带有判别式的如count_if,find_if或者binary_search的派别式版本,其

查找算法:二分查找、顺序查找

08年9月入学,12年7月毕业,结束了我在软件学院愉快丰富的大学生活.此系列是对四年专业课程学习的回顾,索引参见:http://blog.csdn.net/xiaowei_cqu/article/details/7747205 查找算法 查找算法是在存在的序列(list) 中查找特定的目标(target),要求序列中每个记录必须与一个关键词(key)关联才能进行查找. 查找算法通常需要两个输入: 1.被查找的序列 2.要查找的关键词 查找算法的输出参数和返回值: 1.返回类型为 Error_co

java——类型转换,冒泡排序,选择排序,二分查找,数组的翻转

一.类型转换 p { margin-bottom: 0.25cm; direction: ltr; color: #000000; line-height: 120%; text-align: justify; widows: 0; orphans: 0 } p.western { font-family: "Calibri", sans-serif; font-size: 10pt } p.cjk { font-family: "宋体"; font-size: 1

STL之二分查找 (转载)

转载自:地址 Section I正确区分不同的查找算法count,find,binary_search,lower_bound,upper_bound,equal_range 本文是对Effective STL第45条的一个总结,阐述了各种查找算法的异同以及使用他们的时机. 首先可供查找的算法大致有count,find,binary_search,lower_bound,upper_bound,equal_range.带有判别式的如count_if,find_if或者binary_search的

二分查找

递归版(在区间[x, y)中找v的位置) 1 //递归版二分查找 2 int bsearch(int * A, int x, int y, int v) 3 { 4 5 if(v<a[x] || v>a[y-1]) return -1; 6 int m = x + (y-x)/2; //此处能不能用int m = (x+y)/2,需要仔细考虑(暂时想不到原因) 7 if(A[m]==v) return m; 8 else if(A[m]>v) return bsearch(A, x, m

二分查找总结

最近刷leetcode和lintcode,做到二分查找的部分,发现其实这种类型的题目很有规律,题目大致的分为以下几类: 1.最基础的二分查找题目,在一个有序的数组当中查找某个数,如果找到,则返回这个数在数组中的下标,如果没有找到就返回-1或者是它将会被按顺序插入的位置.这种题目继续进阶一下就是在有序数组中查找元素的上下限.继续做可以求两个区间的交集. 2.旋转数组问题,就是将一个有序数组进行旋转,然后在数组中查找某个值,其中分为数组中有重复元素和没有重复元素两种情况. 3.在杨氏矩阵中利用二分查