二分查找可以学习如何上下界问题非常有帮助。
首先讨论统一接口的情况,便于实现其他算法的版本。
这里使用两种思路去讨论算法的实现形式,第一种以“减而治之”的策略实现并从查找行为平衡度等方面对算法进行改进,第二种直接从通过规则的讨论直接讨论算法组成,各层判断。
语义定义:在有序向量的区间[l,h)内查找元素v,返回秩最大者。
语义定义:在有序向量的区间[l,h]内查找元素v,返回秩最大者。
upper_bound 当v存在时返回它出现的最后一个位置的后面一个位置。如果不存在,返回这样一个下标,在此处插入e,原来元素全部往后移动一个位置,后序列仍然有序。
lower_bound 当v存在时返回它出现的第一个位置。如果不存在,返回这样一个下标,在此处插入e,原来元素全部往后移动一个位置,后序列仍然有序。
之所以有如上区别,在于实现时候逻辑的统一性。请看下表
首先讨论上述情况下循环体的三种情况。
设lo为左边界,hi为右边界,mid = (lo+hi)/2,那么,根据mid的取值,分三种情况:
a[mid] == v,中间值等于v,而要求的是最后一次出现的v。最后出现的v值一定在mid的右边或者就是mid位置的这个值,所以我们应该调整左边界,lo = mid,区间变为[mid,hi]
a[mid] < v,说明v如果在数组中,应该出现在mid右侧,则调整左边界,l = mid + 1,区间变为[mid+1,hi]
a[mid] > v,说明v如果在数组中,应该出现在mid左侧,则调整右边界,v如果不在数组中,可能出现在mid的位置上,h=mid,区间变为[lo,mid]
a[mid] == v,中间值等于v,而要求的是最后一次出现的v。最后出现的v值一定在mid的右边或者就是mid位置的这个值,所以我们应该调整左边界,lo = mid,区间变为[mid,hi]
左闭右闭区间,upper_bound
A[m]=v 至少已经找到一个,而右边可能还有,因此区间变为[m,h]
A[m]>v 所求位置不可能在右边,但有可能是m,因此区间变为[l,m]
A[m]<v m和前面都不可行,因此区间变为[m+1,h]
左闭右闭区间,lower_bound
A[m]=v 至少已经找到一个,而左边可能还有,因此区间变为[l,m]
A[m]>v 所求位置不可能在右边,但有可能是m,因此区间变为[l,m]
A[m]<v m和前面都不可行,因此区间变为[m+1,h]
左闭右开区间,upper_bound
A[m]=v 至少已经找到一个,而右边可能还有,因此区间变为[m,h)
A[m]>v 所求位置不可能在右边,但有可能是m,因此区间变为[l,m]
A[m]<v m和前面都不可行,因此区间变为[m+1,h]
设lower_bound和upper_bound的返回值分别为L和R,则v出现的子序列为[L,R)。这个结论当v不存在的时候也成立:此时L=R,区间为空。
这里实现的就是STL中的同名函数。
三分支版本:
二分查找详解
http://www.cnblogs.com/segeon/archive/2012/07/27/2612361.html
两分支版本:
问题描述:有一个按非降序排列的有序数组a[0...n-1]和一个数v
1. 求数组a中最后一次出现的数v的下标
为了更清楚地说明问题,下面讨论一下当循环体内l和h之间(包含l和h)最后只剩3个和2个元素时的情况,初始元素个数超过3个的最后都会转化为这两种情况之一。
只剩3个元素时,可能出现2中情况,一种是其中里面含有v,另一种是不含v。假设v = 2,那么含有v的可能出现以下几种情况:
//二分查找算法版本B:在有序向量的空间[l,h)内查找元素v,0<=l<=h<=_size int bisearch(int* A,int l,int h,int v) { if(!A || l>h) return -1; int m; while(1<h-l)//每步迭代仅需做一次比较判断,有两个分支,成功查找不能提前终止 { m=l+(h-l)>>1;//以中点为轴点 (e<A[m])?hi=mi:lo=mi;//经比较后确定深入[l,m)或[m,h) }//出口时h=l+1,查找区间仅含一个元素A[l] return (e==A[l])?l:-1;//查找成功时返回对应的秩,否则统一返回-1 }//有多个命中元素时,不能保证返回秩最大者;查找失败时,简单返回-1,而不能指示失败的位置
1 int bisearch(int* A,int l,int h,int v) 2 { 3 if(!A || l>h) 4 return -1; 5 int m; 6 while(l<h) 7 { 8 m=l+(h-l)>>1; 9 (e<A[m])?hi=mi:lo=mi+1; 10 } 11 return l--; 12 }