二分法找元素第一次出现位置

常见的二分法找一个元素出现的位置(随机的)是:
//x[0...n-1]是升序排列。找到元素t出现的一个位置(不一定是第一次出现的位置)
l=0;u=n-1;
loop
    if l>u
        p = -1; break;
    m = (l+u)/2;
    case
        x[m] < t: l = m+1
        x[m] ==t: p=m; break
        x[m] > t: u=m-1



现在需要用二分法找到t第一次出现的位置。《编程珠玑》介绍了一个程序写法,很有意思。
如果第一个t的位置是m,x[m]=t, 则x[0...m-1]<t<=x[m...n-1]。
找到m满足这样的关系。用二分法的思想去找到这样的位置。二分法有两个变量l,u来表示数组的两端。找到m就相当于找到这样条件的l和u:
l+1=u,x[l]<t<=x[u]
我们需要从数组两端开始循环寻找满足条件的l,u。第四章介绍的验证循环正确性的知识:初始化时确立不变式,然后每次迭代都保存其真值,最后有终止的条件。
如果数组元素个数多于1,初始化l=0,u=n-1,寻找的循环程序就有不变量:x[l]<t && x[u]>=t && l<u。但是有特殊情况,
1. x[0]=t,如果初始化l=0,u=n-1,初始化时不变量就不能满足。
2. 只有一个元素l=0,u=0,初始化时不变量也不能满足。
如果直接这样写程序就必须处理特殊情况,程序不简练。
书中程序用数组两端两个刚越出数组范围的值(-1和n)来初始化l和u,并假想x[-1]<t,x[n]>=t,而程序不会访问这两个变量。这确实有想象力,突破一种思维限制:用数组范围内值来初始化变量。这样各种情况下初始化时不变量(x[l]<t && x[u]>=t && l<u)都能被满足。我估计初始有这种想法的牛人应该也是想到这个不变量(x[l]<t && x[u]>=t && l<u),然后发现特殊情况不是都满足,然后有了这样的巧妙想法。用这种方法写出的程序很简练。当然我们也可以用常规的二分法找到t的某个位置,然后往前找到第一个。

l=-1;u=n;
while(l+1 != u)
    m = (l+u)/2;
    if x[m] < t
        l = m
    else
        u = m
p = u;
if (p>=n || x[p]!=t)
    p = -1

你可能感兴趣的:(二分法找元素第一次出现位置)