二分查找,无论是从名字还是理论都十分简单一个算法,其博大精深,简直恐怖。Jon Bentley:90%以上的程序员无法正确无误的写出二分查找代码。
别人不知道,反正我早上是写了好久,这个查找算法,将查找的复杂度从 o( n ) 降到了 o( logn ) ,当之无愧的的好算法,更是许多高级算法的优化策略之一。
二分查找之基本思路
虽然二分查找是一个很吊的算法,但是跟很多算法一样,需要使用的基础条件——序列有序!
先假设一个单调非增序列:1 2 3 4 5 6 ,求找到3的位置,地球人都会马上这么想——一个一个找咯,可是这样很慢!
于是有人想要加快查找速度,他们发现如果从中间开始找,那么每次比较后就至少可以排除一半的数,这就是二分查找的基本思想——折半。
1)设置三个指针变量(应该说是指针性质的变量,索引也是可以的)——low ,mid 和 high,并且满足 mid = ( low + high ) / 2;
2)设置循环,进行target值与mid存储值的比较,根据比较结果更新low或者high;
3)在 2)中,若出现找到target的情况,则返回mid指针;如果一直找不到,则返回空指针。
下面是代码:
//方法一int binary_search(int n, int v) { int low=0,high=n-1,mid; while (low<=high) { mid=(low+high)/2; if (a[mid]==v) return mid; if (a[mid]<v) low=mid+1; if (a[mid]>v) high=mid-1; } return -1; }
看起来好像没什么问题,实际上在一些情况下 ,答案会很奇怪:1 2 2 4 5,我们如果找2,答案 -> 2 (这是索引啊!)
那么问题来了,为什么是返回第二个,而不是第一个呢?其实很简单,当序列出现重复元素时,我们找到了当然是其中“任意”一个啦!
但是其实往往我们需要处理的序列总是拥有重复元素的,所以,我们需要优化!
我们先来分析原来的二分查找 —— left < mid <right ,即我们将“=”的情况全部交给mid处理,于是mid会给你各种答案:
所以,我们不妨按这种规则来二分 —— left <= mid < right !
我们可以看看代码:
//方法二:返回第一个位置int lowerBound(const int a[],const int size,const int target) { int low=0,high=size-1,mid; while(low<high) { mid=(low+high)/2; if(a[mid]<target) low=mid+1; else high=mid; } if(a[low]==target) return low; else return -1; }
这里我们会发现两种查找方式一个最明显的不同:方法一是找到答案立即返回,方法二则是一直找到最底部(当成树看)然后再返回!感觉方法二要一
直找到底部好像会很慢,但是事实证明:方法一才是最慢的,所以我们以后可以抛弃第一种写法啦!
当然,方法二是返回第一个位置,那么就会有返回最后一个位置的算法啦!
上代码:
//方法三:返回最后一个位置int upperBound(const int a[], const int size, const int target) { int low=0,high=size-1,mid; while(low<high) { mid=(low+high)/2+1; if(a[mid]>target) high=mid-1; else low=mid; } if(a[high]==target) return high; else return -1; }
以上就是我们的二分查找算法,参考资料:数据结构与程序设计——C++语言描述(这真是一本好书!)