我们如何通过归纳证明二分搜索是正确的?

时间:2012-12-04 04:39:55

标签: algorithm search binary-search proof

我很难理解感应如何与一些不变量一起用来证明算法的正确性。也就是说,如何找到不变量,何时使用归纳假设 - 特别是二元搜索?我还没有找到直观的回复,所以我希望有人可以在这里阐述一下这个话题。

4 个答案:

答案 0 :(得分:12)

我们假设二进制搜索的定义如下:

def binary_search(a,b,e,x):
  n = e-b
  if n==0: return None
  m = b + int(n/2)
  if x<a[m]: return binary_search(a,b,m,x)
  if x>a[m]: return binary_search(a,m+1,e,x)
  return m

其中

  • a是值数组 - 假设已排序
  • [b,e)是从b到e的范围,包括b但不包括e,我们正在搜索。
  • x是我们正在搜索的值

该函数的目标是返回i,其中a [i] == x如果有i的值,否则返回None。

binary_search适用于零大小的范围:

  • 证明:如果范围不包含任何元素,则n == 0且函数返回None,这是正确的。

假设binary_search适用于从0到n的任何大小的元素范围,则二进制搜索适用于大小为n + 1的元素范围。

  • 证明:

    因为数组是排序的,如果x&lt; a [m],则x < a [k]表示所有k>米这意味着我们只需要搜索范围[b,m]。范围[b,m]必然小于范围[b,e),并且我们假设二分搜索适用于小于n + 1的所有大小范围,因此它适用于[b,m]。

    如果x> a [m],然后适用类似的逻辑。

    如果x == a [m],那么函数将返回m,这是正确的。

答案 1 :(得分:3)

/** Return an index of x in a.
 *  Requires: a is sorted in ascending order, and x is found in the array a
 *  somewhere between indices left and right.
 */
int binsrch(int x, int[] a, int left, int right) {
  while (true) {
    int m = (left+right)/2;
    if (x == a[m]) return m;
    if (x < a[m])
    r = m−1;
    else
    l = m+1;
  }
}

关键的观察是,binsrch以分而治之的方式运作,仅仅以某种方式“较小”的论据来称呼它自己。

设P(n)为binsrch对右 - 左= n的输入正确工作的断言。如果我们能够证明P(n)对于所有n都是正确的,那么我们知道binsrch适用于所有可能的参数。

基础案例。在n = 0的情况下,我们知道left = right = m。由于我们假设只在左右之间找到x时才调用该函数,因此必须是x = a [m]的情况,因此函数将返回m,即数组a中x的索引。

归纳步骤。我们假设binsrch只要左右≤k就可以工作。我们的目标是证明它适用于左右= k + 1的输入。有三种情况,其中x = a [m],其中x <1。 a [m],其中x>一个[M]。

    Case x = a[m]. Clearly the function works correctly.

    Case x < a[m]. We know because the array is sorted that x must be found between a[left] and a[m-1]. The n for the recursive call is n = m − 1 − left = ⌊(left+right)/2⌋ − 1 − left. (Note that ⌊x⌋ is the floor of x, which rounds it down toward negative infinity.) If left+right is odd, then n = (left + right − 1)/2 − 1 − left = (right − left)/2 − 1, which is definitely smaller than right−left. If left+right is even then n = (left + right)/2 − 1 − left = (right−left)/2, which is also smaller than k + 1 = right−left because right−left = k + 1 > 0. So the recursive call must be to a range of a that is between 0 and k cells, and must be correct by our induction hypothesis.

    Case x > a[m]. This is more or less symmetrical to the previous case. We need to show that r − (m + 1) ≤ right − left. We have r − (m + 1) − l = right − ⌊(left + right)/2⌋ − 1. If right+left is even, this is (right−left)/2 − 1, which is less than right−left. If right+left is odd, this is right− (left + right − 1)/2 − 1 = (right−left)/2 − 1/2, which is also less than right−left. Therefore, the recursive call is to a smaller range of the array and can be assumed to work correctly by the induction hypothesis. 

因为在所有情况下归纳步骤都有效,我们可以得出结论,binsrch(及其迭代变体)是正确的!

请注意,如果我们在编码x&gt;时犯了错误一个[m]的情况,并将m作为左边而不是m + 1(很容易做到!),我们刚刚构建的证明在那种情况下会失败。事实上,当right = left + 1时,算法可以进入无限循环。这显示了仔细归纳推理的价值。

参考:http://www.cs.cornell.edu/Courses/cs211/2006sp/Lectures/L06-Induction/binary_search.html

答案 2 :(得分:3)

我们假设排序的数组是a[0...n]。二进制搜索通过递归地将此数组划分为三个块,中间元素m,其中所有元素的左侧部分为<= m(因为数组按假设排序) )并且所有元素的正确部分为>=m(同样,因为数组按假设排序)。

如何制定不变量?

让我们首先考虑二分查找的工作原理。如果密钥(正在搜索的项目)为k,则我将其与中间元素m进行比较。

  1. 如果k = m,我找到了我的项目(无其他事情可做)

  2. 如果k < m ,我肯定知道如果k中出现a,它就不会出现在数组的右侧部分,因为所有元素都在数组的那部分是>= m > k。所以它必须出现在数组的左侧部分(如果有)

  3. 如果k > m ,我肯定知道.....

  4. 那么,在这种递归计算的每一步,我们可以保证?在每个步骤中,我们都可以识别两个索引i, j并声明“如果ka[0...n]的元素,那么它必须出现在i, j之间。这是不变,因为它适用于所有递归步骤,并且每一步都会挤压此范围i, j,直到找到您的商品或此范围变为为空(i < j时范围为非空。)

    感应如何运作?

    • 对于基本情况,您需要i = 0, j = n。不变量保持平凡。

    • 对于归纳步​​骤,假设不变量适用于p的递归步骤i = i_p & j = j_p。然后你必须证明,对于下一个递归步骤,i, j得到更新,使得不变量仍然成立。这是你必须使用上面步骤2)和3)中的参数的地方。

    • 范围i, j严格减少,因此计算必须终止。

    我错过了什么吗?

答案 3 :(得分:1)

您应该在二元搜索的每个步骤arr[first] <= element <= arr[last]

之后证明

从此和终止,您可以得出结论,一旦二进制搜索终止arr[first] == element == arr[last]