使用一组有限的操作对2个50000个数字的链表进行排序

时间:2015-11-14 03:42:17

标签: c algorithm list sorting optimization

所以我有这个项目用于学校:我有一个包含5万个数字和第二个空列表的链表。我只有一个非常有限的指示小组。他们是:

  • " SA"交换列表1的前两个元素

  • " SB"交换列表2的前两个元素

  • " SS"是" sa"和" sb"同时

  • " PA" :在列表1的顶部按列表2的顶部元素

  • " pb":在列表2的顶部推送列表1的顶部元素

  • " ra":旋转列表1(第一个元素成为最后一个元素)

  • " rb":旋转列表2(首先成为最后一个)

  • " rr":" ra"" rb"马上

  • " rra":旋转列表1(最后成为第一个)

  • " rrb":旋转列表2(最后成为第一个)

  • " rrr":" rra"和" rrb"马上

我必须在c中实现排序算法,目标是用最少量的指令来完成。 我尝试了一个非常简单的算法,旋转列表一直到最大值在顶部,然后重复推送到列表2,直到所有内容都在列表2中,然后将所有内容推回到列表1中,但我无法对列表进行排序在合理的时间内超过5k的数字

2 个答案:

答案 0 :(得分:6)

我想我已经想出如何使用快速排序来做到这一点。这是一些伪代码。

编辑:更新的伪代码,专注于它正在做的事情,而不是不必要的语法

quicksort(int n)
    if n == 1 return
    int top_half_len = 0
    choose a median //it's up to you to determine the best way to do this
    for 0 to n {    //filter all values above the median into list 2
        if (value > median) {
            push list 1 top to list 2 //list 2 stores the larger half
            top_half_len++
        }
        rotate list 1 forward
    }

    //reverse the list back to original position
    rotate list 1 backward (n - top_half_len) times

    //push larger half onto smaller half
    push list 2 top to list 1 top_half_len times

    //recursively call this on the larger half
    quicksort(top_half_len)

    //rotate smaller half to front
    rotate list 1 forward top_half_len times

    //recursively call this on smaller half
    quicksort(n - top_half_len) 

    //reverse list back to original position
    rotate list 1 backward top_half_len times

基本上,它将列表分成小于或等于中位数(较小的一半)的部分和大于中位数的部分(较大的一半)。然后它会在这两半中调用自己。一旦它们的长度为1,算法就完成了,因为对1长度列表进行了排序。谷歌快速排序实际解释。

我认为这应该有用,但我可能错过了一些优势案例。不要盲目跟随这一点。另外,如果你正在处理数组,我建议你在某个递归深度停止快速排序并使用堆排序(或者某些东西来防止最坏情况下的O(n ^ 2)复杂度),但我不确定这会有什么效率。 更新:根据Peter Cordes的说法,当你达到某个数组大小时,你应该使用插入排序(IMO也应该在某个递归深度)。

显然,链接列表上的合并排序更快。修改它以实现合并排序可能不会太难。合并排序与快速排序非常相似。 why is merge sort preferred over quick sort for sorting linked lists

答案 1 :(得分:1)

问题陈述缺少比较函数,因此我将定义compare(lista,listb)以将lista的第一个节点与listb的第一个节点进行比较,并返回-1表示<,0表示=,1表示更大,或者合并排序真正需要的只是0表示< =和1表示>。

还缺少一个返回值,表示在执行pa或pb时列表为空。我会定义pa或pb如果源列表不为空则返回1,如果源列表为空则定义为0(没有要复制的节点)。

目前尚不清楚目标是否是最小量的指令是指源代码中的指令数量或排序期间执行的指令数量。

-

代码中最小数量的指令将基于与list1的比较来旋转list2,以将节点插入到正确位置的list2中。从pb开始,并将list2大小设置为1.然后执行rb或rrb将list2旋转到下一个pb应该完成的位置。代码将跟踪list2“偏移”到最小节点,以避免旋转list2中的无限循环。复杂度为O(n ^ 2)。

-

我认为最快的排序,也许最少的指令执行是自下而上的合并排序。

在旋转列表时执行自下而上的合并排序,使用它们像循环缓冲区/列表。使用序列|将list1复制到list2以生成节点计数count = 0 |而(pb){rb | count + = 1}。

使用计数,使用{pa,rr}将每个其他节点从list2移动到list1,n / 2次。始终跟踪每个列表上的实际节点数,以便知道何时到达列表的逻辑结尾。还要跟踪每个列表的运行计数器,以了解何时达到运行的逻辑结束。此时,您有两个列表,其中偶数节点位于list1上,奇数节点位于list2上。

跑步大小从1开始,每次传球加倍。对于运行大小为1的第一次传递,将偶数节点与奇数节点合并,创建大小为2的已排序运行,交替将已排序的节点对添加到list1和list2。例如,如果追加到list1和list1节点< = list2节点,请使用{ra,run1count - = 1},否则使用{pa,ra,run2count - = 1}。到达运行结束时,将剩余的剩余运行追加到列表的末尾。在下一次传递中,合并从列表中排序的2个节点的运行,交替地将4个节点的排序运行附加到每个列表。继续运行8,16,...直到所有节点都在一个列表中结束。

这是计算节点的一次传递,一次传递以分割偶数和奇数节点,并且ceil(log2(n))传递以进行合并排序。链表操作的开销很小(旋转删除并附加节点),因此整体合并应该相当快。

计数传递的指令数可以用while(pb){count + = 1}来减少,这会将list1复制到list2。然后将list2吐出到list1中也可以使用rrr来解除它们。

复杂度为O(n log(n))。