Algorithm 稳定、高效的排序?

Algorithm 稳定、高效的排序?,algorithm,language-agnostic,data-structures,sorting,Algorithm,Language Agnostic,Data Structures,Sorting,我正在尝试创建一个非常节省空间的不同寻常的关联数组实现,我需要一个满足以下所有条件的排序算法: 稳定(不会更改具有相等键的元素的相对顺序。) 就地或几乎就地(O(logn)堆栈可以,但没有O(n)空间使用或堆分配 O(n logn)时间复杂度 还要注意,要排序的数据结构是一个数组 很容易看出,有一个基本算法可以匹配这三个条件中的任意两个(插入排序匹配1和2,合并排序匹配1和3,堆排序匹配2和3),但我一辈子都找不到任何匹配这三个条件的算法。快速排序呢 Exchange也可以做到这一点,按照您的说

我正在尝试创建一个非常节省空间的不同寻常的关联数组实现,我需要一个满足以下所有条件的排序算法:

  • 稳定(不会更改具有相等键的元素的相对顺序。)
  • 就地或几乎就地(O(logn)堆栈可以,但没有O(n)空间使用或堆分配
  • O(n logn)时间复杂度
  • 还要注意,要排序的数据结构是一个数组

    很容易看出,有一个基本算法可以匹配这三个条件中的任意两个(插入排序匹配1和2,合并排序匹配1和3,堆排序匹配2和3),但我一辈子都找不到任何匹配这三个条件的算法。

    快速排序呢


    Exchange也可以做到这一点,按照您的说法,它可能更“稳定”,但快速排序更快。

    我相信合并排序可以写在适当的位置。这可能是最好的方法。

    也许吧?如果我正确回忆我的数据结构课程,它往往是稳定的,但更糟糕的情况是时间是O(n log^2 n),尽管它执行O(n)在几乎已排序的数据上。它基于插入排序,因此可以进行适当的排序。

    上有一个排序算法列表。它包括按执行时间、稳定性和分配进行分类


    您最好的选择可能是将一个有效的不稳定排序修改为稳定排序,从而降低其效率。

    注意:标准快速排序不是O(n log n)!在最坏的情况下,它最多需要O(n^2)问题是你可能会关注一个远离中位数的元素,因此你的递归调用是高度不平衡的

    有一种方法可以解决这个问题,那就是仔细挑选一个中位数,这个中位数保证,或者至少很可能接近中位数。令人惊讶的是,你实际上可以在线性时间内找到准确的中位数,尽管在你的情况下,听起来你关心速度,所以我不建议这样做

    我认为最实用的方法是实现一个稳定的快速排序(很容易保持稳定),但在每一步都使用5个随机值的中位数作为轴心。这使得排序速度很慢的可能性很小,而且是稳定的


    顺便说一句,合并排序可以就地完成,尽管就地和稳定都很棘手。

    有一类稳定的就地合并算法,尽管它们复杂且线性,在O(n)中隐藏了一个相当高的常数。要了解更多信息,请查看


    编辑:合并阶段是线性的,因此合并排序是nlog_n。

    在证明O(n log n)的重要性之前,不要太担心它。如果你能找到一个常数大大降低的O(n^2)算法,那就试试吧

    如果您的数据受到高度限制,则一般最坏情况的情况与此无关


    简而言之:运行一些测试。

    因为您的元素位于数组(而不是链表)中,所以您可以在数组索引本身中获得有关其原始顺序的一些信息。您可以通过编写排序和比较函数来了解索引:

    function cmp( ar, idx1, idx2 )
    {
       // first compare elements as usual
       rc = (ar[idx1]<ar[idx2]) ? -1 : ( (ar[idx1]>ar[idx2]) ? 1 : 0 );
    
       // if the elements are identical, then compare their positions
       if( rc != 0 )
          rc = (idx1<idx2) ? -1 : ((idx1>idx2) ? 1 : 0);
    
       return rc; 
    }
    
    函数cmp(ar、idx1、idx2) { //首先,像往常一样比较元素 rc=(ar[idx1]ar[idx2])?1:0; //如果元素相同,则比较它们的位置 如果(rc!=0) rc=(idx1idx2)?1:0; 返回rc; }
    只要排序只执行元素交换,就可以使用此技术使任何排序保持稳定。元素的索引将发生变化,但相同元素的相对顺序将保持不变,因此排序保持健壮。对于像heapsort这样的排序来说,它不会开箱即用,因为原始的heapification“抛弃”了相对的排序,尽管您可以将这个想法应用到其他排序中。

    快速排序可以通过在每个记录中添加一个序列字段而变得相当简单,在排序之前将其初始化为索引,并将其用作排序键的最低有效部分

    这对所花费的时间有轻微的不利影响,但不会影响算法的时间复杂度。对于每个记录,它的存储成本开销也很小,但在获得大量记录之前,这一点几乎不重要(并且可以通过更大的记录大小来模拟)

    我在
    C
    qsort()
    函数中使用了此方法,以避免编写自己的函数。在调用
    qsort()
    之前,每条记录都添加了一个32位整数,并填充了起始序列号

    然后,比较函数检查键和序列(这保证没有重复的键),将快速排序转变为稳定排序。我记得,对于我使用的数据集,它仍然优于固有的稳定mergesort


    您的里程数可能会有所不同,因此请始终记住:测量,不要猜测

    也许我有点墨守成规,但我喜欢手工编码的合并排序。它简单、稳定且行为良好。它需要的额外临时存储空间只有
    N*sizeof(int)
    ,这还不算太糟糕。

    有一个很好的排序函数列表,可以帮助您找到所需的任何类型的排序函数

    例如,为了解决您的特定问题,您需要的似乎是就地合并排序


    不过,您可能还想看看,它有一些非常有趣的属性。

    通过在链接列表上执行快速排序,可以使其稳定。这需要n个随机或中位数选择3个枢轴,但常数非常小(列表遍历)

    通过拆分列表并确保对左侧列表进行排序,使相同的值向左移动,对右侧列表进行排序,使相同的值向右移动,排序将是隐式稳定的,不会产生实际的额外成本。另外,由于这是一个任务而不是交换,我认为速度可能会比快速交换稍微好一点