【问题标题】:Sort name & time complexity排序名称和时间复杂度
【发布时间】:2011-11-27 13:09:19
【问题描述】:

我“发明”了“新”排序算法。好吧,我知道我不能发明一些好的东西,所以我试图在维基百科上搜索它,但所有排序算法似乎都不是我的。所以我有三个问题:

  1. 这个算法叫什么名字?
  2. 为什么它很糟糕? (最佳、平均和最差时间复杂度)
  3. 我可以继续使用这个想法让它变得更好吗?

所以,我的算法的想法:如果我们有一个数组,我们可以计算排序元素的数量,如果这个数字小于长度的一半,我们可以反转数组以使其更有序。之后我们可以对数组的前半部分和后半部分进行排序。在最好的情况下,我们只需要 O(n) - 如果数组完全按好或坏的方向排序。我在评估平均时间复杂度和最差时间复杂度时遇到了一些问题。

C#代码:

    public static void Reverse(int[] array, int begin, int end) {
        int length = end - begin;
        for (int i = 0; i < length / 2; i++)
            Algorithms.Swap(ref array[begin+i], ref array[begin + length - i - 1]);
    }
    public static bool ReverseIf(int[] array, int begin, int end) {
        int countSorted = 1;
        for (int i = begin + 1; i < end; i++)
            if (array[i - 1] <= array[i])
                countSorted++;

        int length = end - begin;
        if (countSorted <= length/2)
            Reverse(array, begin, end);

        if (countSorted == 1 || countSorted == (end - begin))
            return true;
        else
            return false;
    }
    public static void ReverseSort(int[] array, int begin, int end) {
        if (begin == end || begin == end + 1)
            return;
        // if we use if-operator (not while), then array {2,3,1} transforms in array {2,1,3} and algorithm stop
        while (!ReverseIf(array, begin, end)) {
            int pivot = begin + (end - begin) / 2;
            ReverseSort(array, begin, pivot + 1);
            ReverseSort(array, pivot, end);
        }
    }
    public static void ReverseSort(int[] array) {
        ReverseSort(array, 0, array.Length);
    }

P.S.:对不起我的英语。

【问题讨论】:

  • 我们可以在数组的一部分长度
  • Stooge sort不一样,但是味道一样。
  • 它没有名字,因为它实际上并没有排序。考虑 (3, 1, 2, 0)。这翻转到 (0, 2, 1, 3)。您将其拆分为 (0, 2) 和 (1, 3) 并递归。每个子数组都已排序,因此您完成了。结果数组为(0, 2, 1, 3),未排序。
  • 一旦两半按排序顺序(假设它们是),如何组合它们以使结果排序?
  • @nsinreal 我认为这更好,因为检查了排序数组。看我的回答。

标签: algorithm sorting time-complexity


【解决方案1】:

最好的情况是 Theta(n),例如,一个排序数组。最坏的情况是 Theta(n^2 log n)。

上限

次要子问题有一个排序数组,前面或后面有任意元素。这些是 O(n log n)。如果在此之前,我们做 O(n) 的工作,在前半部分解决次要子问题,然后在后半部分解决,然后再做 O(n) 更多的工作 – O(n log n)。如果成功,做 O(n) 工作,对已经排序的前半部分进行排序 (O(n)),在后半部分解决次要子问题,做 O(n) 次工作,在前半部分解决次要子问题,对已经排序后半部分 (O(n)),做 O(n) 工作 - O(n log n)。

现在,在一般情况下,我们解决两半上的两个主要子问题,然后使用辅助调用在枢轴上缓慢交换元素。需要 O(n) 次交换,因此直接应用主定理会产生 O(n^2 log n) 的界限。

下限

对于 k >= 3,我们使用上述分析作为指导,递归地构造一个大小为 2^k 的数组 A(k)。不好的情况是数组 [2^k + 1] + A(k)。

令 A(3) = [1, ..., 8]。这种已排序的基本情况使 Reverse 不会被调用。

对于 k > 3,令 A(k) = [2^(k-1) + A(k-1)[1], ..., 2^(k-1) + A(k-1 )[2^(k-1)]] + A(k-1)。请注意,[2^k + 1] + A(k) 的主要子问题等价于 [2^(k-1) + 1] + A(k-1)。

主递归调用后,数组为 [2^(k-1) + 1, ..., 2^k, 1, ..., 2^(k-1), 2^k + 1 ]。有 Omega(2^k) 元素必须移动 Omega(2^k) 位置,并且到目前为止移动元素的每个辅助调用都有 O(1) 排序的子问题,因此是 Omega(n log n)。


显然需要更多的咖啡——主要的子问题无关紧要。这使得分析 平均情况 并不算太糟糕,也就是 Theta(n^2 log n)

在概率不变的情况下,数组的前半部分至少包含最小四分位数的一半和最大四分位数的至少一半。在这种情况下,无论Reverse 是否发生,都有 Omega(n) 元素必须通过二次调用移动 Omega(n) 位置。

【讨论】:

  • 感谢您的回答。现在我明白了,为什么这个算法没有在 wiki 中描述
【解决方案2】:

看起来这个算法,即使它对“随机”数据执行得非常糟糕(正如 Per 在他们的回答中所证明的那样),对于“修复”“几乎排序”的数组来说是非常有效的。因此,如果你选择进一步发展这个想法(我个人不会,但如果你想把它当作一个练习),你最好专注于这个优势。

this reference on Wikipedia in the Inversion article 很好地暗示了这个问题。 Mahmoud 的书很有见地,指出有多种方法可以衡量“排序”。例如,如果我们使用反转次数来表征“近乎排序的数组”,那么我们可以使用插入排序来非常快速地对其进行排序。但是,如果您的数组以稍微不同的方式“几乎排序”(例如,一副被切割或松散洗牌的纸牌),那么插入排序将不是“修复”列表的最佳排序。

  • 输入:一个已经排序的数组,大小为 N,大约有 N/k 次反转。

我可能会为算法做这样的事情:

  • 计算反转次数。 (O(N lg(lg(N))),或者可以假设很小并跳过步骤)
    • 如果反转次数
    • 否则数组未接近排序;诉诸于使用您最喜欢的比较(或更好的)排序算法

虽然有更好的方法可以做到这一点;如果您对数组进行足够的预处理或使用正确的数据结构(例如具有链表属性的数组或支持二进制搜索的类似数组),则可以至少在 O(log(N)*(# new elements)) 时间内“修复”这样的数组。

您可以进一步概括这个想法。 “修复”阵列是否有效取决于所需的修复类型。因此,如果您在将元素添加到列表或对其进行修改时更新这些统计信息,则可以调度到一个好的“修复”算法。

但不幸的是,这一切都是编码的痛苦。 priority queue.

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-07-22
    • 2012-05-07
    • 2021-05-30
    • 1970-01-01
    • 1970-01-01
    • 2011-09-01
    相关资源
    最近更新 更多