美文网首页
《数据结构与算法之美》12——二分查找

《数据结构与算法之美》12——二分查找

作者: 大杂草 | 来源:发表于2020-07-21 17:44 被阅读0次

二分查找是一种非常简单易懂的快速查找算法,生活中随处可见,比如数字炸弹游戏。

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

惊人的查找速度

二分查找是一种非常高效的查找算法,时间复杂度是O(logn)。

假设数据大小是n,每次查找后数据都会缩小为原来的一半,也就是除以2。最坏情况下,直到查找区间被缩小为空,才停止。

区间大小变化

可以看出这是一个等比数列,其中n/2^k=1时,k就是总共缩小的次数,求得k=log2(n),所以时间复杂度是O(logn)。

代码实现

非递归实现:

public int Find(int[] datas, int value)
{
    if (datas == null || datas.Length == 0) return -1;
 
    int start = 0, end = datas.Length - 1;
    int mid;
    while (start <= end)
    {
        mid = start + (end - start) / 2;
        if (value == datas[mid])
        {
            return mid;
        }
        else if (value < datas[mid])
        {
            end = mid - 1;
        }
        else
        {
            start = mid + 1;
        }
    }
 
    return -1;
}

递归实现:

public int FindByRecursion(int[] datas, int value)
{
    return FindInternallyByRecursion(datas, value, 0, datas.Length - 1);
}
 
private int FindInternallyByRecursion(int[] datas, int value, int start, int end)
{
    if (datas == null || datas.Length == 0) return -1;
 
    if (start > end) return -1;
 
    int mid = start + (end - start) / 2;
 
    if (value == datas[mid])
    {
        return mid;
    }
    else if (value < datas[mid])
    {
        end = mid - 1;
        return FindInternallyByRecursion(datas, value, start, end);
    }
    else
    {
        start = mid + 1;
        return FindInternallyByRecursion(datas, value, start, end);
    }
}

二分查找容易出错的地方

1.循环退出条件

注意是low<=high,而不是low<high。

2.mid的取值

mid=(low+high)/2有可能溢出,改进方法是mid=low+(high-low)/2,进一步改为mid=low+((high-low)>>1)。

3.low和high的更新

low=mid+1, high=mid-1。注意这里的+1和-1,写成low=mid或者high=mid,就可能会发生死循环。

二分查找应用场景的局限性

  1. 二分查找依赖的是顺序表结构,简单点说就是数组。
  2. 二分查找针对的是有序数组。
  3. 数据量太小不适合二分查找。
  4. 数据量太大也不适合二分查找。

二分查找的变体

二分查找虽然原理极其简单,但是想写出没有Bug的二分查找并不容易。

下面介绍四种常见的二分查找变形问题:

变体一:查找第一个值等于给定值的元素

/**
 * 查找第一个值等于给定值的元素
 */
public int FindFirst(int[] datas, int value)
{
    if (datas == null || datas.Length == 0) return -1;
 
    int start = 0, end = datas.Length - 1;
    int mid;
    while (start <= end)
    {
        mid = start + (end - start) / 2;
 
        if (value < datas[mid])
        {
            end = mid - 1;
        }
        else if (value > datas[mid])
        {
            start = mid + 1;
        }
        else
        {
            if ((mid == 0) || (datas[mid - 1] != value)) return mid;
            else end = mid - 1;
        }
    }
 
    return -1;
}

变体二:查找最后一个值等于给定值的元素

/**
 * 查找最后一个值等于给定值的元素
 */
public int FindLast(int[] datas, int value)
{
    if (datas == null || datas.Length == 0) return -1;
 
    int start = 0, end = datas.Length - 1, n = datas.Length;
    int mid;
    while (start <= end)
    {
        mid = start + (end - start) / 2;
 
        if (value < datas[mid])
        {
            end = mid - 1;
        }
        else if (value > datas[mid])
        {
            start = mid + 1;
        }
        else
        {
            if ((mid == n - 1) || (datas[mid + 1] > value)) return mid;
            else start = mid + 1;
        }
    }
 
    return -1;
}

变体三:查找第一个大于等于给定值的元素

/**
 * 查找第一个值大于等于(不小于)给定值的元素
 */
public int FindFirstNotLessThan(int[] datas, int value)
{
    if (datas == null || datas.Length == 0) return -1;
 
    int start = 0, end = datas.Length - 1;
    int mid;
    while (start <= end)
    {
        mid = start + (end - start) / 2;
 
        if (value <= datas[mid])
        {
            if ((mid == 0) || (datas[mid - 1] < value)) return mid;
            end = mid - 1;
        }
        else
        {
            start = mid + 1;
        }
    }
 
    return -1;
}

变体四:查找最后一个小于等于给定值的元素

/**
 * 查找最后一个值大于等于(不小于)给定值的元素
*/
public int FindLastNotMoreThan(int[] datas, int value)
{
    if (datas == null || datas.Length == 0) return -1;
 
    int start = 0, end = datas.Length - 1, n = datas.Length;
    int mid;
    while (start <= end)
    {
        mid = start + (end - start) / 2;
 
        if (value < datas[mid])
        {
            end = mid - 1;
        }
        else
        {
            if ((mid == n - 1) || (datas[mid + 1] > value)) return mid;
            start = mid + 1;
        }
    }
 
    return -1;
}

相关文章

  • 排序算法

    算法与数据结构基础 查找算法: 二分查找法: 简介:二分查找法又被称为折半查找法,用于预排序的查找问题 过程: 如...

  • 2019-10-27文章阅读

    二分查找(上):如何用最省内存的方式实现快速查找功能? 来源:王争的数据结构与算法之美时间复杂度为O(logn) ...

  • 20181205_ARTS_W9

    Algorithm 数据结构与算法之美之变形二分查找大前提:都是有序数组,可能存在重复元素 查找第一个等于某个数值...

  • 数据结构与算法

    数据结构与算法之美 数据结构与算法之美1--如何学数据结构与算法之美2--复杂度分析(上)数据结构与算法之美3--...

  • 带领初学者学习数据结构与算法免费视频教程

    带领初学者学习数据结构与算法免费视频教程(2 个视频) 详解使用 JavaScript 实现二分查找算法「12:3...

  • 二分查找

    学习极客时间的数据结构与算法之美的专栏,记录笔记。 1 二分查找应用场景的局限性 (1)二分查找依赖的是顺序表结构...

  • 数据结构与算法--散列表

    数据结构与算法--散列表 之前学习了基于链表的顺序查找、基于有序数组的二分查找、二叉查找树、红黑树,这些算法在查找...

  • 数据结构与算法 树 引言 顺序查找 ​ 哨兵的方式 ​ 时间复杂度为O(N) 二分查找查找树的形式我...

  • 《数据结构与算法之美》12——二分查找

    二分查找是一种非常简单易懂的快速查找算法,生活中随处可见,比如数字炸弹游戏。 二分查找针对的是一个有序的数据集合,...

  • 常考的数据结构与算法之算法

    本文记录一下我学习数据结构与算法中算法的知识点,使用的语言是C/C++语言 查找 二分查找又叫折半查找,要求线性表...

网友评论

      本文标题:《数据结构与算法之美》12——二分查找

      本文链接:https://www.haomeiwen.com/subject/zxrwkktx.html