【问题标题】:How to make binary search of a stored array to be stable如何使存储数组的二进制搜索稳定
【发布时间】:2025-12-21 06:30:12
【问题描述】:

下面是对有序数组中的元素进行二分查找的代码:

#include<stdio.h>
int binarySearch(int *arr, int l, int r, int data)
{
    if(l > r)
        return -1;

    int mid = l+(r-l)/2;    //find the middle index 

    if(data < arr[mid]) {
        return(binarySearch(arr, l, mid-1, data));
    }
    else if(data > arr[mid]) {
        return(binarySearch(arr, mid+1, r, data));
    }
    else {
        return mid;
    }        
}

int main()
{
    int arr [] = {0 , 11, 22, 33, 44, 55, 66 };
    int n = sizeof(arr)/sizeof(arr[0]);     
    int data = 22;
    int index = binarySearch(arr, 0, n-1, data);
    if( index != -1) 
    {
          printf("%d" , index);
    }
    return 0;          
}

如何使搜索稳定?当数组的元素重复时,我的搜索应该返回数组中数据第一次出现的索引。

我希望我修改后的代码作为输出产生:

input array is {1, 22, 22, 22}
output = 1, 
input array is {1, 12, 15, 22, 22, 22, 22, 22, 22, 22, 55 ,66}
output = 3

我不知道该怎么做。

