数据结构与算法之美(笔记6)二分查找

本文深入讲解二分查找算法,探讨其原理、代码实现及变体,包括查找首个、末个目标值,及首尾边界值。同时分析算法的时间与空间复杂度,并讨论适用场景与限制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

二分思想

二分查找针对的是一个有序的数据集合,查找思想有点类似分治思想。每次都用过跟区间的中间元素对比,将待查找的区间缩小为之前的一半,直到找到要查找的元素,或者区间被缩小为0。

假设我们要寻找19。

这里给出二分查找的代码实现:

int binary_search(int* arr,int capacity,int elem){
     int low = 0;
     int high = capacity -1;
     while(low <= high){
         int mid = low+ ((high-low)>>1);// 防止两个数相加溢出
         if(arr[mid] == elem){
             return mid+1;
         }else if(elem > arr[mid]){
             low = mid+1;
         }else{
             high = mid-1;
         }
     }
     return -1;
}

分析:

我们假设数据规模为n,每次查找后数据都会缩小为原来的一半,最坏的情况直到区间为空,才停止。

k的值就是总共缩小的次数,而每一次缩小操作只涉及两个数据的大小比较,所以经过了k次区间缩小操作,时间复杂度就是O(k)。当n/(2^k) = 1的时候,k =log2n,所以时间复杂度是O(logn)。

另外,它不需要额外的空间,因此空间复杂度是O(1)。

 

适用场景

首先二分查找依赖的数据结构是数组。

因为数组支持随机访问,因此在访问数据的时候,才能做到O(1)。如果你是使用链表,我们第一次找到中间结点的时间复杂度是O(n),第二次是O(n/2),直到为1,次数k = log2n,这是一个等比数列求和,可以求出时间复杂度是O(n)。

其次,二分查找针对有序数据

对于数据来说,必须有序。我们在维护一个数组的时候,如果维护的是一组静态的数据,没有频繁地插入和删除,那么我们可以在使用快速排序之后,多次二分查找,这样排序的成本可被均摊,二分查找的边际成本就会比较低。

但是,如果我们针对的是一组动态的数组,有频繁地插入和删除,那么每一次插入,我们都需要数据搬移,再使用二分查找,那么成本是很高的。

所以二分查找只能够用在插入、删除操作不频繁,一次排序多次查找的场景中。针对动态变化的数据集合,二分查找不再使用,我们应该使用二叉排序数。

再次,二分查找不适用数据量小的规模。

如果要处理的数据很小,比如只有10个,那么遍历更快。

最后,数据太大也不适合

因为二分查找依赖数组,数组需要连续的空间,假设你有1GB的数据,那么你要在内存中开辟一个1GB的连续的空间。连续这个词就限制了应用。

 

二分查找的变体

1.查找第一个值等于给定值的元素

上一个实现的情况是基于没有重复的,如果现在有这样一个数组:

我们要查找第一个等于8的位置,要怎么做?当我们找到第一个等于8的元素的时候,只要判断一下它的前一个是否为8就行了,如果是的话,说明我们找到的8不是第一个,所以继续查找。这里给出代码的实现:

int binary_search_first(int* arr,int capacity,int elem){
    int low = 0;
    int high = capacity - 1;
    while(low<=high){
        int mid = low + ((high-low)>>1);
        if(elem == arr[mid]){
            if(elem != arr[mid-1]){
                return mid+1;
            }else{
                high = mid-1;
            }
        }
        else if(elem > arr[mid]) low = mid+1;
        else high = mid-1;
    }
    return -1;
}

2.查找最后一个值等于给定值的元素

其实跟前面一样,只要判断找到的值的下一个是否等于给定值就行了,这里直接给出代码:

int binary_search_last(int* arr,int capacity,int elem){
    int low = 0;
    int high = capacity -1;
    while(low<=high){
        int mid = low + ((high-low) >> 1);
        if(elem == arr[mid]){
            if(arr[mid+1] != elem){
                return mid+1;
            }else{
                low = mid+1;
            }
        }else if(elem > arr[mid]){
            low = mid+1;
        }else{
            high = mid-1;
        }
    }
    return -1;
}

3.查找第一个大于等于给定值的元素

实际上,我们只要在前面代码的基础上进行小的改动就可以了,代码如下:

int bsearch_large(int* arr,int capacity,int elem){
    int low = 0;
    int high = capacity -1;
    while(low <= high){
        int mid = low + ((high - low) >> 1);
        if(arr[mid] >= elem){
            if(mid == 0 || arr[mid-1] < elem) return mid+1;
            else high = mid-1;

        }else{
            low = mid+1;
        }
    }
    return -1;
}

4.最后一个小于等于给定值的元素

int bsearch_small(int* arr,int capacity,int elem){
    int low = 0;
    int high = capacity - 1;
    while(low <= high){
        int mid = low + ((high - low) >> 1);
        if(arr[mid] <= elem){
            if(mid == capacity-1 || arr[mid+1]>elem) return mid+1;
            else low = mid +1;
        }else{
            high = mid-1;
        }
    }
    return -1;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值