【问题标题】:Efficient search of sorted numerical values有效搜索已排序的数值
【发布时间】:2014-01-22 22:31:49
【问题描述】:

我有一个 int[] 数组,其中包含具有以下属性的值:

  • 它们是排序的
  • 它们是独一无二的(没有重复)
  • 它们在已知范围 [0..MAX)
  • MAX 通常比数组的长度大很多(比如 10-100x)
  • 有时数字会均匀分布在整个范围内,但有时会有相当长的连续数字序列。我估计这两种情况之间的比例约为 50/50。

给定这个列表,我想有效地找到数组中特定值的索引(或者如果该值不存在,则查找下一个更高的值)。

我已经实现了直接二分搜索,其中区间二等分效果相当好,但我怀疑可以利用数据的性质/分布来更快地收敛到解决方案。

我对优化平均案例搜索时间很感兴趣,但重要的是,最坏的情况永远不会比 O(log n) 更糟,因为数组有时非常大。

问题:在一般情况下,有可能比普通的二分搜索做得更好吗?

编辑(澄清其他问题/cmets)

  • O(log n) 中的常数绝对很重要。事实上,假设不可能有比 O(log n) 更好的算法复杂度,那么常数可能是唯一重要的事情.....
  • 这通常是一次性搜索,因此虽然可以进行预处理,但它可能不值得。

【问题讨论】:

  • 您搜索了多少次?将它们粘贴到Map 将为您提供O(1) 搜索,但将使用O(n) 进行设置。附言你不需要实现二分搜索 Arrays.binarySearch 完全符合你的要求。
  • 如果真的有大块连续的数字(没有重复),你可以修改你的二分搜索来识别它何时到达一个(x[end] - x[start] == end - start?),然后直接跳到解决方案.请记住,这种方法依赖于非常长的连续序列,否则您不会消除许多递归阶段。
  • 您尝试过插值搜索吗? en.wikipedia.org/wiki/Interpolation_search
  • 我认为 O(log n) 中的常数对你也很重要。正确的?为什么不做一个类似于二分查找的算法,而是每次将数组分成 3 或 4 个子数组呢?并对此进行一些实验。
  • @peter.petrov:如果你的插值是愚蠢的,那可能就是那么糟糕。这真的取决于数据。如果分布相当均匀,智能插值搜索将击败直接二进制。如果数据不是合理均匀分布,二分查找可能是更好的选择。 OP 说它可以是 50/50(均匀分布与长时间的连续值)。我会说需要对几个有代表性的数据集进行测试才能确定哪个更好。

标签: java algorithm indexing


【解决方案1】:

这是在 cmets 中,应该是一个答案。这是共同努力的结果,所以我将其设为 CW 答案:

您可能想查看interpolation search。在最坏的情况下,它们可能比O(log n) 更糟糕,因此如果这是一个硬性要求,则不适用。但是,如果您的插值是不错的,根据数据分布,插值搜索可以击败直接二进制。

要知道,您必须使用相当智能的插值算法来实现插值搜索,然后通过这两种算法运行几个有代表性的数据集,看看是插值还是二进制更适合。我认为它会是两者之一,但我并不熟悉真正尖端的搜索算法。

【讨论】:

    【解决方案2】:

    让我们将区间命名为x,并将z 命名为搜索到的数字。

    由于您希望这些值均匀分布,您可以使用插值搜索。这类似于二分查找,但将索引范围拆分为start + ((z - x[start]) * (end - start)) / (x[end] - x[start])。

    要获得O(log n) 的运行时间,您必须将插值搜索与二分搜索相结合(交替执行二分搜索和插值搜索步骤):

    public int search(int[] values, int z) {
        int start = 0;
        int end = values.length-1;
    
        if (values[0] == z)
             return 0;
        else if (values[end] == z) {
            return end;
        }
    
        boolean interpolation = true;
    
        while (start < end) {
            int mid;
            if (interpolation) {
                mid = start + ((z - values[start]) * (end - start)) / (values[end] - values[start]);
            } else {
                mid = (end-start) / 2;
            }
            int v = values[mid];
            if (v == z)
                return mid;
            else if (v > z)
                end = mid;
            else
                start = mid;
            interpolation = !interpolation;
        }
        return -1;
    }
    

    由于 while 循环的每第二次迭代都会在二分搜索中执行一个步骤,因此它最多使用二分搜索将使用的迭代次数的两倍 (O(log n))。由于每第二步都是插值搜索的一步,因此如果输入具有所需的属性,算法应该快速减小区间大小。

    【讨论】:

      【解决方案3】:

      如果 int[] 是

      • 已排序
      • 具有独特的价值
      • 你知道范围(提前)

      而不是搜索为什么不将值保存在其索引处。

      假设数字是 243,而不是将值保存在 int[243] = 243 中。

      这样搜索会更容易和更快。剩下的就是找出下一个更高的价值。

      【讨论】:

      • OP 说 “MAX 通常比数组的长度大很多(比如 10-100x)”,所以这会非常消耗内存。跨度>
      【解决方案4】:

      我有一个解决方案。
      你是说数组可以是
      1)数字在范围内均匀分布
      2)有相当长的连续数字序列。

      所以,首先我们开始一个简单的测试来确定它是 type1 还是 type2。
      要测试类型 1,
      长度 =array.length;
      range = array[length-1] - array[0];
      现在考虑
      处的数组值 { 长度(1/5),长度(2/5),长度(3/5),长度(4/5)},
      如果数组分布是类型1,那么我们大致知道array[i]处的值一定是什么,所以我们检查上面4个位置是否接近已知值,如果它的分布相等。
      如果它们接近,那么它的分布是相等的,所以我们可以很容易地找到数组中的任何元素。如果我们不能根据上述方法找到元素,我们认为它是类型2。

      如果上面的测试失败,那么它是类型 2,这意味着在数组中很少有地方存在长序列的连续数字。

      所以,我们用二进制搜索等术语来解决它。解释如下
      *我们首先在数组的中间搜索,(比如在长度/2处,索引为i)

      左=0,右=长度;
      开始:
      i=(左+右)/2;

      case a.1:我们的搜索数大于array[i]
      左=我;
      *现在我们检查该位置是否存在任何长连续序列,即
      array[i],array[i+1],array[i+2] 是连续整数。

      case a.1.1:(如果它们是连续的),
      由于它们是连续的,并且序列可能很长,因此我们根据搜索整数值直接在特定索引处搜索。
      例如,如果我们的搜索int是10,sequence是5,6,7,8,9,10,11 15,100,103,
      和array[i]=5,那么我们直接在array[i+10-5]处搜索,
      如果我们找到我们的搜索 int,则返回它,否则仅从 case a.2 继续 [因为它显然会小于它],通过将 right 设置为
      对=(数组[i+10-5])

      case a.1.2,如果它们不是连续的
      从 BEGIN 继续;

      case a.2:我们的搜索数小于array[i],
      *case a.2 与 a.1 完全相同
      *同样检查是否有任何后序,即 array[i-2],array[i-1],array[i] 是否按顺序排列,
      如果它们是连续的,则像我们在 a.1.1
      中所做的那样搜索回精确值 如果它们不连续,则与案例 a.1.2 类似重复。

      case a.3,就是我们的搜索int,
      然后返回它。

      希望这有帮助

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-07-10
        • 2012-12-24
        • 1970-01-01
        • 2020-03-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多