如何使存储数组的二进制搜索稳定

How to make binary search of a stored array to be stable

本文关键字:搜索 二进制 何使 存储 数组      更新时间:2023-10-16

这是在排序数组中对元素进行二进制搜索的代码:

#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

我看不出该怎么做。

您可以将匹配的条件从 arr[mid] == data 更改为更复杂的arr[mid] == data && (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)最佳情况。

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

  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

请考虑将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;
在这里,

我更改了您的代码,以便它检查找到的每个元素是否也等于搜索的元素。

    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 - 1mid + 1 )。这是代码(抱歉更改格式)。

   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);

有了<algorithm>,你可以做

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);
}

演示