二进制搜索和不变关系

时间:2014-10-25 16:30:44

标签: algorithm search binary-search

我正在阅读此post并尝试弄清楚如何确定二元搜索的不变关系。具体来说,在他给出的两个例子中,为什么这两个不变的关系是不同的?是什么让它与众不同?

A部分[开始]<目标< [结束]很明显,但问题是在哪里放置=符号?

另一个问题是,我可以简单地将框架更改为:

int binarySearchFramework(int A[], int n, int target) {
    int start = start index of array - 1;
    int end = length of the A;
    while (end - start > 1) {
        int mid = (end - start) / 2 + start;
        if (A[mid] == target) return mid;
        if (A[mid] < target) {
            end = mid;
        } else {
            start = mid;
        }
    }      
   //not found
   ...
}  

这个不如帖子中提供的那个好吗?

非常感谢!

3 个答案:

答案 0 :(得分:3)

你可以选择不变量。这是从实践中学到的技能。即使有经验,它通常也会涉及一些反复试验。选一个。看看它如何。寻找机会选择一个需要较少维护工作的机会。您选择的不变量可以对代码的复杂性和/或效率产生重大影响。

二元搜索中至少有四个合理的不变量选择:

a[lo] <  target <  a[hi]
a[lo] <= target <  a[hi]
a[lo] <  target <= a[hi]
a[lo] <= target <= a[hi]

你通常会看到最后一个因为它是最容易解释的,并且不涉及使用超出范围的数组索引进行棘手的初始化,而其他人则这样做。

现在 是使用a[lo] < target <= a[hi]等不变量的理由。如果您希望始终在目标的重复系列中找到,则此不变量将执行O(log n)时间。当hi - lo == 1时,hi指向第一次出现的目标。

int find(int target, int *a, int size) {
  // Establish invariant: a[lo] < target <= a[hi] || target does not exist
  // We assume a[-1] contains an element less than target. But since it is never
  // accessed, we don't need a real element there.
  int lo = -1, hi = size - 1;
  while (hi - lo > 1) {
    // mid == -1 is impossible because hi-lo >= 2 due to while condition
    int mid = lo + (hi - lo) / 2;  // or (hi + lo) / 2 on 32 bit machines
    if (a[mid] < target)
      lo = mid; // a[mid] < target, so this maintains invariant
    else
      hi = mid; // target <= a[mid], so this maintains invariant
  }
  // if hi - lo == 1, then hi must be first occurrence of target, if it exists.
  return hi > lo && a[hi] == target ? hi : NOT_FOUND;
}

注意,此代码未经测试,但应该由不变逻辑工作。

两个<=的不变量只会找到目标的某些实例。你无法控制哪一个。

此不变 需要使用lo = -1进行初始化。这增加了证明要求。您必须证明mid永远不能设置为-1,这会导致超出范围的访问权限。幸运的是,这个证据并不难。

你引用的文章很差。它有几个错误和不一致。寻找其他地方的例子。编程珍珠是一个不错的选择。

您建议的更改是正确的,但可能会有点慢,因为它取代了一次只运行一次的测试,每次迭代运行一次。

答案 1 :(得分:1)

问题的答案是问题的答案&#34;什么是循环不变量&#34;。

循环不变量的要点是在循环终止之前,期间和(可能最重要的)之后提供有用的属性。作为示例,插入排序具有循环不变量,即要排序的数组按照从1索引开始的范围的排序顺序(一个项始终排序),并且增长为整个数组。 这有用的是,如果在循环开始之前它是真的,并且循环没有违反它,你可以正确地推断出在执行循环之后整个数组都被排序了。假设你并没有弄乱你的终止条件,它没有违反循环不变量,因为不变量只是指整个数组的子数组,可能是也可能不是整个数组。如果提前终止,则子数组小于整个数组,但保证子数组按照不变量进行排序。

你链接的帖子说的大致相同,但如果作者真正解释了他所谈论的内容可能会更好。这篇文章似乎试图教导,但仍然没有说出应该说的,即使只是为那些好奇或需要更多信息的人提供更深入的信息的脚注。

回答你的问题&#34;为什么两个不变量不同&#34;直接,答案是因为他们正在解决两个不同的问题。

链接中的几个引用说明了这一点:

  • 我再次强调,不变关系引导我们编码。
  • 找到问题的不变关系,然后一切都变得容易。

答案 2 :(得分:-1)

你写了

  

A部分[开始]&lt;目标&lt; [结束]很明显

但显然是错误的,因为初始值应该是start = 0,end = N-1(不是-1,N)。顺便说一句,对于链接中描述的情况(不同元素的数组),您不需要任何不变量。

这将毫无问题且易于理解。

int arr[] = {0,1,2,3,4,5,6,7};
int N = sizeof (arr) / sizeof (arr[0]);
int target = 4;

int l = 0, r = N-1;
while( l <= r ) {
    int mid = (l+r)>>1;
    if( arr[mid] == target )
        return mid;
    if( arr[mid] < target )
        l = mid + 1;
    else
        r = mid - 1;
}
return -1; // not found