合并两个排序列表


14

合并排序

在此挑战中,您将实现合并排序的合并子例程。具体来说,您必须创建一个函数或程序或动词或类似词,该函数或函数或类似物包含两个列表,每个列表以升序排列,然后将它们组合成一个列表,以升序排列。要求:

-您的算法必须在输入大小上采用渐近线性的时间量。请停止提供O(n ^ 2)个解决方案。

  • 您不得使用任何能够对列表进行排序或合并列表的内置函数。作者的自由裁量权。
  • 该代码应能够处理重复的元素。
  • 不用担心空列表。

例子:

merge([1],[0,2,3,4])
[0,1,2,3,4]

merge([1,5,10,17,19],[2,5,9,11,13,20])
[1, 2, 5, 5, 9, 10, 11, 13, 17, 19, 20]

这是,所以可能最短的代码获胜!


我们是否必须处理列表中的重复元素,或者仅处理两个列表之间的重复元素?
基思·兰德尔

我们都说。想法是您应该能够使用它进行合并排序。
isaacg 2014年

破坏输入数组是否洁净?
skibrianski 2014年

3
我不确定如何解释算法必须花费渐近线性的时间。算法不需要花费任何时间,而实现则需要花费任何时间。我的Golfscript答案的执行时间在Ruby解释器中为O(scary),但是Online Golfscript Tester的性能要好得多,并且实际上可能是线性的(尽管没有源代码也没有真正的告诉方法)。我的观点是:b=a;b=b.length可能会复制整个数组a(如果对每个元素都执行,则将导致O(n ^ 2)时间)或仅复制对数组的引用(O(n)时间)。哪一个重要?
丹尼斯

1
我想在这样的情况下,请尽力弄清楚,但是如果老实说不出话,您可以假设一切正常,就像您提到的第二种选择一样。如果您的语言没有标准的口译员,则可以假定口译员工作良好。
isaacg 2014年

Answers:


8

Rebmu(35 32个字符)

u[iG^aNXa[rvA]apGtkFaM?fA]apGscA

测试

>> rebmu/args [u[iG^aNXa[rvA]apGtkFaM?fA]apGscA] [[1 5 10 17 19] [2 5 9 11 13 20]] 
== [1 2 5 5 9 10 11 13 17 19 20]

>> rebmu/args [u[iG^aNXa[rvA]apGtkFaM?fA]apGscA] [[2 5 9 11 13 20] [1 5 10 17 19]] 
== [1 2 5 5 9 10 11 13 17 19 20]

关于

Rebmu是Rebol的方言,允许在需要简洁的情况下“拼写”常规代码。不加修改,代码的工作原理类似于:

u [                     ; until
    i g^ a nx a [       ; if greater? args next args
       rv a             ; reverse args
    ]                   ; (we want the block containing the next value first)

    ap g tk f a         ; append output take first args
    m? f a              ; empty? first args
]                       ; (first block is now empty)

ap g sc a               ; append output second args
                        ; (add the remainder of the second)

我相信这满足了O(n)的要求,因为直到块的循环最多是输入长度的循环(并且reverse唯一切换输入块容器的顺序,而不是块本身)。使用take也许是一种自由,但对效率的影响仍然很小。

Rebol(83 75字符)

只是有点不同:在Rebol中,路径是比first或短的表达式seconda是包含两个块的输入块:

until[if a/2/1 < a/1/1[reverse a]append o:[]take a/1 tail? a/1]append o a/2

5

OP的解决方案:

哈斯克尔49 44 40

k@(p:r)%l@(q:s)|p>=q=q:k%s|0<1=l%k
a%_=a

的Python 131 105 101 99 93

感谢@Evpok:

f=lambda u,v:v and(v[-1]<u[-1]and f(v,u)or[b.append(a)for a,b in[(v.pop(),f(u,v))]]and b)or u

1
您可以a%b=a++b在主模式匹配之后编写以处理空列表,这将删除几个字符。
swish 2014年

如果第一个列表首先用完了内容,Haskell解决方案不会失败吗?
John Dvorak 2014年

