【问题标题】:Stratify list into unordered partitions将列表分层为无序分区
【发布时间】:2026-02-15 04:25:03
【问题描述】:

我正在努力为以下问题找到一个好的算法:

  • 输入:n 个整数的未排序列表
  • 输出:p 个(大致)大小相等的未排序列表,其中每个列表的每个最小元素都大于其之前列表的最大元素

目标是对输出进行分层,例如,在 p = 3 的情况下,我得到 3 个小数、中数和大数的无序列表(按此顺序)。

例如:

n = 10, p = 3

  • 输入:[4, 1, 8, 7, 9, 3, 6, 0, 2, 5]
  • 输出:[[1, 0, 2], [4, 3, 6, 5], [8, 7, 9]]

显然,我可以在O(n*log(n)) 时间通过简单地排序然后分区来做到这一点,但我想知道这是否不能在线性时间内完成。我知道 QuickSelect 在预期的O(n) 平均情况下运行,所以我的直觉是这个问题应该可以在O(p*n) 时间内解决。

天真地我认为你可以简单地运行 QuickSelect p 次,连续找到下一个第 k 个最小的元素,然后对每个元素执行类似基数的排序,以通过在原始步骤中识别的 p 个枢轴对元素进行分区.

所以:

  1. 我不确定我概述的算法是否有效
  2. 我不确定 确实需要O(p*n)
  3. 即使是O(p*n),我也不确定 这是一个最佳的复杂性(虽然我怀疑它是,因为它 似乎在 p = 1 和 p = n 的边缘情况下工作)
  4. 不是很 优雅

有更好的算法吗?

谢谢

【问题讨论】:

  • 举个例子,[[0], [1], [4, 8, 7, 9, 3, 6, 2, 5]] 是一个有效的输出吗?通过找到p-1 最小的元素,并将每个元素放在一个单例列表中,其余元素在最终列表中,可以在 O(p*n) 时间内找到它。如果它无效,则说明您的问题未充分说明。
  • 好点。目的是使输出列表具有相同大小的 +/- 1 个元素。更新问题。

标签: algorithm sorting


【解决方案1】:

QuickSelect其实是一种分区算法,所以在QuickSelecting之后不需要额外的步骤。

假设我们有一个函数 Partition(arr, lo, hi) 它返回一些 k 使得 lo <= k < hi 并重新排列 arr 使得 arr[i] <= arr[k] 如果 i < karr[k] <= arr[i] 如果 k < i .那么,本质上,QuickSelect 是:

# After this call:
#   arr[i] <= arr[med] if lo <= i < med
#   arr[med] <= arr[i] if med < i < hi
QuickSelect(arr, lo, med, hi):
  if lo < hi:
    k = Partition(arr, lo, hi)
    if med < k:
      QuickSelect(arr, lo, med, k)
    else if k < med:
      QuickSelect(arr, k + 1, med, hi)

这与快速排序非常相似:

QuickSort(arr, lo, hi):
  if lo < hi:
    k = Partition(arr, lo, hi)
    QuickSort(arr, lo, k)
    QuickSort(arr, k + 1, hi)

由于 QuickSelect 在指定点对数组进行分区(这不仅仅是查找相关元素),因此我们可以轻松地将 Stratify 定义为对 QuickSelect 的重复调用:

Stratify(arr, n, p):
  for i from 0 to p - 2 (inclusive):
    QuickSelect(arr, floor(i * n / p), floor((i+1) * n /p, n) 

由于 QuickSelect 是 O(n),所以上面的 Stratify 是 O(p*n)。仅对数组进行排序的选项将采用O(n log n),因此如果p 不在O(log n) 中,则上述分层很有用。 (由于log n 是一个很小的数字,在实践中很可能排序是优越的。)

但是,很容易将分层合并到 QuickSelect 中,我们可以将这种算法称为 QuickStratify。 QuickStratify 执行快速排序,精确到数组被统计的点:

为方便起见,报告给定索引属于哪个层的函数:

Stratum(i, n, p): floor(i * p / n)

现在:

QuickStratify(arr, n, p, lo, hi):
  if Stratum(lo, n, p) < Stratum(hi, n, p):
    k = Partition(arr, lo, hi)
    QuickStratify(arr, n, p, lo, k)
    QuickStratify(arr, n, p, k + 1, hi)

我很确定 QuickStratify 是平均时间 O(n log p),但我手头没有证据,我可能错了。

【讨论】:

    【解决方案2】:

    你的算法在我看来不错。我唯一的狡辩是,我看不出您如何执行您所说的“类基数排序”。对于每个值 x,您需要确定它进入哪个 p 个插槽,并且由于这些插槽似乎没有非常特殊的结构(不像在常规基数排序中,它们对应于某个固定值的倍数)我认为您需要对每个值进行 O(log p) 比较。

    假设只使用比较,你不能比 O(n log n) 做得更好,因为如果可以的话,你可以通过设置 p = n 和运行这个算法。

    还要注意,如果一个值可以出现多次,那么结果子集可能是任意不平衡的。 (如果您在条件中使用严格的“大于”,则这种可能性是不可避免的。)

    最后,如果担心最坏情况下的性能,有一个worst-case linear algorithm for selection。请注意,它有一个很大的常数,因此请仅在您的输入具有异常模式或来自潜在的敌对来源时才考虑它。

    【讨论】: