使用二进制搜索查找多个条目

时间:2012-08-27 15:18:52

标签: algorithm binary-search

我使用标准二进制搜索快速返回排序列表中的单个对象(相对于可排序属性)。

现在我需要修改搜索,以便返回 ALL 匹配列表条目。我该怎么做才最好呢?

14 个答案:

答案 0 :(得分:18)

好吧,由于列表已排序,您感兴趣的所有条目都是连续的。这意味着您需要找到与找到的项目相等的第一个项目,从二进制搜索生成的索引向后查找。关于最后一项也是如此。

你可以简单地从找到的索引向后移动,但是这样解决方案可能和O(n)一样慢,如果有很多项目等于找到的项目。所以你应该更好地使用指数搜索:当你发现更多相等的项目时,你的跳跃加倍。这样你的整个搜索仍然是O(log n)。

答案 1 :(得分:15)

首先让我们回想起天真的二进制搜索代码片段:

int bin_search(int arr[], int key, int low, int high)
{
    if (low > high)
        return -1;

    int mid = low + ((high - low) >> 1);

    if (arr[mid] == key) return mid;
    if (arr[mid] > key)
        return bin_search(arr, key, low, mid - 1);
    else
        return bin_search(arr, key, mid + 1, high);
}
  

来自Dr.Skiena的引言:   假设我们删除了等式测试if(s [middle] == key)   返程(中);从上面的实现和返回索引低   在每次不成功的搜索中代替-1。现在所有搜索都是   不成功,因为没有相等的测试。搜索将继续进行   只要将密钥与相同的数组进行比较,就会向右半部分   元素,最终终止于右边界。重复   反转二进制比较方向后搜索   引导我们到左边界。每次搜索需要O(lgn)时间,所以我们可以   无论大小如何,都以对数时间计算出现次数   块。

因此,我们需要两轮binary_search来查找lower_bound(找到第一个不小于KEY的数字)和upper_bound(找到大于KEY的第一个数字)。

int lower_bound(int arr[], int key, int low, int high)
{
    if (low > high)
        //return -1;
        return low;

    int mid = low + ((high - low) >> 1);
    //if (arr[mid] == key) return mid;

    //Attention here, we go left for lower_bound when meeting equal values
    if (arr[mid] >= key) 
        return lower_bound(arr, key, low, mid - 1);
    else
        return lower_bound(arr, key, mid + 1, high);
}

int upper_bound(int arr[], int key, int low, int high)
{
    if (low > high)
        //return -1;
        return low;

    int mid = low + ((high - low) >> 1);
    //if (arr[mid] == key) return mid;

    //Attention here, we go right for upper_bound when meeting equal values
    if (arr[mid] > key) 
        return upper_bound(arr, key, low, mid - 1);
    else
        return upper_bound(arr, key, mid + 1, high);
}

希望它有用:)

答案 2 :(得分:6)

如果我正在关注您的问题,那么您有一个对象列表,为了进行比较,它们看起来像{1,2,2,3,4,5,5,5,6,7,8,8,9}。正常搜索5会命中一个比较为5的对象,但你想要全部获取它们,是吗?

在这种情况下,我会建议一个标准的二进制搜索,在着陆到匹配元素时,开始向左看,直到它停止匹配,然后再向右(从第一个匹配)再次直到它停止匹配。

请注意,您使用的任何数据结构都不会覆盖与之相比的元素!

或者,考虑使用一种结构,该结构存储与该位置中的存储桶相同的元素。

答案 3 :(得分:3)

我会做两个二进制搜索,一个搜索第一个元素比较> =值(用C ++术语,lower_bound)然后一个搜索第一个元素比较>值(用C ++表示,upper_bound)。从lower_bound到just bound的元素就是你要找的东西(根据java.util.SortedSet,subset(key,key))。

因此,您需要对标准二进制搜索进行两种不同的轻微修改:您仍然可以探测并使用探针上的比较来缩小您要查找的值必须位于的区域,但现在例如对于lower_bound,如果你点击相等,你所知道的是你正在寻找的元素(第一个相等的值)介于目前范围的第一个元素和你刚刚探测过的值之间 - 你不能马上回来。

答案 4 :(得分:3)

一旦你找到了与bsearch的匹配,只需递归bsearch,直到不再匹配

伪代码:

    range search (type *array) {
      int index = bsearch(array, 0, array.length-1);

      // left
      int upperBound = index -1;
      int i = upperBound;
      do {
         upperBound = i;
         i = bsearch(array, 0, upperBound);
      } while (i != -1)

      // right
      int lowerBound = index + 1;
      int i = lowerBound;
      do {
         lowerBound = i;
         i = bsearch(array, lowerBound, array.length);
      } while (i != -1)

      return range(lowerBound, UpperBound);
}

但是没有涉及角落案件。我认为这会使你的复杂性保持在(O(logN))。

答案 5 :(得分:2)

