为什么使用链表时,归并排序的空间复杂度为O(log(n))?

27

在数组上执行归并排序的空间复杂度为O(n),而在链表上执行归并排序的空间复杂度为O(log(n)),详见这里

我认为我理解了数组的情况,因为在合并两个子数组时需要辅助存储空间。但是链表归并排序不是只需在原地合并两个子链表吗?我认为这将具有O(1)的空间复杂度来创建一个新头。

原地合并(无需辅助存储):

public Node merge(Node a, Node b) {
    Node dummyHead, curr; dummyHead = new Node(); curr = dummyHead;
    while(a !=null && b!= null) {
        if(a.info <= b.info) { curr.next = a; a = a.next; }
        else { curr.next = b; b = b.next; }
        curr = curr.next;
    }
    curr.next = (a == null) ? b : a;
    return dummyHead.next;
}

能够给出一个解释会很好。


O(n)?这一定是新东西。我知道最好的平均排序复杂度是O(nlogn)。 - NiVeR
3
这个问题涉及到空间复杂度,而不是时间复杂度。 - Codor
哦,那我道歉了。是我的错误。 - NiVeR
2
请注意,这是特指递归归并排序。您可以编写一个空间复杂度为O(1)的迭代归并排序。 - Jim Mischel
2个回答

30

归并排序算法是递归的,所以对于数组和链表情况都需要O(log n)的堆栈空间。但是数组情况还需要额外分配O(n)的空间,这比需要的堆栈空间O(log n)更占用空间。因此数组版本的复杂度是O(n),而链表版本的复杂度为O(log n)。


为什么O(n)比O(logn)更占主导地位?我认为使用数组的归并排序将具有n + log(n)的复杂度,其中n表示子数组,log(n)表示调用栈。 - nhoxbypass
5
@nhoxbypass说的“O(n + log n)仍然是O(n)”是因为我们忽略了低阶项。 - David G

23

Mergesort(归并排序)是一种递归算法。每一次递归都会在栈上添加一个新的帧。如果要对64个项目进行排序,需要比32个项目多进行一次递归步骤,实际上空间需求为O(log(n))时所引用的是栈的大小。


啊,是的,那很有道理! - modulitos
2
或者,您可以使用迭代版本,它只需要恒定数量的整数和指针,但您需要O(log n)位来表示整数或指针。 - tmyklebu
@j_random_hacker:跨步访问没有问题;在每次长度为k的查找之后,您可以访问2k个连续元素。我的观点是,根据您对计算机的假设,有两种非常不同的方法可以得出O(log n)空间限制。 - tmyklebu
@tmyklebu:我理解你的第一句话,但这只让我相信链表可以在O(n log n)时间和O(1)额外空间内进行归并排序,因此OP链接页面上的声明是错误的!你的第二句话(以及你最初的评论)仍然至少非常误导人,因为它暗示迭代和递归形式的链表归并排序的空间使用等价,当我们比较同类项时,前者明显更好。 - j_random_hacker
那么使用数组的归并排序的复杂度将为 n + log(n)?其中 n 代表子数组,log(n) 代表调用栈? - nhoxbypass
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接