如果您查看第一个函数,它将以缩短的列表作为第二个参数,以加长的列表作为第一个参数来递归调用该函数,否则交换参数。因此,第一个参数永远不会变短。由于OP不会将其开始为空,因此它将永远不会为空。
isaacg 2014年

4

巨蟒(79)

from itertools import*
def m(*a):
 while any(a):yield min(compress(a,a)).pop(0)

Python(95,如果不允许我们返回生成器)

from itertools import*
def m(*a):
 r=[]
 while any(a):r+=[min(compress(a,a)).pop(0)]
 return r

Itertools是解决所有世俗问题的解决方案。

奖励:这两个工作在任意数量的列表上,并且确实要担心空列表(例如,他们会很高兴地获得2个空列表,并返回一个空列表,或者获得1个空列表和1个非空列表,并返回非空。两个非屈服函数的另一个附加功能:它们也将不带参数运行,仅返回一个空列表。)

取消高尔夫:

from itertools import *  # Import all items from itertools
def m(*a):               # Define the function m, that takes any number of arguments, 
                         #  and stores those arguments in list a
    r=[]                 # Create an empty list r                         
    while any(a):        # While any element in a returns True as value:
        b=compress(a,a)  # Remove any element from a that isn't True (empty lists)
                         #  The example in the official documentation is this one:
                         #  compress('ABCDEF', [1,0,1,0,1,1]) --> A C E F
        c=min(b)         # Sort the lists by first value, and take the first one of these.
        d=c.pop(0)       # Take the first element from c
        r.append(d)      # Append this first element to r
    return r             # Gives back r

在没有生成器的解决方案中,请使用r+=[...]代替r.append(...)(每次节省4个字符)
2014年

我的意思不是冒犯任何人,但是,如果您的答案包含的语言代码只是另一种针对高尔夫的修改而已,那么我将对此予以否决。很遗憾,真正的python答案很好。
地下

如果您将它们拆分为不同的帖子,我将为python投票。
地下

4
@undergroundmonorail您是否对GolfScript的所有答案都投了反对票?
Evpok 2014年

1
@Evpok现在您提到它,不妨将其放在meta上,然后看看他们在那儿怎么说。
ɐɔıʇǝɥʇuʎs

3

C-75

这可以对的NULL终止数组进行操作int *,尽管通过将适当的比较函数替换为**b < **a(例如strcmp(*b, *a) < 0),它对于指向其他类型的指针也同样适用。

void m(int**a,int**b,int**c){while(*a||*b)*c++=!*a||*b&&**b<**a?*b++:*a++;}

取消高尔夫:

void merge(int **a, int **b, int **c)
{
    while(*a || *b)
        *c++ = !*a || *b && **b < **a
            ? *b++
            : *a++;
}

3

Golfscript,29 27 30 29 26字节

~{.0=@.0=@<{}{\}if(@@.}do;~]p

要么

~{.0=@.0=@>{\}*(@@.}do;~]p

怎么运行的

命令

golfscript merge.gs <<< '[2 3] [1 4]'

将得到如下处理:

~            # Interpret the input string.
             #
             # STACK: [2 3] [1 4]
{            #
    .@=0.@=0 # Duplicate the two topmost arrays of the stack and extract their first 
             # elements. This reverses the original order of the first copy.
             #
             # STACK: [1 4] [2 3] 2 1
             #
    >        # Check if the respective first elements of the arrays are ordered.
             #
             # STACK: [1 4] [2 3] 1
             #
    {\}*     # If they are not, swap the arrays. This brings the array with the smallest
             # element to the top of the stack.
             #
             # STACK: [2 3] [1 4]
             #
    (@@      # Shift the first element of the array on top of the stack and rotate it
             # behind the arrays.
             #
             # STACK: 1 [2 3] [4]
             #
    .        # Duplicate the topmost array.
             #
             # STACK: 1 [2 3] [4] [4]
             #
}do          # Repeat this process if the array is non-empty.
             #
             # STACK: 1 [2 3] [4] -> 1 2 [4] [3] -> 1 2 3 [4] []
             #
;~           # Delete the empty array from the stack and dump the non-empty array.
             #
             # STACK: 1 2 3 4
             #
]p           # Combine all elements on the stack into a single array, the to a string and
             # print.