【问题讨论】:

  • 您的示例无法编译(data 未在 main() 中定义)并且它不应该编译的 #include &lt;stdio.h&gt;(对于 printf)。
  • {1, 12, **25**, 22, 22, .. 未排序

标签: c++ c algorithm data-structures


【解决方案1】:

您可以将匹配条件从arr[mid] == data 更改为更复杂的arr[mid] == data &amp;&amp; (mid == 0 || arr[mid-1] != data)。变化:

    else {
        return mid;
    }        

到:

    else if (mid == 0 || arr[mid-1] != data) {
        // note that arr[mid] == data is implied at this point
        return mid;
    }
    else {
        return(binarySearch(arr, l, mid, data));
    }

如果数组中存在大量搜索值,这仍然会为您提供 O(log(n)) 性能(与其他一些更简单的解决方案相比,在这种情况下会降低到 O(n) 性能)。您还保留了原始搜索的 O(1) 最佳情况:也就是说,可能会找到结果,而不会发生任何递归。

请注意,它确实假设可以访问下限 (l) 之外的数组,但前提是该边界不为 0,而原始代码没有做出这样的假设。在您发布的示例中,这不是问题。如果这是一个问题,您可以将原始绑定向下传递(例如,上面的 ol,然后上面的 mid == 0 变为 mid == ol),或者改为使用:

else if (mid == l) {
    return mid;
}
else {
    return(binarySearch(arr, l, mid - 1, data));
}

然而,后者失去了 O(1) 的最佳情况。

【讨论】:

  • 感谢@davmac 的回复。我们不能让 binarySearch 在最后一个“else”中对“mid-1”执行“l”,因为我们知道 arr[mid-1] == mid。所以其他情况将是 return(binarySearch(arr, l, mid-1, data));
  • @viky 我最后的评论;是的你可以。我会更新答案。
【解决方案2】:

根据您期望的相等元素的数量,这里有两种方法:

  1. 只需从找到的元素开始在列表中倒退,直到到达第一个相等的元素(需要 O(n) n = 相等元素的数量)

  2. 在从索引 0 开始并以找到元素的索引结束的子数组中再次搜索。这样做,直到新的声音元素具有与之前找到的相同的索引。

这里是版本 2 的插图(让每个字符成为一个元素)并寻找 B

AAAABBBBBBBBBBBBBBBBBBBBBBCDDDDEEEFFFZ
^                                    ^  search range

AAAABBBBBBBBBBBBBBBBBBBBBBCDDDDEEEFFFZ
^                 !                  ^  found at position !

AAAABBBBBBBBBBBBBBBBBBBBBBCDDDDEEEFFFZ
^                 ^  new search range

AAAABBBBBBBBBBBBBBBBBBBBBBCDDDDEEEFFFZ
^        !        ^  found at position ! 
(different from previous finding position)

AAAABBBBBBBBBBBBBBBBBBBBBBCDDDDEEEFFFZ
^        ^  new search range

AAAABBBBBBBBBBBBBBBBBBBBBBCDDDDEEEFFFZ
^    !   ^   found at position ! 
(different from previous finding position)

AAAABBBBBBBBBBBBBBBBBBBBBBCDDDDEEEFFFZ
^    ^  new search range

AAAABBBBBBBBBBBBBBBBBBBBBBCDDDDEEEFFFZ
^   !^   found at position ! 
(different from previous finding position)

AAAABBBBBBBBBBBBBBBBBBBBBBCDDDDEEEFFFZ
^   ^  new search range

AAAABBBBBBBBBBBBBBBBBBBBBBCDDDDEEEFFFZ
^   !  found at same position as before => lirst one

【讨论】:

  • 这个(第二个)解决方案也有效,但不是最有效的(尽管我认为它仍然是 O(log(n)) 复杂度,这与你能得到的一样好)。无需将下界重置为0;查看我的解决方案。
【解决方案3】:

考虑将int binarySearch(int *arr, int l, int r, int data) 内的return mid; 替换为以下内容:

for(; (mid > 0) && (data == arr[mid]); mid--);
return (data == arr[mid]) ? mid : mid + 1;

【讨论】:

  • 当然这有效,但在有 很多 个元素具有相同(搜索)值的(病态)情况下,它会降低到线性性能。可以继续使用二进制搜索。
  • (另外,您的代码允许 mid 达到 -1,这将导致使用return 的行的数组访问越界)。
  • @davmac 首先,您在第二条评论中是正确的,我已经修复了代码。其次,您不能继续进行二分搜索,因为您永远不会知道您的值是否重复,您必须继续。此外,如果您继续在以下数组中对 2 进行二分搜索:1 2 2 3 4,您最终会得到 1。还有更多,您怎么知道这是序列的第一个值?简而言之,我没有看到线性搜索的任何替代方法来确保命中 first 元素
  • 查看我的回答,了解如何继续使用二分搜索。
  • @davmac 你值得拥有你的声誉。我为你 +1
【解决方案4】:

在这里,我已经更改了您的代码,因此它也会检查找到的左侧的每个元素是否与搜索的元素相等。

    if(data < arr[mid]) {
        return(binarySearch(arr, l, mid-1, data));
    }
    else if(data > arr[mid]) {
        return(binarySearch(arr, mid+1, r, data));
    }
    else {
        while(mid && data == arr[--mid]);
        return mid + 1;
    }      

但是如果你的整个数组由相同的元素组成,它可能会很慢。其他解决方案是继续搜索,但您需要记住,找到的元素是有效的并且可能是唯一有效的元素,因此您永远不应该在下一次递归调用中丢失它(使用 mid 而不是 mid - 1 或 @987654324 @)。 这是代码(抱歉更改格式)。

   if (data == arr[mid]) {
        if (r - l == 0) {
            return mid;
        }
        return binarySearch(arr, l, mid, data);
    }
    if(data < arr[mid])
        return binarySearch(arr, l, mid-1, data);
    return binarySearch(arr, mid+1, r, data);

【讨论】:

    【解决方案5】:

    &lt;algorithm&gt;,你可以这样做

    int binarySearch(const int *arr, int l, int r, int data)
    {
        // inclusive `r` for binarySearch
        auto it = std::lower_bound(arr + l, arr + r + 1, data);
    
        if (it == arr + r + 1 || *it != data) {
            return -1;
        }
        return std::distance(arr + l, it);
    }
    

    Demo

    【讨论】: