Algorithm 二进制分区将n个已排序的数据点放入`O(b.log(n))中的b个存储桶中`

Algorithm 二进制分区将n个已排序的数据点放入`O(b.log(n))中的b个存储桶中`,algorithm,Algorithm,给定大小为n的已排序的随机可访问输入数据(一个已排序的数组),我想使用参数化分区函数将其分区到bucket中。我希望结果作为桶边的索引数组返回 分区函数返回一个bool,指示太多的值是否应该在同一分区中 请注意,在分区之前,我们不知道需要多少存储桶: 所有输入都可能放在一个铲斗中 可能每个输入都需要自己的bucket 也许第一个值需要一个bucket,而数组的整个剩余部分都在第二个bucket中 …这在运行分区算法之前是未知的 作为一个具体的例子,假设我们有分区函数: sameBucket(

给定大小为n的已排序的随机可访问输入数据(一个已排序的数组),我想使用参数化分区函数将其分区到bucket中。我希望结果作为桶边的索引数组返回

分区函数返回一个bool,指示太多的值是否应该在同一分区中

请注意,在分区之前,我们不知道需要多少存储桶:

  • 所有输入都可能放在一个铲斗中
  • 可能每个输入都需要自己的bucket
  • 也许第一个值需要一个bucket,而数组的整个剩余部分都在第二个bucket中
…这在运行分区算法之前是未知的

作为一个具体的例子,假设我们有分区函数:

sameBucket(a, b) = (a/10 == b/10)
其中
/
为整数除法(向下舍入)。所以

分区函数告诉我们
0
10
不应放在同一个存储桶中

为了清晰起见,考虑下面显示的这个输入数组及其索引(我假设一个名为end的“过去的结束”索引):

对于该数据,作为新存储桶成员的元素用
^
表示:

[1, 3, 7, 14, 90, 91, 92, 93, 95, 99]
 0  1  2  3   4   5   6   7   8   9   end
 ^        ^   ^                       ^
如果我使用上面的分区函数,我只会返回开始一个新bucket的索引:

[0, 3, 4, end]
结果数组中的每个索引表示bucket函数所说的第一个元素与它前面的元素不在同一个bucket中


  • 整个数据由范围
    0…表示。有效解决此问题需要假设,如果排序范围两端的元素根据
    sameBucket(左、右)
    属于同一个存储桶,则
    之间的所有值也必须属于同一个存储桶

    我相信经过修改的二进制搜索应该能够有效地执行这种分区

    是的,您可以运行二进制搜索,如下所示:

  • nextBucket
    设置为零
  • left
    设置为
    nextBucket
    并将
    right
    设置为输入数组的末尾
  • mid
    设置为
    left
    right
    之间的中点
  • 如果
    sameBucket(nextBucket,mid)
    true
    ,则将
    向左移动到
    mid
    ;否则,将
    向右
    移动到
    中间
  • 如果
    left==right
    ,则退出循环;否则,返回步骤3
  • 循环完成后,
    left
    是下一个分区索引。
    nextBucket
    left
    之间的所有项目都在同一个bucket中
  • nextBucket
    设置为
    left+1
  • 如果
    nextBucket
    等于
    n
    ,则完成;否则,返回步骤2

  • 我不认为这个算法有什么特别的名字——它是一个伪装得很差的二进制搜索。

    多亏了dasblinkenlight的帮助

    我很确定有一种算法比他们给出的方法(具有我在OP中提到的不对称复杂性)具有更好的大O性能。我昨晚写的,是这样的

    bucket_starts(in_inclusive_range: r)
    {
      if r.count is 0 or 1 elements
      {
        // There are no bucket starts in this range.
        // Return an empty array.
        return []
      }
      else
      {
        // If `r`'s start & end elements are in the same bucket
        // then `r` contains no bucket starts.
        if same_bucket(element_at[r.first_index], element_at[r.last_index])
        {
          // There are no buckets in this range.
          return []
        }
        else
        {
          // `r` has 1 or more buckets in it. Subdivide and find them.
          // Note that `midpoint` is in both lower & upper ranges.
          let first_subrange = range(from: r.first_index to: r.mid_index)
          let second_subrange = range(from: r.mid_index to: r.last_index)
          return bucket_starts(in_inclusive_range: first_subrange) + 
                 bucket_starts(in_inclusive_range: second_subrange)
        }
      }
    }
    
    时间复杂性 如果输入有
    n
    元素,并且包含
    b
    不同的bucket,那么(我相信)算法的运行时复杂性是最坏情况
    O(b.log(n/b))

    • 在每个输入元素落在不同的桶中的极限情况下,
      b==n
      ,成本在
      n
      中变为线性
    • 对于给定的
      b
      ,当所有存储桶的大小都相同时,最坏情况的复杂性就会出现。如果大多数存储桶都很小,而少数存储桶很大,那么运行时复杂性就会降低
    • 对于输出桶大小序列
      B
      ,其中
      sum(B)=n
      ,复杂性将类似于:
      O(sum\u of_log\u of_B)
    • 在极端情况下,
      b-1
      桶的大小为1,而桶的大小大于
      n-b
      ,这将接近
      O(b+log(n))
    这似乎是一个很好的复杂行为。它适应内容的具体情况,在输入大小上具有绝对的最坏情况时间性能线性

    我不知道是否有这个名字-我非常怀疑它是原创的!如果你知道它的名字,我想知道它是什么

    空间复杂性
    该算法所需的空间复杂度(不是输入或输出)是
    O(log(n))
    ,因此输入和输出的线性存储已使其不堪重负。

    我认为您遗漏了一些关键假设。值数组是否已排序?
    sameBucket
    可传递吗?它也必须是反射性的吗?如果asameBucket(a,b)
    为假,这是否意味着
    sameBucket(a,c)
    也必须为假?是的,排序::-)检查帖子的第二个单词(编辑前在原始剪切中诚实地显示)。关于后面几点,你完全正确……我不确定。我在想也许我应该把
    sameBucket
    表达成与
    Yes类似的形式——sameBucket肯定是可传递的。是的,正如你所描述的,它也是反射性的。感谢您的澄清。任何给定的分区是否像您的示例一样只包含顺序元素?我的最后一点不是关于自反性(这意味着
    sameBucket(x,x)
    对于x的所有值都是真的)。我不确定我描述的属性是否有名称。基本上,它不允许空白:如果输入序列被排序,索引
    low
    high
    处的项目都进入一个bucket,它们之间的所有项目也必须进入同一个bucket。如果你的假设是真的,你不能通过遍历项目并记录
    sameBucket(A[i],A]处的索引,在O(N)中解决这个问题吗[i+1])==false
    ?@AShelly当然可以,但是sin
    [0, 3, 4, end]
    
    bucket_starts(in_inclusive_range: r)
    {
      if r.count is 0 or 1 elements
      {
        // There are no bucket starts in this range.
        // Return an empty array.
        return []
      }
      else
      {
        // If `r`'s start & end elements are in the same bucket
        // then `r` contains no bucket starts.
        if same_bucket(element_at[r.first_index], element_at[r.last_index])
        {
          // There are no buckets in this range.
          return []
        }
        else
        {
          // `r` has 1 or more buckets in it. Subdivide and find them.
          // Note that `midpoint` is in both lower & upper ranges.
          let first_subrange = range(from: r.first_index to: r.mid_index)
          let second_subrange = range(from: r.mid_index to: r.last_index)
          return bucket_starts(in_inclusive_range: first_subrange) + 
                 bucket_starts(in_inclusive_range: second_subrange)
        }
      }
    }