在最小数量的比较中二进制搜索大数组中的多个不同数字

时间:2014-09-06 11:40:43

标签: c arrays algorithm

我有一个大小为n的数组(比如n = 1000000),其值单调不减。我有一套' k'关键值(比如k = {1,23,39,55,..})。假设键值已排序。我必须使用最少的比较数在大数组中找到这些键值的索引。如何使用二进制搜索来搜索多个唯一值?对每个键值单独执行操作需要进行大量比较。当我在同一个大阵列上搜索另一个元素时,我能否以某种方式重复使用我在一次搜索中学到的知识?

6 个答案:

答案 0 :(得分:5)

  1. 对针进行排序(您将搜索的值)。
  2. 创建一个与针相同长度的数组,每个元素都是一对索引。使用{0, len(haystack)}初始化每对。这些对代表了我们对针的可能位置的所有知识。
  3. 看看大海捞针的中间值。现在在您的针头中进行二进制搜索。对于所有较小的针,将上限(在步骤2的数组中)设置为当前的haystack索引。对于所有更大的针,设置下限。
  4. 当您执行第3步时,请跟踪哪个针现在具有最大范围。将其平分并将其用作新的中间值以重复步骤3.如果最大范围是单数,则完成:已找到所有针(或者如果未找到,则它们在大海捞针中的预期位置现在已知)。 / LI>

    当你在大海捞针中有重复的值时,这里可能会有一些轻微的复杂情况,但我认为一旦你完成其余的整理,这应该不会太困难。


    如果NumPy实现了这样的话,我很好奇。你正在做的事情的Python名称是numpy.searchsorted(),一旦你通过API层,它就会出现在this

        /*
         * Updating only one of the indices based on the previous key
         * gives the search a big boost when keys are sorted, but slightly
         * slows down things for purely random ones.
         */
        if (@TYPE@_LT(last_key_val, key_val)) {
            max_idx = arr_len;
        }
        else {
            min_idx = 0;
            max_idx = (max_idx < arr_len) ? (max_idx + 1) : arr_len;
        }
    

    所以他们没有像我描述的那样进行全面的优化,但他们确实跟踪当前针头是否比最后一根针头更大,他们可以避免搜索最后一根针头被发现的下方的草垛。这是对简单实现的简单而优雅的改进,从评论中可以看出,它必须保持简单快速,因为该功能不需要首先对针进行排序。


    顺便说一下:我提出的解决方案的目的是在大O方面达到理论上的最优性,但如果你有大量的针头,最快的事情就是对针头进行分类,然后在整个草堆上进行迭代。串联的所有针:线性搜索第一个针,然后从那里继续寻找第二个,等等。你甚至可以通过识别针是否大于A且小于C来跳过大海捞针中的每一个项目,它必须属于B位置(假设您不关心不在大海捞针中的针的左/右插入顺序)。然后你可以做len(haystack)/ 2比较,整个事情将非常适合缓存(当然,在排序针之后)。

答案 1 :(得分:4)

重用以前步骤中的知识的一种方法就像其他人建议的那样:一旦找到了某个键,就可以限制较小和较大键的搜索范围。

假设N = 2 ^ n,K = 2 ^ k且幸运结果: 找到中间键(n比较)后,你有两个大小为N / 2的子阵列。执行2次搜索“四分位”键(每次n-1比较),减少到N / 4个子阵列...

总计,n + 2(n-1)+ 4(n-2)+ ... + 2 ^(k-1)(n-k + 1)比较。经过一些数学计算,这大致等于K.n-K.k = K.(n-k)。

这是一个最好的案例,与独立搜索(K.n比较)相比,节省的费用并不那么显着。无论如何,最糟糕的情况(导致分区不平衡的所有搜索)并不比独立搜索差。

更新:这是最低比较合并问题的实例

在N个值的数组中查找K键的位置与合并两个已排序的序列相同。