这取决于您使用的二进制搜索的实现:

  • 在Java和.NET中,二进制搜索将为您提供任意元素;你必须搜索两种方式来获得你想要的范围。
  • 在C ++中,您可以使用equal_range方法在一次调用中生成所需的结果。

为了加速Java和.NET中的搜索,以便在相等范围太长而无法线性迭代的情况下,您可以查找前导元素和后继元素,并在您找到的范围的中间取值,不包括目的。

由于第二次二进制搜索,这应该是否太慢,请考虑编写自己的搜索,同时查找两端。这可能有点乏味,但它应该运行得更快。

答案 6 :(得分:2)

我首先找到给定sortable属性的单个元素的索引(使用“normal”二分搜索),然后开始查看列表中元素的左右两边,添加找到的所有元素符合搜索条件,当元素不符合标准时停止在一端,或者没有更多元素可以遍历,并且当左右两端都满足前面提到的停止条件时完全停止。

答案 7 :(得分:1)

你的二进制搜索是返回元素还是元素所在的索引?你能得到索引吗?

由于列表已排序,因此所有匹配的元素应相邻。如果您可以获得标准搜索中返回的项目的索引,则只需要从该索引中搜索两个方向,直到找到不匹配为止。

答案 8 :(得分:1)

Java中的这段代码以O(logN)时间以一遍计数排序数组中目标值的出现。修改它以返回找到的索引列表很简单,只需传入ArrayList。

想法是递归地优化eb的边界,直到它们成为具有目标值的连续块的下边界和上边界为止;

static int countMatching(int[] arr, int b, int e, int target){
    int m = (b+e)/2;
    
    if(e-b<2){
        int count = 0;
        if(arr[b] == target){
            count++;
        }
        if(arr[e] == target && b!=e){
            count++;
        }
        return count;
    }
    else if(arr[m] > target){
        return countMatching(arr,b,m-1, target);
    }
    else if(arr[m] < target){
        return countMatching(arr, m+1, e, target);
    }
    else {
        return countMatching(arr, b, m-1, target) + 1 
            + countMatching(arr, m+1, e, target);
    }
}

答案 9 :(得分:0)

试试这个。它的工作非常出色。

工作示例,Click here

   var arr = [1, 1, 2, 3, "a", "a", "a", "b", "c"]; // It should be sorted array.
   // if it arr contain more than one keys than it will return an array indexes. 

   binarySearch(arr, "a", false);

   function binarySearch(array, key, caseInsensitive) {
       var keyArr = [];
       var len = array.length;
       var ub = (len - 1);
       var p = 0;
       var mid = 0;
       var lb = p;

       key = caseInsensitive && key && typeof key == "string" ? key.toLowerCase() : key;

       function isCaseInsensitive(caseInsensitive, element) {
           return caseInsensitive && element && typeof element == "string" ? element.toLowerCase() : element;
       }
       while (lb <= ub) {
           mid = parseInt(lb + (ub - lb) / 2, 10);

           if (key === isCaseInsensitive(caseInsensitive, array[mid])) {
               keyArr.push(mid);
               if (keyArr.length > len) {
                   return keyArr;
               } else if (key == isCaseInsensitive(caseInsensitive, array[mid + 1])) {
                   for (var i = 1; i < len; i++) {
                       if (key != isCaseInsensitive(caseInsensitive, array[mid + i])) {
                           break;
                       } else {
                           keyArr.push(mid + i);

                       }
                   }
               }
               if (keyArr.length > len) {
                   return keyArr;
               } else if (key == isCaseInsensitive(caseInsensitive, array[mid - 1])) {
                   for (var i = 1; i < len; i++) {

                       if (key != isCaseInsensitive(caseInsensitive, array[mid - i])) {
                           break;
                       } else {
                           keyArr.push(mid - i);
                       }
                   }
               }
               return keyArr;

           } else if (key > isCaseInsensitive(caseInsensitive, array[mid])) {
               lb = mid + 1;
           } else {
               ub = mid - 1;
           }
       }

       return -1;
   }

答案 10 :(得分:0)

您可以使用以下代码解决问题。这里的主要目的是首先找到密钥的下限,然后找到密钥的上限。后来我们得到了指数的差异,我们得到了答案。除了具有两个不同的功能外,我们还可以使用一个标志,该标志可用于查找同一函数中的上限和下限。

#include <iostream>
#include <bits/stdc++.h>
using namespace std;

int bin_search(int a[], int low, int high, int key, bool flag){
long long int mid,result=-1;
while(low<=high){
    mid = (low+high)/2;
    if(a[mid]<key)
        low = mid + 1;
    else if(a[mid]>key)
        high = mid - 1;
    else{
        result = mid;
        if(flag)
            high=mid-1;//Go on searching towards left (lower indices)
        else
            low=mid+1;//Go on searching towards right (higher indices)
    }
}
return result;
}

