【问题标题】:Dividing the elements of an array in 3 groups将数组的元素分为 3 组
【发布时间】:2013-10-11 20:48:50
【问题描述】:

我必须将数组的元素分成 3 组。这需要在不对数组进行排序的情况下完成。考虑这个例子

我们有 120 个未排序的值,因此最小的 40 个值需要在第一组中,接下来的 40 个在第二组中,最大的 40 个在第三组中

我正在考虑中位数方法,但无法将其应用于我的问题,请建议一种算法。

【问题讨论】:

  • 我想这是就地完成的,使用 O(1) 额外存储和 O(n) 时间,对吧?
  • 是否有无法排序的原因?我认为中位数和排序的中位数都是 O(n lg(n))
  • 实数还是整数?如果它们是整数,则可以使用基数排序。
  • @jwpat7 我认为任务可以在 O(nlog3) 内完成,O(n) 会很棒
  • O(nlog3) 和 O(n) 是一样的

标签: algorithm computer-science median-of-medians


【解决方案1】:

您可以在阵列上调用quickselect 两次以就地执行此操作,平均情况下是线性时间。通过使用线性时间median of medians algorithm 为快速选择选择最佳枢轴,也可以将最坏情况的运行时间提高到 O(n)。

对于 quickselect 的两次调用,使用 k = n / 3。在第一次调用时,对整个数组使用 quickselect,在第二次调用时,使用 arr[k..n-1](对于 0 -索引数组)。

快速选择的维基百科解释:

快速选择使用与快速排序相同的整体方法,选择一个 元素作为枢轴并根据 枢轴,因此小于或大于枢轴。然而, 而不是递归到双方,如快速排序,快速选择 只递归到一侧——它所在的元素的一侧 寻找。这降低了 O(n log n) 的平均复杂度(在 快速排序)到 O(n)(在快速选择中)。

与快速排序一样,快速选择通常作为就地实现 算法,除了选择第 k 个元素之外,它还部分地 对数据进行排序。请参阅selection algorithm 以进一步讨论 与排序有关。

要将数组的元素分成3组,请结合使用以下用Python编写的算法并结合快速选择:

k = n / 3

# First group smallest elements in array
quickselect(L, 0, n - 1, k) # Call quickselect on your entire array

# Then group middle elements in array
quickselect(L, k, n - 1, k) # Call quickselect on subarray

# Largest elements in array are already grouped so
# there is no need to call quickselect again

这一切的关键在于快速选择使用了一个称为分区的子程序。分区将数组分成两部分,大于给定元素的部分和小于给定元素的部分。因此,它围绕该元素对数组进行部分排序,并返回其新的排序位置。因此,通过使用快速选择,您实际上可以对第 k 个元素周围的数组进行部分排序(请注意,这与实际对整个数组进行排序不同)就地和平均情况下的线性时间。

快速选择的时间复杂度:

  1. 最坏情况下的性能 O(n2)
  2. 最佳案例性能 O(n)
  3. 平均案例性能 O(n)

快速选择的运行时间几乎总是线性的,而不是二次的,但这取决于这样一个事实,即对于大多数数组,简单地选择一个随机枢轴点几乎总是会产生线性运行时间。但是,如果您想提高快速选择的最坏情况性能,您可以选择在每次调用之前使用median of medians algorithm 来近似用于快速选择的最佳枢轴。这样做,您会将快速选择算法的最坏情况性能提高到 O(n)。这种开销可能不是必需的,但如果您正在处理大量随机整数列表,它可以防止您的算法出现一些异常的二次运行时。

这是一个完整的 Python 示例,它实现了快速选择并将其两次应用于 120 个整数的反向排序列表并打印出三个结果子列表。

from random import randint


def partition(L, left, right, pivotIndex):
    '''partition L so it's ordered around L[pivotIndex]
       also return its new sorted position in array'''
    pivotValue = L[pivotIndex]
    L[pivotIndex], L[right] = L[right], L[pivotIndex]
    storeIndex = left
    for i in xrange(left, right):
        if L[i] < pivotValue:
            L[storeIndex], L[i] = L[i], L[storeIndex]
            storeIndex = storeIndex + 1
    L[right], L[storeIndex] = L[storeIndex], L[right]
    return storeIndex


def quickselect(L, left, right, k):
    '''retrieve kth smallest element of L[left..right] inclusive
       additionally partition L so that it's ordered around kth 
       smallest element'''
    if left == right:
        return L[left]
    # Randomly choose pivot and partition around it
    pivotIndex = randint(left, right)
    pivotNewIndex = partition(L, left, right, pivotIndex)
    pivotDist = pivotNewIndex - left + 1
    if pivotDist == k:
        return L[pivotNewIndex]
    elif k < pivotDist:
        return quickselect(L, left, pivotNewIndex - 1, k)
    else:
        return quickselect(L, pivotNewIndex + 1, right, k - pivotDist)


def main():
    # Setup array of 120 elements [120..1]
    n = 120
    L = range(n, 0, -1)

    k = n / 3

    # First group smallest elements in array
    quickselect(L, 0, n - 1, k) # Call quickselect on your entire array

    # Then group middle elements in array
    quickselect(L, k, n - 1, k) # Call quickselect on subarray

    # Largest elements in array are already grouped so 
    # there is no need to call quickselect again

    print L[:k], '\n'
    print L[k:k*2], '\n'
    print L[k*2:]


if __name__ == '__main__':
    main()

【讨论】:

  • 这个算法工作正常,但如果我有 n 个元素和 G 组,整体复杂度会是多少?
  • 你必须调用快速选择(G-1)次,具体取决于你拥有的组数。对快速选择的每次调用都是 O(n)。因此,总体复杂度为 O(G*n),如果您愿意,其中 G 可能是 n 本身。但是,如果 G == n,您可能最好使用快速排序之类的排序算法。 :)
【解决方案2】:

我会看看order statistics。统计样本的第 k 阶统计量等于其第 k 个最小值。计算列表中第 k 个最小(或最大)元素的问题称为选择问题,由 selection algorithm 解决。

以中位数的方式思考中位数是正确的。但是,您可能希望从数组中找到第 20 个和第 40 个最小的元素,而不是找到中位数。就像找到中位数一样,使用选择算法只需线性时间即可找到它们。最后你遍历数组,根据这两个元素划分元素,这也是线性时间。

PS。如果这是您在算法课上的练习,this 可能会对您有所帮助:)

【讨论】:

    【解决方案3】:

    分配一个与输入数组大小相同的数组 扫描输入数组一次并跟踪数组的最小值和最大值。 同时将第二个数组的所有值设置为 1。 计算delta = (max - min) / 3。 再次扫描数组,如果编号为&gt; min+delta and &lt; max-delta,则将第二个数组设置为两个;否则if &gt;= max-delta,设置为3; 结果,您将拥有一个数组,该数组告诉您该数字在哪个组中。 我假设所有数字都彼此不同。 复杂度:O(2n)

    【讨论】:

    • 没有直接提到,但是数组应该分成3个相等(元素个数相同)的组,否则没有意义。在您的情况下,您只得到一些 3 个组,但不相等(例如,尝试值为 0、1000 且所有其他元素都等于 1 的数组)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-05-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多