来自Knuth Vol。 3,第5.3.2节,我们知道至少需要进行ceiling(lg(C(N+K,K)))次比较(因为有C(N+K,K)种方式来散布数组中的键。当K远小于N时,它接近lg((N^K/K!)K lg(N) - K lg(K) = K.(n-k)

这个边界不能被任何基于比较的方法打败,所以任何这样的算法都需要花费时间与键的数量基本成比例。

答案 2 :(得分:2)

  1. 排针。
  2. 搜索第一针
  3. 使用搜索结果更新干草堆的下限
  4. 搜索最后一针
  5. 使用搜索结果更新干草堆的上限
  6. 去2。
  7. 虽然不是最佳,但实施起来要容易得多。

答案 3 :(得分:1)

如果你有一组整数,并且你想搜索最少的比较数,我想建议你从Knuth,6.2.1进行插值搜索。如果二进制搜索需要Log(N)次迭代(和比较),则插值搜索只需要Log(Log(N))操作。

有关详细信息和代码示例,请参阅:

http://en.wikipedia.org/wiki/Interpolation_search

http://xlinux.nist.gov/dads//HTML/interpolationSearch.html

答案 4 :(得分:0)

我知道问题是关于C,但我只是在Javascript中实现了这个,我以为我会分享。如果你在数组中有重复的元素,则无意工作...我认为在这种情况下它只会返回任何可能的索引。对于包含100万个元素的数组,您可以在其中搜索每个元素,其速度提高约2.5倍。如果您还搜索未包含在数组中的元素,那么它甚至更快。在一个数据集中,我通过它的速度要快几倍。对于小阵列,它大致相同

        singleSearch=function(array, num) {
            return this.singleSearch_(array, num, 0, array.length)
        }

        singleSearch_=function(array, num, left, right){
            while (left < right) {
                var middle =(left + right) >> 1;
                var midValue = array[middle];

                if (num > midValue) {
                    left = middle + 1;
                } else {
                    right = middle;
                }
            }
            return left;
        };


        multiSearch=function(array, nums) {
            var numsLength=nums.length;
            var results=new Int32Array(numsLength);
            this.multiSearch_(array, nums, 0, array.length, 0, numsLength, results);
            return results;
        };

        multiSearch_=function(array, nums, left, right, numsLeft, numsRight, results) {
            var middle = (left + right) >> 1;
            var midValue = array[middle];
            var numsMiddle = this.singleSearch_(nums, midValue, numsLeft, numsRight);
            if ((numsRight - numsLeft) > 1) {
                if (middle + 1 < right) {
                    var newLeft = middle;
                    var newRight = middle;
                    if ((numsRight - numsMiddle) > 0) {
                        this.multiSearch_(array, nums, newLeft, right, numsMiddle, numsRight, results);
                    }
                    if (numsMiddle - numsLeft > 0) {
                        this.multiSearch_(array, nums, left, newRight, numsLeft, numsMiddle, results);
                    }
                }
                else {
                    for (var i = numsLeft; i < numsRight; i++) {
                        var result = this.singleSearch_(array, nums[i], left, right);
                        results[i] = result;
                    }
                }
            }
            else {
                var result = this.singleSearch_(array, nums[numsLeft], left, right);
                results[numsLeft] = result;
            };
        }

答案 5 :(得分:-1)

//基于递归二进制搜索的函数。它返回x的索引 //给定数组arr [l..r]存在,否则为-1。

int binarySearch(int arr[], int l, int r, int x)
{
   if (r >= l)
   {
        int mid = l + (r - l)/2;

        // If the element is present at one of the middle 3 positions
        if (arr[mid] == x)  return mid;
        if (mid > l && arr[mid-1] == x) return (mid - 1);
        if (mid < r && arr[mid+1] == x) return (mid + 1);

        // If element is smaller than mid, then it can only be present
        // in left subarray
        if (arr[mid] > x) return binarySearch(arr, l, mid-2, x);

        // Else the element can only be present in right subarray
        return binarySearch(arr, mid+2, r, x);
   }

   // We reach here when element is not present in array
   return -1;
}