二分查找

二分查找

有序数组无重复元素的查找Key的下标

// 二分查找的迭代版本

/* 
    注:如果数组索引使用整数求mid时,l+h可能发生溢出*/

int binarySearch(int a[], int n, int key) 
{
        int l = 0;
        int h = n-1;
        int mid; 

        while (l <= h) {
                mid = (l + h)/2; // l+h可能发生溢出
                if (a[mid] > key) {
                        h = mid-1;           
                } else if (a[mid] < key) {
                        l = mid+1;
                } else {
                        return mid;
                }
        }
        return -1;
}
// 二分查找的递归版本
// 与上面的存在相同的问题

int binarySearch2(int a[], int l, int h, int key) 
{
        if (l <= h) {
            int mid = (l+h)/2;
            if (a[mid] > key)
                    return binarySearch2(a, l, mid-1, key);
            else if (a[mid] < key) 
                    return binarySearch2(a, mid+1, h, key);
            else
                    return mid;
         } 

        return -1;
}
// 以上两个版本都有可能在求mid,即l+h计算式发生溢出情况

int binarySearch3(int a[], int n; int key)
{
    int h = n;
    int l = 0;
    int mid;

    while (l <= h) {
    // 求解mid,使用 l+ (h-l)/2防止以上版本溢出情况
        mid = l + (h-l)/2;
        if (a[mid] > key) 
            h = mid - 1;
        else if (a[mid] < key)
            l = mid + 1;
        else 
            return mid;
    }

    return -1;
}

二分查找返回key(可能有重复)第一次出现的下标

// 自己优先想到的版本
// 直接在binarySearch3上改动的版本,直觉能直接想到的。没有一通科学的分析,总是缺少点什么。

int binarySearch_fist(int a[], int n; int key)
{
    int h = n;
    int l = 0;
    int mid;

    while (l <= h) {
        mid = l + (h-l)/2;
        if (a[mid] > key) 
            h = mid - 1;
        else if (a[mid] < key)
            l = mid + 1;
        else {
            mid--;
            while (mid >= 0) {
                if (a[mid] == key)
                    mid--;
                else 
                    break;
            }
            return mid + 1;
        }
    }

    return -1;
}

其他版本

通过循环不变式理论分析

循环不变式:
如果key存在于数组,那么key第一次出现的下标x一定在[left,right]中,且array[left] < key, array[right] >=key。

初始化:
第一轮循环开始之前,处理的数组是[0,n-1],这时显然成立。

保持:
每次循环开始前,如果key存在于原数组,那么x存在于待处理数组array[left,…right]中。

对于array[mid] < key,array[left,…,right]均小于key,x只可能存在于array[mid+1,…,right]中。数组减少的长度为mid-left+1,至少为1。

否则,array[mid] >= key,array[mid]是array[mid,…,right]中第一个大于等于key的元素,后续的等于key的元素(如果有)不可能对应于下标x,舍去。此时x在[left,…,mid]之后。数组减少的长度为right-(mid+1)+1,即right-mid,根据while的条件,当right==mid时为0。此时right==left,循环结束。

终止:
此时left>=right。在每次循环结束时,left总是x的第一个可能下标,array[right]总是第一个等于key或者大于key的元素。

那么对应于left==right的情况,检查array[left]即可获得key是否存在,若存在则下标为x;

对于left>right的情况,其实是不用考虑的。因为left==上一次循环的mid+1,而mid <= right。若mi+1>right,意味着mid == right,但此时必有left == right,这一轮循环从开始就不可能进入。

int binarySearch_first(int array[], int length, int key)
{
    if (!array)
        return -1;

    int left = 0, right = lenght -1, mid;
    while (left < right) {
        mid = left + (right - left)/2;
        if (array[mid] < key)
            left = mid + 1;
        else 
            right = mid;
    }
    if (array[left] == key)
        return left;

    return -1;
}

二分查找的思想

利用循环不变式,通过每轮循环都能够缩小(或找到)关键字key的范围,直到找到或循环结束(即查找失败)。

循环不变式

循环不变式主要是用来帮助理解算法的正确性。形式上类似于数学归纳法,它是一个需要保证正确的断言。

循环不变式的三个性质:
初始化:他在循环的第一轮迭代开始之前,应该是正确的。
保持:如果在循环的某一次迭代开始之前它是正确的,那么下一次迭代开始之前,它也应该保持正确。
终止:循环能够终止,并且可以得到期望的结果。

理解

  1. 利用循环不变式的思想指导二分查找中每一次迭代开始时,必须保证排除了不可能存在key的范围,确定key可能存在的范围[left, right]。
  2. 确定循环终止的条件,防止死循环的发生。

怎么确定循环终止条件?

举例:
分析二分查找返回key(可能有重复)第一次出现的下标中循环终止条件

  1. 若array[mid] < key 时,第一个等于Key的元素可能存在的范围为[mid+1, right],那么原数组减少的长度为 mid - left + 1,即数组减少至少为1(mid=left时)。

  2. 若array[mid] >= key 时,可知array[mid]可能是array[mid, right]范围内第一个大于等于key的值,因此第一个等于key值可能存在arry[left, mid]范围,那么原数组减少的长度为right - (mid + 1) +1 = right -mid,当right = mid时,left=right,此时key可能的取值为array[left]。

  3. 因此,循环终止条件为left < right。当left=right,循环在array[mid]>=key时,left=right=mid,发生死循环。

  4. 循环终止时:left=right
    若循环结束前,上一次循环是array[mid]<key,那么循环结束时,left=mid+1=right。

    若循环结束前,上一次循环是array[mid]>=key,那么循环结束时,left=mid=right。

    因此,只需判断array[left]是否等于key即可。

参考

如何写出正确的二分查找?——利用循环不变式理解二分查找及其变体的正确性以及构造方式

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值