繁体   English   中英

近排序数组的插入排序最坏时间复杂度?

[英]insertion sort worst time complexity for near sorted array?

我有一个 n 元素数组。 4√n之外的所有元素都被排序。 我们不知道这些错位元素的位置。 对此列表进行排序的最有效方法是什么?

有没有 O(n) 的方法来做到这一点?

更新 1:

对于几乎排序的数据,插入排序的时间复杂度是 O(n)(在最坏的情况下是真的吗?)?

有一种对几乎已排序的数组进行排序的快速通用方法:

  1. 从头到尾扫描原始数组。 如果您发现两个项目的顺序不正确,请将它们移动到第二个数组并从第一个数组中删除它们。 当心; 例如,如果删除 x2 和 x3,则需要再次检查 x1 ≤ x2。 这是在 O(n) 时间内完成的。 在您的情况下,新数组的大小最多为 8sqrt(n)。

  2. 对第二个数组进行排序,然后合并两个数组。 由于第二个数组的项数较少,任何合理的排序算法都会在O(n)中对较小的第二个数组进行排序,合并又需要O(n),所以总时间为O(n)。

如果使用 O(n log n) 算法对第二个数组进行排序,那么只要错误位置的项数最多为 O(n / log n),排序就是 O(n)。

不,插入排序不是 O(n)。 最坏的情况是最后4√n 个元素错位了,而且它们太小以至于它们位于数组的前面 需要插入排序 Θ(n √n) 才能将它们移到那里。

这是gnasher729 答案的 Python 实现,在这种接近排序的输入上是 O(n) 时间和 O(n) 空间。 但是,我们不能天真地从数组中“删除”对,这将是低效的。 相反,我将正确排序的值移动到一个good列表中,将错误排序的对移动到一个bad列表中。 所以只要数字在增加,它们就会被添加到good 但是如果下一个数字x小于最后一个好数字good[-1] ,那么它们都被移动到bad 完成后,我将goodbad连接起来,然后让 Python 的Timsort完成剩下的工作。 它在 O(n - √n) 时间内检测已经排序good运行,然后在 O(√n log √n) 时间内对bad部分进行排序,最后在 O(n) 时间内合并两个已排序部分。

def sort1(a):
    good, bad = [], []
    for x in a:
        if good and x < good[-1]:
            bad += x, good.pop()
        else:
            good += x,
    a[:] = sorted(good + bad)

接下来是空间改进版本,需要 O(n) 时间和 O(√n) 空间。 我没有将好的部分存储在额外的列表中,而是将其存储在a[:good]

def sort2(a):
    good, bad = 0, []
    for x in a:
        if good and x < a[good-1]:
            bad += x, a[good-1]
            good -= 1
        else:
            a[good] = x
            good += 1
    a[good:] = bad
    a.sort()

这是另一个 O(n) 时间和 O(√n) 空间变化,我让 Python 对我bad ,但我自己将好的部分与坏的部分从右到左合并。 所以这不依赖于 Timsort 的排序运行检测,因此很容易移植到其他语言:

def sort3(a):
    good, bad = 0, []
    for x in a:
        if good and x < a[good-1]:
            bad += x, a[good-1]
            good -= 1
        else:
            a[good] = x
            good += 1
    bad.sort()
    i = len(a)
    while bad:
        i -= 1
        if good and a[good-1] > bad[-1]:
            good -= 1
            a[i] = a[good]
        else:
            a[i] = bad.pop()

最后,一些测试代码:

from random import random, sample
from math import isqrt

def sort1(a):
    ...

def sort2(a):
    ...

def sort3(a):
    ...

def fake(a):
    """Intentionally do nothing, to show that the test works."""

def main():
    n = 10**6
    a = [random() for _ in range(n)]
    a.sort()
    for i in sample(range(n), 4 * isqrt(n)):
        a[i] = random()

    for sort in sort1, sort2, sort3, fake:
        copy = a.copy()
        sort(copy)
        print(sort.__name__, copy == sorted(a))

if __name__ == '__main__':
    main()

输出,显示两个解决方案都通过了测试(并且测试有效,检测到fake的不正确):

sort1 True
sort2 True
sort3 True
fake False

有趣的事实:对于 Timsort 单独(即,不用作上述算法的一部分),我上面提到的最坏情况是最好的情况:它会在 O(n) 时间内对其进行排序。 就像我第一个版本的sorted(good + bad) ,它会在 O(n - √n) 时间内识别 n-√n 个已排序元素的前缀,对 O(√n log √n) 中的 √n 最后一个元素进行排序) 时间,然后在 O(n) 时间内合并两个已排序的部分。

那么我们可以让 Timsort 做所有事情吗? 在所有这些接近排序的输入上都是 O(n) 吗? 不,这不对。 如果 4√n 个错位元素均匀分布在数组上,那么我们最多有 4√n 次排序运行,Timsort 将花费 O(n log(4√n)) = O(n log n) 时间来合并它们。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM