【问题标题】:Fastest way to determine values matching an interval in an array确定与数组中的间隔匹配的值的最快方法
【发布时间】:2016-03-18 23:25:08
【问题描述】:

我有一个从xyint 排序数组(元素的值是随机的,但使用qsort() 按升序排序)。该程序接收各种间隔,如<10;50><50;100>。我有以下简单的for 循环来确定数组中的值是否在设定的间隔内,如果是,则向计数器添加一。

 for(int i = 0; i < arraySize ;i++ )  {        
       if (points[i] >= interval1 && points[i] <= interval2){
            counter++;               
        }
    }

我需要一种比O(n) 更快的方法来搜索数组,并确定points[i] 中的值是否在设置的区间内。价值可能以百万计,因此速度会急剧下降。

数组中的元素范围可以从 0 到 1000000000 (1e9)。间隔分别。

【问题讨论】:

标签: c arrays performance


【解决方案1】:

使用二分查找——对于输入区间[i, j],找到大于i的最小整数的索引,找到小于j的最大整数的索引,然后返回距离他们之间。

ssize_t bin_search_first_larger(int arr[], size_t arr_sz, int val) {
    ssize_t l = -1;
    ssize_t r = arr_sz;
    /* invariant: arr[l] < val && val <= arr[r] */
    while (l+1 != r) {
        ssize_t m = l+(r-l)/2;
        if (arr[m] < val) {
            l = m;
        } else {
            r = m;
        }
    }
    /* l+1 == r && arr[l] < val && val <= arr[r] */
    return r;
}

ssize_t bin_search_last_smaller(int arr[], size_t arr_sz, int val) {
    ssize_t l = -1;
    ssize_t r = arr_sz;
    /* invariant: arr[l] <= val && val < arr[r] */
    while (l+1 != r) {
        ssize_t m = l+(r-l)/2;
        if (arr[m] <= val) {
            l = m;
        } else {
            r = m;
        }
    }
    /* l+1 == r && arr[l] <= val && val < arr[r] */
    return l;
}

ssize_t values_in(int arr[], size_t arr_sz, int x, int y) {
    ssize_t i = bin_search_first_larger(arr, arr_sz, x);
    ssize_t j = bin_search_last_smaller(arr, arr_sz, y);
    return j-i+1;
}

二分搜索代码改编自 Jon Bentley 的 Programming Pearls(非常值得一读),其中展示了如何修改二分搜索以返回第一次出现或最后一次出现具有重复值的排序数组中的值(而不是返回任意出现的重复值)。该过程与您的用例相似,但差别很小。

请注意,从概念上讲,假设arr[-1] 是负无穷大,arr[N] 是正无穷大(其中N 是数组的大小),但显然,代码从不尝试访问这些元素。

时间复杂度是O(log(N)),其中N 是数组的大小,很难(不可能?)得到比这更好的结果。

我进行了一些测试,它似乎适用于一般情况和边缘情况(范围内没有元素,或y 大于每个元素,或x 小于每个元素,或两者都有@ 987654332@ 比每个元素都小,y 比每个元素都大),但您可能知道这并不能证明没有错误。

【讨论】:

  • 像魅力一样工作。除了我目前正在整理的一些内存泄漏。
  • 更正:内存泄漏是我造成的,抱歉。
  • @Supercan 是的,我认为当我阅读您的第一条评论时 - 我以为您已经意识到这一点。此代码不执行任何内存分配或任何其他与内存相关的操作,因此它不可能对泄漏负责。
【解决方案2】:

这里有其他版本的 BinSearch,复杂度也是 O(logN)。

    int BinSearch(int *array, int first, int last, int value){

          int m;
          /* Optional Error control */
          if (!array || first<0 || last<first)  return -1;

          while (first <= last){

                  m = (first + last)/2;

                  if(array[m] == value) return m;

                  if(value < array[m]) last = m-1;

                  else
                       first = m+1;
            }
           /* Failure search */
           return -1;
     }

如果值不在数组或值所在的索引中,则函数返回 -1。

你可以做一个变体,如果找到值返回 1 或 0,那么你可以做

      counter += BinSearch_variant(array,first,last,value);

【讨论】:

  • 这不会编译(fist &lt; 0 而不是first),这表明您甚至没有测试代码(或者更糟糕的是:您没有尝试编译它)。另外,这还不足以解决问题,因为区间极值不一定在数组中。问题不是给我二进制搜索的代码,而是关于优化特定场景(二进制搜索肯定起作用,但这个答案太不完整,在我看来没有用处)。
【解决方案3】:

所需距离等于:

// position of first element greater than interval2
auto lb = std::upper_bound(array.begin(), array.end(), interval2);
// position of first element greater or equal than interval1
auto ub = std::lower_bound(array.begin(), array.end(), interval1);
// their difference is the number of elements in the needed range
return (ub - lb);

得到的复杂度为O(log N),因为已排序数组的下限/上限为O(log N)

编辑:抱歉,没有注意到C 标签。那么在C 中,您需要自己实现下限/上限操作。为了让你的生活更简单——你只能使用lower_bound,然后使用upper_bound作为lower_bound(interval2 + 1)

【讨论】:

  • 问题标记为 C。
【解决方案4】:

晚会,试图接受挑战以比O(log n)做得更好,这是一个O(1)(时间)解决方案,用于获取给定范围[a,b]内的值数量。

初始化本身,只做一次,是O(MAXVALUE+NVALUES)MAXVALUE 是数据集中可能出现的最大值,NVALUES 是数据集中值的个数。并根据问题

  • 0 是最小值
  • 1,000,000,000 是最高值
  • 数据集数以百万计

O(1) 要求允许程序分配MAXVALUE+1 int 的数组。基本上对于 10 亿个值,1GB x sizeof(int) 的数组(gcc 在 Linux x86_64 上通常需要 4 GB 的 RAM,或部分交换)。这意味着程序必须在至少 64 位的机器上运行。

要排序的初始数据集。

原则

  • 初始化(一次):索引i处的m[0, 1bn]数组获取大于或等于i

  • 的值的个数>
  • [a, b] 范围内的值数只是 m[a] - m[b+1]
    (如果b+1 > MAXVALUE,请改用0

初始化:

#define MAXVALUE 1000000000
#define NVALUES     1000000

int *m; // big array

void initialization(int *values)
{
   m = malloc((MAXVALUE+1) * sizeof(*m)); // check if NULL!

   int i,j;

   for(j=0,i=0 ; i<=MAXVALUE ; ) {
      if (j >= NVALUES) m[i++] = 0;
      else if (values[j] >= i) m[i++] = NVALUES-j;
      else j++;
   }
}

获取[a, b] a&lt;=b范围内的值个数:

int count_in_range(int a, int b) {
   int ma = m[a];
   int mb = b >= MAXVALUE ? 0 : m[b+1];
   return ma-mb;
}

m 必须在计算完所有范围后释放。

【讨论】:

    猜你喜欢
    • 2011-08-08
    • 2021-11-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-07-22
    • 1970-01-01
    • 1970-01-01
    • 2012-02-10
    相关资源
    最近更新 更多