输出为:

[1 2 3 4]

堆栈中的数组重复是否使其变为O(n ^ 2)?
2014年

@swish:我不是计算机科学家,但我会说这取决于实现方式。如果解释器实际上复制了整个数组,那么我猜确实是这样。
丹尼斯

对于非常相似的数组(例如[1 1 1 ... 2][1 1 1 ... 3]),以前的版本是O(n ^ 2),因为在这种情况下比较数组(而不是它们的第一个元素)会很慢。
丹尼斯

新版本中发生的唯一阵列操作是堆栈上的复制,交换和旋转。由于复制的数组仅用于提取单个元素并测试数组的非空性(Golfscript中的两个破坏性操作),因此上述代码可以在O(n)时间内运行(通过复制,交换和旋转对数组的引用)。数组)。实际表现取决于口译员。
丹尼斯

2

J- 42 33

来自此处的修改版本+ @algorithmshark的评论

k=:(m}.),~0{]
m=:k~`k@.(>&{.) ::,

k将右侧数组的开头添加到两个数组的合并尾部。k~是一样的,但是数组翻转了。(>&{.)正在比较头。如果其中一个数组为空,则代码将引发错误,在这种情况下,我们仅返回它们的串联,


我想是因为这/:~ a,b是禁止的答案(以及[:/:~,),所以您正在争取的最短答案不包括/:,对吧?
Dane

我将指出问题是,“不用担心空列表”。
Dane

@Dane停止递归所需的空性测试。
swish 2014年

m=:k~`k@.(>&{.)`,@.(0=*&#)节省2个字符。
algorithmhark

实际上,您可以将整个内容降低到33个char:k=:(m}.),~0{]m=:k~`k@.(>&{.) ::,。我们0{通常在列表为空时引发错误,然后捕获该错误并以退出,
algorithmhark

2

JavaScript(ES6),69 79字节

f=(a,b,c=[])=>(x=a[0]<b[0]?a:b).length?f(a,b,c.concat(x.shift())):c.concat(a,b)

怎么运行的

f = (a, b, c = []) =>          // `f' is a function that takes arguments `a', `b' and `c' -
                               // `c' defaults to `[]' - which returns the following
                               // expression:
                               //
 (x = a[0] < b[0] ? a : b)     // Store the array among `a' and `b' with the smaller first 
                               // element in `x'.
                               //
 .length ?                     // If it's non-empty,
                               //
  f(a, b, c.concat(x.shift())) // append the first element of array `x' to array `c' and run
                               // `f' again;
                               //
  : c.concat(a,b)              // otherwise, append the arrays `a' and `b' to `c'.
                               //
)

将数组与<运算符进行比较是无效的,因为它会进行字符串比较:f([123, 456, 789], [1, 2, 3, 4, 5]) => [1, 123, 2, 3, 4, 456, 5, 789]
nderscore 2014年

@nderscore:对。无论如何它都不会起作用,因为比较整个数组可能不是O(n)。对于非空性测试似乎也是如此,该测试必须将整个数组转换为字符串。
丹尼斯

是的,我不确定用于array-> string类型转换的big-o是什么。
nderscore 2014年

1
将一个数组与之连接[],然后将其转换为字符串需要O(n)时间。对数组的所有n个元素执行一次操作需要O(n ^ 2)时间。
丹尼斯

说得通。得到它了。
nderscore 2014年

2

蟒蛇(63)(69)(71)

def m(a,b):
 if a[0]>b[0]:a,b=b,a
 return[a.pop(0)]+(m(a,b)if a else b)

我在看到OP关于其他答案的运行时的评论之前就写了这篇文章,因此这是另一种在算法上是O(n)的解决方案,而不是实现上的解决方案。


哪些算法从数组的开头提取为O(1)?哪些列表比较算法采用O(1)?另外,您可以通过将... if ... else ...更改为... and ... or ...来进一步打高尔夫球
isaacg 2014年

@isaacg射击,我忘记了重复可能会使列表比较为O(n)。因此,我对另外6个字符进行了优化。您可以在链表的O(1)中提取并追加到前面。我看不出如何才能使...和...或...在返回值方面表现出色。
xnor

好的,我现在知道该怎么做...和...或...,但是由于需要parens,它不能保存字符。return[a.pop(0)]+(a and m(a,b)or b)
xnor 2014年

@isaacg:要提取O(1)中数组的前面,只需增加数组指针,使其指向第二个元素,然后释放第一个元素消耗的内存即可。
Wrzlprmft

@Wrzlprmft我无法使用数组技巧,因为无论布尔值如何,都将对数组的两个元素进行求值,当a为空列表时会引发错误。是否有一种简短的方法可以制作“惰性数组”?
xnor 2014年

2

Haskell,35个字节

a#b@(c:d)|a<[c]=b#a|0<1=c:a#d
a#_=a

Haskell,30个字节(非竞争)

此非竞争版本仅在ab具有不相交的元素时才保证线性运行时。否则它仍然可以正确运行,但可能会使用二次时间。

a#b|a<b=b#a|c:d<-b=c:a#d
a#_=a

2

PHP 91 98 91字节

编辑#1:“空” $b要求在花括号(+7)中有一个附加条件。
编辑2:未成年人打高尔夫
编辑3:新增第二版

非常简单。最好的部分是内的三元数array_shift
(如果您尝试不使用卷发,则会失败)

function m($a,$b){for($c=[];$a|$b;)$c[]=array_shift(${$a&(!$b|$a[0]<$b[0])?a:b});return$c;}

要么

function m($a,$b){for($c=[];$a|$b;)$c[]=array_shift(${$a?!$b|$a[0]<$b[0]?a:b:b});return$c;}

不打高尔夫球

function m($a,$b)
{
    $c=[];
    while($a||$b)
    {
        $c[] = array_shift(${
            $a&&(!$b||$a[0]<$b[0])
                ?a
                :b
        });
#       echo '<br>', outA($a), ' / ', outA($b) , ' -> ', outA($c);
    }
    return $c;
}

测试

$cases = array (
    [1],[0,2,3,4], [0,1,2,3,4],
    [1,5,10,17,19],[2,5,9,11,13,20], [1, 2, 5, 5, 9, 10, 11, 13, 17, 19, 20],
    [1,2,3],[], [1,2,3],
    [],[4,5,6], [4,5,6],
);
function outA($a) { return '['. implode(',',$a). ']'; }
echo '<table border=1><tr><th>A</th><th>B</th><th>expected</th><th>actual result</th></tr>';
while ($cases)
{
    $a = array_shift($cases);
    $b = array_shift($cases);
#   echo '<hr>', outA($a), ' / ', outA($b) , ' -> ', outA($c);
    $expect = array_shift($cases);
    $result=m($a,$b);
    echo '<tr><td>',outA($a),'</td><td>',outA($b),'</td><td>', outA($expect), '</td><td>', outA($result),'</td></tr>';
}
echo '</table>';

我不明白为什么你把它并不简单$a&(!$b|$a[0]<$b[0])?$a:$b,而不是${$a&(!$b|$a[0]<$b[0])?a:b}
约尔格Hülsermann

1
@JörgHülsermann该array_shift参数仅供参考。它必须是一个变量;表达式不起作用。
泰特斯

1

前进124个字符

func m(a,b[]int)(r[]int){for len(a)>0{if len(b)==0||a[0]>b[0]{a,b=b,a}else{r=append(r,a[0]);a=a[1:]}};return append(r,b...)}

1

的JavaScript的-133

function m(a,b){c=[];for(i=j=0;i<a.length&j<b.length;)c.push(a[i]<b[j]?a[i++]:b[j++]);return c.concat(a.slice(i)).concat(b.slice(j))}

与OP的方法相同。


1

perl,87个字符/ perl 5.14,78 + 1 = 79个字符

此实现掩盖了输入数组引用。除此之外,这非常简单:虽然两个数组都有东西,但要移走两者中的较低者。然后返回合并后的位,并与其他所有位合并(仅保留@ $ x或@ $ y中的一个)。Perl-up perl5,87个字符:

sub M{($x,$y,@o)=@_;push@o,$$x[0]>$$y[0]?shift@$y:shift@$x while@$x&&@$y;@o,@$x,@$y}

使用perl 5.14.0及其更新的arrayref移位:78个字符+ 1个字符的罚款= 79个字符:

sub M{($x,$y,@o)=@_;push@o,shift($$x[0]>$$y[0]?$y:$x)while@$x&&@$y;@o,@$x,@$y}

*而不是&&将节省一个字节。甚至还有更多sub M{map{shift(!@$x+@$y*($$y[0]<$$x[0])?$y:$x)}map@$_,($x,$y)=@_}
user2846289 2014年

@VadimR,哇。做得好。继续并发布它,如果您愿意-我从未想过要做双重映射技巧而不是推入数组。
skibrianski 2014年

1

爪哇:144

这很简单。一个接受两个数组并返回一个的函数,合并后的版本,经过合并,没有编译包装器:

int[]m(int[]a,int[]b){int A=a.length,B=b.length,i,j;int[]c=new int[A+B];for(i=j=0;i+j<A+B;c[i+j]=j==B||i<A&&a[i]<b[j]?a[i++]:b[j++]);return c;}

Ungolfed(具有可编译和可运行的包装器):

class M{
    public static void main(String[]args){
        int[]a=new int[args[0].split(",").length];
        int i=0;
        for(String arg:args[0].split(","))
            a[i++]=Integer.valueOf(arg);
        int[]b=new int[args[1].split(",").length];
        int j=0;
        for(String arg:args[1].split(","))
            b[j++]=Integer.valueOf(arg);
        int[]c=(new M()).m(a,b);
        for(int d:c)
            System.out.printf(" %d",d);
        System.out.println();
    }
    int[]m(int[]a,int[]b){
        int A=a.length,B=b.length,i,j;
        int[]c=new int[A+B];
        for(i=j=0;i+j<A+B;c[i+j]=j==B||i<A&&a[i]<b[j]?a[i++]:b[j++]);
        return c;
    }
}

执行示例:

$ javac M.java
$ java M 10,11,12 0,1,2,20,30
 0 1 2 10 11 12 20 30
$ java M 10,11,12,25,26 0,1,2,20,30
 0 1 2 10 11 12 20 25 26 30

任何提示,以缩短将不胜感激。


1

Scala,97个字节

O(n)的递归解。为了缩短代码,有时可以通过切换2个可互换参数来完成操作,即f(a,b)调用f(b,a)。

type L=List[Int];def f(a:L,b:L):L=if(a==Nil)b else if(a(0)<=b(0))a(0)::f(a.drop(1),b) else f(b,a)

取消高尔夫:

type L=List[Int]

def f(a:L, b:L) : L =
  if (a == Nil)
    b 
  else 
    if (a(0) <= b(0))
      a(0) :: f(a.drop(1), b) 
    else
      f(b,a)

如果a不为空,但b为空,则为例外
Dan Osipov 2014年

1

APL(32)

{⍺⍵∊⍨⊂⍬:⍺,⍵⋄g[⍋g←⊃¨⍺⍵],⊃∇/1↓¨⍺⍵}

说明:

{⍺⍵∊⍨⊂⍬                               if one or both of the arrays are empty
        :⍺,⍵                           then return the concatenation of the arrays
             ⋄g[⍋g←⊃¨⍺⍵]              otherwise return the sorted first elements of both arrays
                          ,⊃∇/        followed by the result of running the function with
                               1↓¨⍺⍵}  both arrays minus their first element

1

LISP,117个字节

该算法以n + 1迭代结束,其中n是输入中最短列表的长度。

(defun o(a b)(let((c(car a))(d(car b)))(if(null a)b(if(null b)a(if(< c d)(cons c(o(cdr a)b))(cons d(o a(cdr b))))))))


0

Python – 69个字节

def m(A,B):
    C=[]
    while A and B:C+=[[A,B][A>B].pop(0)]
    return C+A+B

如果输入和输出的顺序降序,则可以缩短为61个字节

def m(A,B):
    C=[]
    while A+B:C+=[[A,B][A<B].pop(0)]
    return C

如果允许生成器,则进一步减少到45个字节

def m(A,B):
    while A+B:yield[A,B][A<B].pop(0)

绝对不是O(n)。.pop(0)和+ =都是O(n)次操作,您执行O(n)次。
isaacg 2014年

我什至不知道到目前为止,列表还没有在Python中实现为列表,即使那样pop(0)也可以在O(1)中实现,并且+=至少可以比O(n)更好地实现(请参阅链接)。顺便说一句,您的解决方案使用+=(即appendextend)的频率与我的相同。无论如何,所有这些都是实现问题(据我所知),因此在(虚构的)Python实现中,列表作为列表实现,我的功能是O(n)。最后,您的问题要求算法为O(n),而我的为。
Wrzlprmft 2014年

实际上,追加和扩展在python中的实现与+ =是不同的。+ =创建一个新列表,而.append和.extend修改现有列表。
isaacg 2014年

0

Perl 6:53个字符

sub M(\a,\b){{shift a[0]>b[0]??b!!a}...{a^b},a[],b[]}

从较小的值ab较小的值开始转换,直到aXOR ba^b)为真。然后返回剩下的所有内容,[]将数组展平()到列表(a[],b[])中。

假设从数组的开头偏移为O(n),最坏的情况是两次比较,每个元素偏移一次,因此算法为O(n)。


0

JavaScript(ES5)90 86 90字节

function f(a,b){for(o=[];(x=a[0]<b[0]?a:b).length;)o.push(x.shift());return o.concat(a,b)}

编辑:(90-> 86)将三元数移到for循环条件中。想法从丹尼斯被盗。

编辑:(86-> 90)删除了数组到String的转换,因为它违反了O(n)的要求。


0

数学,137 135

m[a_,b_]:=(l=a;Do[Do[If[b[[f]]<=l[[s]],(l=Insert[l,b[[f]],s];Break[]),If[s==Length@l,l=l~Append~b[[f]]]],{s,Length@l}],{f,Length@b}];l)

输入:

m[{2,2,4,6,7,11},{1,2,3,3,3,3,7}]

输出:

{1, 2, 2, 2, 3, 3, 3, 3, 4, 6, 7, 7, 11}

取消高尔夫:

mergeList[a_, b_] := (
    list = a;
    Do[
        Do[(
            If[
                b[[f]] <= list[[s]],
                (list = Insert[list, b[[f]], s]; Break[]),
                If[
                    s == Length@list,
                    list = list~Append~b[[f]]
                ]
        ]),
        {s, Length@list}
    ],
    {f, Length@b}
    ];
    list
)

可能做得更好。


m[a:{x___,y_},b:{___,z_}]:=If[y<z,b~m~a,{x}~m~b~Join~{y}];{}~m~b_=b;
alephalpha 2014年

0

R,80

与Scala和其他语言相同的解决方案。我不确定x [-1]是否为O(1)。

f=function(a,b)if(length(a)){if(a[1]<=b[1])c(a[1],f(a[-1],b))else f(b,a)}else b

0

Mathematica,104个字节

Reap[{m,n}=Length/@{##};i=k=1;Do[If[k>n||TrueQ[#[[i]]<#2[[k]]],Sow@#[[i++]],Sow@#2[[k++]]],n+m]][[2,1]]&

匿名函数,存储在变量两个输入列表的长度mn,则每次迭代Do循环Sow■一个列表递增该列表中的计数器(的元素i为第一,k第二个)由一个。如果其中一个计数器超过列表的长度,则该If语句将始终Sow是另一个列表中的元素。之后n+m的操作中,所有的元素都被照顾。Reap或更确切地说[[2,1]],它的输出的一部分是按元素顺序排列的元素列表Sow

我不确定内部结构(是否在访问列表的一部分进行O(1)操作),但是相对于输入列表的长度,我的机器上的时序看起来很线性。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.