【问题标题】:rotated ordered array search旋转有序数组搜索
【发布时间】:2016-04-26 07:08:07
【问题描述】:

处理以下算法难题。发布问题陈述和解决方案。问题是,我们是否需要“搜索两半”部分来保证它的安全?或者当a[left] == a[mid]时,我们可以只搜索右边部分而不检查是否a[mid] == a[right] -- 因为当a[left] == a[mid]时,我认为左边的所有元素都是相等的,不能满足搜索条件来找到值。

更详细地说,我的意思是写 last else if as 是否安全,

else if (a[left] == a[mid]) {
            return search(a, mid + 1, right, x);
    }

问题陈述

给定一个由 n 个整数组成的排序数组,该数组已经旋转了未知次数,编写代码来查找一个元素 在数组中,你可以假设数组最初是按升序排序的

例如, 在 {15, 16, 19, 20, 25, 1, 3, 4, 5, 7, 10, 14} 中输入 find 5 输出,8(数组中5的索引)

代码

public static int search(int a[], int left, int right, int x) {
    int mid = (left + right) / 2;
    if (x == a[mid]) { // Found element
        return mid;
    }
    if (right < left) {
        return -1;
    }

    /* While there may be an inflection point due to the rotation, either the left or 
     * right half must be normally ordered.  We can look at the normally ordered half
     * to make a determination as to which half we should search. 
     */
    if (a[left] < a[mid]) { // Left is normally ordered.
        if (x >= a[left] && x < a[mid]) { 
            return search(a, left, mid - 1, x);
        } else {
            return search(a, mid + 1, right, x);
        }
    } else if (a[mid] < a[left]) { // Right is normally ordered.
        if (x > a[mid] && x <= a[right]) {
            return search(a, mid + 1, right, x);
        } else {
            return search(a, left, mid - 1, x);
        }               
    } else if (a[left] == a[mid]) { // Left is either all repeats OR loops around (with the right half being all dups)
        if (a[mid] != a[right]) { // If right half is different, search there
            return search(a, mid + 1, right, x);
        } else { // Else, we have to search both halves
            int result = search(a, left, mid - 1, x); 
            if (result == -1) {
                return search(a, mid + 1, right, x); 
            } else {
                return result;
            }
        }
    }
    return -1;
}

【问题讨论】:

  • 我认为这是必不可少的。你能给出完整的问题描述吗?
  • @AbuHanifa,感谢您的提问和投票,您的意思是问题陈述部分吗?这是完整的陈述。你对哪些部分感到困惑?我很高兴澄清。

标签: java algorithm sorting


【解决方案1】:

就您的问题而言,您的假设是完全正确的,您不必在那里搜索两半。你可以只搜索右半部分,因为如果a[mid] != key,你的元素肯定在右半部分,如果它在数组中。

编辑:

以上结论是不完整的。我现在写一个新的,对此有一些进一步的考虑。

首先,如果在任何时候,您都在搜索两半,那么进行二分搜索毫无意义,因为搜索的复杂性变为O(n)

现在,如果a[mid] == a[left],您的密钥可以在任何一半中。但是,您可以确定的一件事是,其中一半的所有元素都相同。

