你好,我想告诉我这两种mergesort算法的空间复杂度是否相同。
算法1:
def mergeSort(alist, l, r):
if r - l >= 1:
mid = l + (r - l)//2
mergeSort(alist, l, mid)
mergeSort(alist, mid+1, r)
i = l
j = mid+1
k = 0
temp_list = [None]*(r-l+1)
while i < mid+1 and j < r+1:
if alist[i] <= alist[j]:
temp_list[k] = alist[i]
i=i+1
else:
temp_list[k] = alist[j]
j=j+1
k=k+1
while i < mid+1:
temp_list[k] = alist[i]
i=i+1
k=k+1
while j < r+1:
temp_list[k] = alist[j]
j=j+1
k=k+1
n = 0
for index in range(l,r+1):
alist[index] = temp_list[n]
n += 1
算法2:
def mergeSort2(alist):
if len(alist)>1:
mid = len(alist)//2
lefthalf = alist[:mid]
righthalf = alist[mid:]
mergeSort2(lefthalf)
mergeSort2(righthalf)
i=0
j=0
k=0
while i < len(lefthalf) and j < len(righthalf):
if lefthalf[i] <= righthalf[j]:
alist[k]=lefthalf[i]
i=i+1
else:
alist[k]=righthalf[j]
j=j+1
k=k+1
while i < len(lefthalf):
alist[k]=lefthalf[i]
i=i+1
k=k+1
while j < len(righthalf):
alist[k]=righthalf[j]
j=j+1
k=k+1
直观上来说,Algo2的空间复杂度更高,因为复制的列表lefthalf
和righthalf
在调用mergeSort2
的情况下被推入堆栈。
Algo1直到合并时间temp_list = [None]*(r-l+1)
才分配额外的空间,因此执行堆栈仅具有当前执行的mergeSort
的额外数组。
这是正确的吗?
答案 0 :(得分:1)
首先,让我们假设我们有完美的垃圾回收器,并且每个列表在用完后都会立即释放。
基于此假设,算法具有相同的大O空间复杂度。
首先看一下算法2并考虑以下示例: 想象一下,您正在对长度为16的列表进行排序。
[15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0]
您计算列表的前一半和后一半:
[15,14,13,12,11,10,9,8] [7,6,5,4,3,2,1,0]
然后您对前半部分进行排序,特别是将其分为两个新的子列表:
[15,14,13,12] [11,10,9,8]
然后您再次执行相同操作:
[15,14] [13,12]
再次:
[15] [14]
然后,您才开始合并列表。
此时该算法分配的列表的总长度是多少?
它是16 + 2*8 + 2*4 + 2*2 + 2*1
。通常,它是N + 2N/2 + 2N/4 + 2N/8 + ... + 2
。这是一个简单的几何级数,总计约为3 * N。
该算法还需要O(log(N))空间用于调用堆栈,但是以大的O表示法消失了: O(N)
很容易看出这是算法在任何给定点将使用的最大值-将来将要使用的已分配列表的长度(因此不能被释放)不会超过3 * N。
再次考虑相同的示例。我们将对以下列表进行排序。
[15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0]
想象一下,我们已经对其前半部分和后半部分进行了排序:
[8,9,10,11,12,13,14,15, 0,1,2,3,4,5,6,7]
现在,我们需要分配一个长度为N的临时列表来执行合并。 因此,在那一刻,我们积极使用两个长度为N的列表,这使我们得到2 * N = O(N)。
同样,很容易看出我们永远不会使用更多的内存:对列表两半进行排序的任务自然不会比对列表本身进行排序花费更多。
这两种算法都使用 O(N)内存。他们将O(log(N))用于调用堆栈,但是与O(N)相比,这是一笔不小的支出。
另外知道Python uses reference counting可以取消分配未使用的对象,这验证了我们对垃圾回收的最初假设。