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

时间:2016-06-10 11:17:01

标签: c++ c algorithm data-structures

以下是对已排序数组中的元素进行二进制搜索的代码:

#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

我看不出怎么做。

5 个答案:

答案 0 :(得分: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 :(得分:1)

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

  1. 只需从找到的元素开始在列表中向后移动,直到到达第一个相等的元素(取 O(n) n =相等元素的数量)

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

  3. 这里是版本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
    

答案 2 :(得分:0)

请考虑使用以下内容替换return mid;内的int binarySearch(int *arr, int l, int r, int data)

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

答案 3 :(得分:0)

我已经更改了您的代码,因此它会检查找到左边的每个元素是否也等于搜索元素。

    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而不是{{1} }或mid - 1)。 这是代码(抱歉更改格式)。

mid + 1

答案 4 :(得分:0)

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

Demo