排序名称&时间复杂性

时间:2011-11-27 13:09:19

标签: algorithm sorting time-complexity

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

  1. 此算法的名称是什么?
  2. 为什么这么糟透了? (最佳,平均和最差时间复杂度)
  3. 我还能用这个想法让它变得更好吗?
  4. 所以,我的算法的想法:如果我们有一个数组,我们可以计算已排序元素的数量,如果这个数字少于长度的一半,我们可以反转数组,使其更加有序。之后我们可以对数组的前半部分和后半部分进行排序。在最好的情况下,我们只需要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。:对不起我的英语。

2 个答案:

答案 0 :(得分:3)

最好的情况是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&gt; = 3,我们使用上述分析作为指导,递归地构造大小为2 ^ k的阵列A(k)。不好的情况是数组[2 ^ k + 1] + A(k)。

设A(3)= [1,...,8]。这个有序的基本案例使Reverse无法被调用。

对于k&gt; 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)位置。

答案 1 :(得分:1)

看起来这个算法,即使它对“随机”数据执行可怕(如Per在他们的回答中所示),对于“修复”“几乎排序”的数组非常有效。因此,如果你选择进一步发展这个想法(我个人不会,但如果你想把它当作一个练习),那么你应该把注意力放在这个力量上。

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

  • 输入:已经按大小N排序的数组,大致为N / k反转。

对于算法,我可能会这样做:

  • 计算反转次数。 (O(N lg(lg(N))),或者可以假设很小并跳过步骤)
    • 如果反转次数< [threshold],使用插入排序排序数组(它会很快)。
    • 否则数组不接近排序;求助于使用您最喜欢的比较(或更好)排序算法

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

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

但不幸的是,这对代码来说都是一种痛苦。你可能只需要priority queue就可以逃脱。