概念

假设要在电话簿中找一个名字以K打头的人,可以从头开始翻页,直到进入以K打头的部分。但你很可能不这样做,而是从中间开始,因为你知道以K打头的名字在电话簿中间。
又假设要在字典中找一个以O打头的单词,你也将从中间附近开始。

这是一个查找问题,在前述所有情况下,都可以使用同一种算法来解决问题,这种算法就是二分查找
二分查找是一种算法,其输入是一个有序的元素列表,二分查找返回其位置;否则返回null.

优势

假设有1-100的数字,你的目标是以最少的次数猜到这个数字。假设你从1开始往上猜,如果我想的数字是99,你得猜99次才能猜到。这也被称为简单查找

有一种更佳的猜法,即从50开始。如果小了,就排除了一半的数字。至此,你知道1-50都小了。接下来,你猜75.如果大了,那余下的数字又排除了一半……

一般而言,对于包含n个元素的列表,用二分查找最多需要log2 n步,而简单查找最多需要n步。
(本文使用大O表示法讨论运行时间时,log指的都是log2).
当且仅当列表是有序的时候,二分查找才管用

代码实现

函数binary_search接受一个有序数组和一个元素。如果指定元素包含在数组中,这个函数将返回其位置。

  1. 首先你将跟踪要在其中查找的数组部分,开始时为整个数组。
low = 0;
high = len(list) - 1;
  1. 你每次都检查中间的元素。
mid = (low + high) / 2
//如果(low + high)不是偶数,Python自动将mid向下取整
guess = list[mid]
  1. 如果猜的数字小了,就相应地修改low.
if guess < item:
low = mid + 1

JavaScript实现:

function findEven(array, item) {
        let low = 0;
          let high = array.length - 1;
          let mid, guess;
          while (low <= high) {
              mid = Math.floor((low + high) / 2);
            guess = array[mid];
            if (guess === item) {
                return mid;
            }
            if (guess > item) {
                high = mid - 1;
            } else {
                low = mid + 1;
            }    
          }
          return null;
    }

运行时间

每次介绍算法时,都会讨论其运行时间。一般而言,应选择效率最高的算法,以最大限度地减少运行时间或占用空间。

上面讲到的简单查找,如果列表包含100个数字,最多需要猜100次。如果列表包含40亿个数字,最多需要猜40亿次。换言之,最多需要猜测的次数与列表长度相同,这被称之为线性时间
二分查找则不同。如果列表包含100个元素,最多需要猜7次;如果列表包含40亿个数字,最多需要猜32次。二分查找的运行时间为对数时间

我们仅知道算法需要多长时间才能运行完毕还不够,还需知道运行时间如何随列表增长而增加。这正是大O表示法的用武之地。
大O表示法指出了算法有多快,它没有单位。大O表示法让你能够比较操作数,它指出了算法运行时间的增速。

大O表示法指出了最糟糕情况下的运行时间

假设你使用简单查找在电话簿中招人。你知道,简单查找的运行时间为O(n),这意味着在最糟的情况下,必须查看电话簿中的每个条目。
如果要查找的的Adit---电话簿中的第一个人,一次就能找到,无需查看每个条目。
考虑到一次就找到了Adit,请问这种算法的运行时间是O(n)还是O(1)呢?

简单查找的运行时间总是为O(n).查找Adit时,一次就找到了,这是最佳的情形,但大O表示法说的是最糟的情形

常见的大O运行时间

下面按从快到慢的顺序列出了你经常会遇到的5种大O运行时间。

  • O(log n),也叫对数时间,这样的算法包括二分查找。
  • O(n),也叫线性时间,这样的算法包括简单查找。
  • O(n * log n),这样的算法包括快速排序
  • O(n ^ 2),这样的算法包括选择排序
  • O(n!)这样的算法包括接下来介绍的旅行商问题的解决方案。

在实际运用过程中,我们并不能如此干净利落地将大O运行时间转换为操作数,但就目前而言,这种准确度足够了。我们需要记住的几个结论如下:

  • 算法的速度指的并非时间,而是操作数的增速。
  • 谈论算法的速度时,我们说的是随着输入的增加,其运行时间将以什么样的速度增加。
  • 算法的运行时间用大O表示法表示
  • O(log n)比O(n)快,当需要搜索的元素越多时,前者比后者快得越多。

Zuckjet
437 声望657 粉丝

学如逆水行舟,不进则退。