MergeSort的时间复杂度是O(nlgn),这是一项基本知识。
Merge Sort的空间复杂度始终为O(n),包括数组在内。
如果你将空间树画出来,看起来空间复杂度是O(nlgn)。然而,由于代码是深度优先的,你总是只会沿着树的一个分支扩展,因此,所需的总空间使用量始终受到O(3n) = O(n)的限制。
2023年10月24日更新:有一个关于我如何得出3n上界的问题。
我在评论中解释了一下,现在重新粘贴在这里。数学上证明3n的方法与为未排序数组构建堆的时间复杂度为2n次交换的原因非常相似,这需要O(2n) = O(n)的时间。在这种情况下,总是只有一个额外的分支。因此,可以将其视为为1个额外的分支再次构建堆。因此,它将受到另外n的限制,总的上界为3n,即O(3n) = O(n)。请注意,在这种情况下,我们使用了构建堆(inputArray)时间复杂度的类似数学方法来证明单线程mergeSort的空间复杂度,而不是时间复杂度。我有时间时可以提供一个完整而严谨的数学证明。
例如,如果你将空间树画出来,似乎是O(nlgn)的复杂度。
16 | 16
/ \
/ \
/ \
/ \
8 8 | 16
/ \ / \
/ \ / \
4 4 4 4 | 16
/ \ / \ / \ / \
2 2 2 2..................... | 16
/ \ /\ ........................
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 | 16
当树的高度为O(logn)时,空间复杂度为O(nlogn + n) = O(nlogn)。
然而,在实际代码中并非如此,因为它不是并行执行的。例如,在N = 16的情况下,这是mergesort代码的执行方式。N = 16。
16
/
8
/
4
/
2
/ \
1 1
注意到使用的空间数量为32 = 2n = 2*16 < 3n。
然后它向上合并。
16
/
8
/
4
/ \
2 2
/ \
1 1
这是34 < 3n。
然后它向上合并
16
/
8
/ \
4 4
/
2
/ \
1 1
36 < 16 * 3 = 48
然后它向上合并
16
/ \
8 8
/ \
4 4
/ \
2 2
/\
1 1
16 + 16 + 14 = 46 < 3*n = 48
in a larger case, n = 64
64
/ \
32 32
/ \
16 16
/ \
8 8
/ \
4 4
/ \
2 2
/\
1 1
这是一个例子,其中 643 <= 3n = 3*64。
你可以通过归纳法来证明这个一般情况。
因此,即使你使用数组实现,并且在合并后清理已使用的空间,并且不并行执行代码而是按顺序执行,空间复杂度始终受到 O(3n) = O(n) 的限制。
下面是我的实现示例:
templace<class X>
void mergesort(X a[], int n)
{
if (n==1)
{
return;
}
int q, p;
q = n/2;
p = n/2;
if(n & 0x1) p++;
X b[q];
int i = 0;
for (i = 0; i < q; i++)
{
b[i] = a[i];
}
mergesort(b, i);
X c[p];
int k = 0;
for (int j = q; j < n; j++)
{
c[k] = a[j];
k++;
}
mergesort(c, k);
int r, s, t;
t = 0; r = 0; s = 0;
while( (r!= q) && (s != p))
{
if (b[r] <= c[s])
{
a[t] = b[r];
r++;
}
else
{
a[t] = c[s];
s++;
}
t++;
}
if (r==q)
{
while(s!=p)
{
a[t] = c[s];
s++;
t++;
}
}
else
{
while(r != q)
{
a[t] = b[r];
r++;
t++;
}
}
return;
}