数据结构和算法之——二分查找上

原文链接: https://segmentfault.com/a/1190000016793795
二分查找(Binary Search)的思想非常简单,但看似越简单的东西往往越难掌握好,想要灵活运用就更加困难。

1. 二分查找的思想?

生活中二分查找的思想无处不在。一个最常见的就是猜数游戏,我随机写一个 0 到 99 的数,然后你来猜我写的是什么。猜的过程中,我会告诉你每次是猜大了还是猜小了,直到猜中为止。假如我写的数是 23,猜数过程如下所示。

最多只需要 7 次就猜出来了,这个过程是很快的。同理,要查找某个数据是否在给定的数组中,我们同样也可以利用这个思想。

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

2. 二分查找的时间复杂度?

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

若经过 k 次区间缩小最后变为空,则 $\frac{n}{2^k} = 1, k = log_2n$,所以二分查找的时间复杂度为 $O(logn)$。

这种对数时间复杂度的算法是一种非常高效的算法,有时候甚至比时间复杂度为常量级的算法还要高效。因为常量级的时间复杂度对应的常数可能非常大,比如 O(100), O(1000),因此这些算法有时候可能还没有 $O(logn)$ 的算法执行效率高。

2. 简单二分查找的算法实现?

循环法

float Binary_Search(float data[], int left, int right, float value)
{
    while (left <= right)
    {
        int mid = left + (right - left) / 2;
        if (value == data[mid])
        {
            return mid;
        }
        else if (value < data[mid])
        {
            right = mid - 1;
        }
        else
        {
            left = mid + 1;
        }
    }

    return -1;
}

递归法

float Binary_Search(float data[], int left, int right, float value)
{
    if (left <= right)
    {
        int mid = left + (right - left) / 2;
        if (value == data[mid])
        {
            return mid;
        }
        else if (value < data[mid])
        {
           return Binary_Search(data, left, mid-1, value);
        }
        else
        {
            return Binary_Search(data, mid+1, right, value);
        }
    }

    return -1;
}

注意事项

  • 循环退出条件 left <= right
  • mid = left + ((right-left) >> 1),用移位运算优化计算性能
  • left 和 right 的更新分别是 mid+1 和 mid-1

3. 二分查找的应用场景?

二分查 找依赖的是顺序表结构,也就是数组,需要能够按照下标随机访问元素

二分查找针对的是有序数据,如果数据无序,需要先进行排序。而如果有频繁的插入、删除操作,则每次查找前都需要再次排序,这时候,二分查找将不再适用。

数据量太小可以直接遍历查找,没有必要用二分查找。但如果数据之间的比较操作非常耗时,比如数据为长度超过 300 的字符串,则不管数据量大小,都推荐使用二分查找。

而如果数据量过大,则意味着我们需要用非常大的连续内存空间来存储这些数据,内存开销可能无法满足

获取更多精彩,请关注「seniusen」!

你可能感兴趣的:(数据结构和算法之——二分查找上)