二进制分区中的n个已排序数据点到“O(b.log(n))”

时间:2017-05-30 17:31:44

标签: algorithm

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

分区函数返回一个bool来指示是否值也应该在同一个分区中。

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

  • 可能所有输入都应放在一个存储桶中。
  • 可能是每个输入都需要自己的桶。
  • 也许第一个值需要一个桶,而阵列的其余部分都在第二个桶中。

...在运行分区算法之前不知道。

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

sameBucket(a, b) = (a/10 == b/10)

/是整数除法(向下舍入)。所以

sameBucket(0,1) == yes
sameBucket(1,2) == yes
sameBucket(0,9) == yes
sameBucket(0,10) == no

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

为了清楚起见,考虑下面显示的输入数组及其索引(我假设一个"过了结束"索引叫做end):

[1, 3, 7, 14, 90, 91, 92, 93, 95, 99]
 0  1  2  3   4   5   6   7   8   9   end

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

[1, 3, 7, 14, 90, 91, 92, 93, 95, 99]
 0  1  2  3   4   5   6   7   8   9   end
 ^        ^   ^                       ^

如果我使用上面的分区功能,我只会回到开始新桶的索引:

[0, 3, 4, end]

结果数组中的每个索引表示存储桶函数所说的第一个元素与之前的元素不在同一个存储桶中。

  • 整个数据由范围0…<end表示。
  • 范围0…<3是数字[1, 3, 7],全部为0,除以10
  • 范围3…<4是单个数字14,除以1时为10
  • 范围4…<end是数字[90, 91, 92, 93, 95, 99],除以0时全部为10

我认为修改后的二进制搜索应该能够有效地执行此分区。对于n输入值和b输出存储桶,运行时间应该最差O(b.log(n))。有没有人有它的算法,甚至只是名字,所以我可以查找它?

2 个答案:

答案 0 :(得分:1)

有效解决此问题需要假设如果排序范围两端的元素属于sameBucket(left, right)所属的同一个存储桶,那么leftright之间的所有值都必须属于也是同一个桶。

  

我认为修改后的二进制搜索应该能够有效地执行此分区

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

  1. nextBucket设为零
  2. left设置为nextBucket,将right设置为输入数组的末尾
  3. mid设置为leftright
  4. 之间的中点
  5. 如果sameBucket(nextBucket, mid)true,请将left移至mid;否则,请将right移至mid
  6. if left == right,退出循环;否则,请返回第3步
  7. 完成循环后,left是下一个分区索引。 nextBucketleft之间的所有项目都在同一个文件夹中。
  8. nextBucket设为left+1
  9. 如果nextBucket等于n,您就完成了;否则,请返回第2步。
  10. 我不认为这个算法有一个特殊的名字 - 它是一个很难伪装的二进制搜索。

答案 1 :(得分:1)

感谢their answer的dasblinkenlight。

我很确定有一种算法可以提供比他们给出的方法更好的大O性能(它确实具有我在OP中提到的asymtotic复杂性)。我昨晚写了它,就像这样。

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个不同的存储桶,那么(我相信)算法的运行时复杂性最坏情况 O(b.log(n/b))

  • 在极限情况下,每个输入元素都位于不同的存储区中b == n,成本在n中变为线性。
  • 对于给定的b,当所有桶具有统一大小时,发生最坏情况的复杂性。如果大多数存储桶很小而且很少很大,则运行时复杂性会降低。
  • 对于输出存储区大小B的序列,其中sum(B) == n的复杂度类似于:O(sum_of_logs_of_B)
  • 如果b-1存储桶的大小为1,存储区大小为n-b,则会接近O(b + log(n))

这似乎是一个很好的复杂行为。它适应内容的细节,并且在输入大小方面具有绝对最差的时间性能线性。

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

空间复杂度

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