eg. Suppose a[12] = [1,1,1,2,2,2,2,2,2,2,2,2]
    rotate r=2 times, a`[12] = [2,2,1,1,1,2,2,2,2,2,2,2]
    so, a[mid] = a[left] = 2
    if key = 1, it is in left half.

    now, rotate r=9 times,
         a``[12] = [2,2,2,2,2,2,2,2,2,1,1,1]
    so, a[mid] = a[left] = 2
    if key = 1, it is in right half

因此,您需要在该数组的两半中搜索您的密钥,而这种情况实际上使二进制搜索变得多余。

所以,现在,我什至建议您使用我在下面提到的解决方案。

虽然没有限制,但我想提出一个解决方案:

这是Binary Search 算法的一个简单变体。

您首先必须在数组中应用二进制搜索,并带有终止条件:

a[mid-1] > a[mid]

一旦满足此条件,您就有了索引k = mid,它为您提供了 2 个已排序的数组。

第一个数组A[0...k-1] 和第二个数组A[k...n-1],假设n 元素。

现在,请检查一下。

if(key > A[0])
    Binary Search in A[0...k-1]
else
    Binary Search in A[k...n-1]

有一个例外,如果数组已旋转n 次,您将不会得到k 的值。对整个数组进行二分查找。

EDIT2:回答 cmets 的问题

我想知道如果 a[left] = a[left] && x

如果a[left] &lt; a[mid],那么我们可以确定a[left...mid]是升序(排序)的。所以,如果x &gt;= a[left] &amp;&amp; x &lt; a[mid],只在左半边搜索就足够了。

如果您还可以澄清 a[left] == a[mid] 和 a[mid] == a[right] 时,我们需要搜索两个方向吗?

是的。在这种情况下,我们需要搜索两个方向。说,例如。您的原始数组是 {1,2,2,2,2,2,2,2,2,2} 旋转一次。数组变为{2,1,2,2,2,2,2,2,2,2}。如果旋转 8 次,则变为{2,2,2,2,2,2,2,2,1,2}。现在,如果您的搜索关键字是1,那么在这两种情况下,在开始时都是a[mid] == a[left] == a[right],并且您的关键字位于不同的两半中。因此,您需要在两半中搜索密钥。

【讨论】:

  • 是的...即使数组旋转了n 次,如果a[mid] == a[left],则隐含的结论是mid = left 或左半部分的所有元素都相等。所以,你可以只检查右半部分的元素。
  • @LinMa,另外,我建议你做一个二分搜索的通用实现,这样你每次只需要编写条件函数,而不是整个二分搜索。
  • 好的...对此进行进一步思考...我已对答案进行了编辑。 (之前的结论是错误的)
  • @LinMa,我在答案中的 Edit-2 回答了你的问题……不是吗?
  • 谢谢 vish4071,你是对的。我错过了 Edit-2,只看到第一个编辑。将您的回复标记为答案。 :)
【解决方案2】:

您需要考虑两件事..

首先,由于数组旋转意味着它就像一个循环缓冲区,因此您需要以不同的方式访问数组以避免超出数组边界。

Normal

    array[index]

Circular

    array[index % size]

其次你需要考虑旋转,第一个有序项不是索引0,假设它是第一个索引f,所以要访问有序索引i,你使用i+f

Normal

    array[index]

Circular

    array[index % size]

Rotated

    array[(index + first) % size]

要找到旋转有序数组的第一项,我们可以检查排序条件在哪里中断..

// assuming ascending order a[0] <= a[1]

int find_first_index (int [] arr)
{
    for(int i = 0; i < arr.length; i++)
    {
        if(arr[i] > arr[(i+1) % arr.length])
        {
            return (i+1) % arr.length;
        }
    }

    return 0;
}

现在您只需要在排序之前获取第一个索引..

int f = find_first_index(arr);

然后将排序算法中的所有数组访问替换为[(i+f) % arr.length],而不是arr[i]。所以你的代码变成了这样..

public static int startSearch(int a[], int x)
{
    int f = find_first_index(a);
    return search(a, f, 0, (a.length-1), x);
}

public static int search(int a[], int f, int left, int right, int x)
{
    int s = a.length;
    int mid = (left + right) / 2;
    if (x == a[(mid+f)%s]) { // Found element
        return mid;
    }
    if (right < left) {
        return -1;
    }
..

.. The complete source-code is Here

【讨论】:

  • @LinMa 我的想法是将问题简化为 2 个问题,其中之一是二进制搜索,您已经有了解决方案。而不是试图提出一个复杂的算法来解决整个问题。
  • @LinMa 我对find_first_index 的实现给出了O(n),这使得search 给出了O(n + log n)。它可以重新实现,使其O(log n) 使search 具有O(2 log n) ~ O(log n)。但想法保持不变。
  • @LinMa 假设它在中间某处旋转,您可以根据与第一项和最后一项的比较,而不是对整个列表进行二进制搜索,而是对两半中的一个进行二进制搜索。所以如果key =&gt; a[0]搜索右半部分,如果key &lt;= a[size-1]搜索左半部分,否则找不到。
  • @LinMa 是的,有一种方法可以在O(log n) 中找到中点,有时间我会添加它。
  • @LinMa a[left] == a[mid]a[mid] == a[right] 在一种情况下会发生,如果数组具有相同的项目,例如 { 2, 2, 2, .. 2 .. 2, 2, 2 }