int main() {

int n,k,ctr,lowind,highind;
cin>>n>>k;
//k being the required number to find for
int a[n];
for(i=0;i<n;i++){
    cin>>a[i];
}
    sort(a,a+n);
    lowind = bin_search(a,0,n-1,k,true);
    if(lowind==-1)
        ctr=0;
    else{
        highind = bin_search(a,0,n-1,k,false);
        ctr= highind - lowind +1;   
}
cout<<ctr<<endl;
return 0;
}

答案 11 :(得分:0)

您可以进行两次搜索:一次搜索范围之前的索引,一次搜索范围之后的索引。因为前后都可以重复-将float用作“唯一”键”

    static int[] findFromTo(int[] arr, int key) {
    float beforeKey = (float) ((float) key - 0.2);
    float afterKey = (float) ((float) key + 0.2);
    int left = 0;
    int right = arr.length - 1;
    for (; left <= right;) {
        int mid = left + (right - left) / 2;
        float cur = (float) arr[mid];
        if (beforeKey < cur)
            right = mid - 1;
        else
            left = mid + 1;
    }
    leftAfter = 0;
    right = arr.length - 1;
    for (; leftAfter <= right;) {
        int mid = left + (right - leftAfter) / 2;
        float cur = (float) arr[mid];
        if (afterKey < cur)
            right = mid - 1;
        else
            left = mid + 1;
    }
    return new int[] { left, leftAfter };
}

答案 12 :(得分:-1)

class binary_search_descending_multiple_s
{
    public static void main(int s)
    {
        int a[]={100,100,100,100,100,100,100,100,99,100,87,80,90,78,87,8,64,100,99,99,99,99,99,99};
        int l=a.length;
        int i,x,c,fv=0,lv=l-1,m;
        for(i=0;i<l-1;i++)
        {
            for(x=i+1;x<l;x++)
            {
                if(a[i]<a[x])//descending order used
                {
                 c=a[i];
                 a[i]=a[x];
                 a[x]=c;
                }
            }
        }
        c=0;
        while(fv<=lv&&c==0)
        {
            m=(lv+fv)/2;
            if(a[m]==s)
            {
                System.out.println("found at"+(m+1));
                int xr=m+1;//for right side nos 
                int xl=m-1;//for left side nos
                c=0;
                while(c>=-1 && xr<a.length)
                {
                      if(a[xr]==s)
                      System.out.println("found at"+(xr+1));
                      else//to terminate the loop
                      break;
                      xr++;//increment to check terms further right
                }
                while(c>=0 && xl>=0)//for left side
                {
                     if(a[xl]==s)
                     System.out.println("found at"+(xl+1));
                     else//to terminate the loop
                     break;
                     xl-=1;//decrement to check terms further left
                }
                c=1;
            }
            if(a[m]<=s)
             lv=m-1;
            if(a[m]>s)
             fv=m+1;
        }
    }
}

该程序将数据按降序排序然后搜索。它适用于单跳,但双跳可能会导致您错过一个值。阅读程序,你就会明白。

答案 13 :(得分:-1)

最近发现了一种非常有效的算法。
该算法具有考虑两个变量(输入大小和搜索键的数量)的对数时间复杂度。但是,搜索到的键也必须进行排序。

#define MIDDLE(left, right) ((left) + (((right) - (left)) >> 1))

int bs (const int *arr, int left, int right, int key, bool *found)
{
    int middle = MIDDLE(left, right);

    while (left <= right)
    {
        if (key < arr[middle])
            right = middle - 1;
        else if (key == arr[middle]) {
            *found = true;
            return middle;
        }
        else
            left = middle + 1;
        middle = MIDDLE(left, right);
    }

    *found = false;
    /* left points to the position of first bigger element */
    return left;
}

static void _mkbs (const int *arr, int arr_l, int arr_r,
                   const int *keys, int keys_l, int keys_r, int *results)
{
    /* end condition */
    if (keys_r - keys_l < 0)
        return;

    int keys_middle = MIDDLE(keys_l, keys_r);

    /* throw away half of keys, if the key on keys_middle is out */
    if (keys[keys_middle] < arr[arr_l]) {
        _mkbs(arr, arr_l, arr_r, keys, keys_middle + 1, keys_r, results);
        return;
    }
    if (keys[keys_middle] > arr[arr_r]) {
        _mkbs(arr, arr_l, arr_r, keys, keys_l, keys_middle - 1, results);
        return;
    }

    bool found;
    int pos = bs(arr, arr_l, arr_r, keys[keys_middle], &found);

    if (found)
        results[keys_middle] = pos;

    _mkbs(arr, arr_l, pos - 1, keys, keys_l, keys_middle - 1, results);
    _mkbs(arr, (found) ? pos + 1 : pos, arr_r, keys, keys_middle + 1, keys_r, results);
}

void mkbs (const int *arr, int N, const int *keys, int M, int *results)
{   _mkbs(arr, 0, N - 1, keys, 0, M - 1, results);   }

以下是C的实现和拟发布的论文草案: https://github.com/juliusmilan/multi_value_binary_search

能否请您分享一个用例?