让我们以Merge Sort的实现为例
void mergesort(Item a[], int l, int r) {
if (r <= l) return;
int m = (r+l)/2;
mergesort(a, l, m); ------------ (1)
mergesort(a, m+1, r); ------------(2)
merge(a, l, m, r);
a)此合并排序的时间复杂度为O(nlg(n))。并行化(1)和(2)会带来任何实际收益吗?从理论上讲,似乎在并行化后,你最终会得到O(nlg(n)。但实际上我们可以获得任何收益吗?
b)此合并排序的空间复杂度为O(n)。但是,如果我选择使用链接列表执行就地合并排序(不确定是否可以合理地使用数组),空间复杂度将变为O(lg(n)),因为您必须考虑递归堆栈帧大小? 我们可以将O(lg(n))视为常数,因为它不能超过64吗?我可能在几个地方误解了这一点。 64的意义究竟是什么?
c)http://www.cprogramming.com/tutorial/computersciencetheory/sortcomp.html表示合并排序需要使用链接列表的常量空间。怎么样 ?他们是否对待O(lg(n)常数?
d)[添加以获得更多清晰度]对于空间复杂度计算,假设输入数组或列表已经在内存中是公平的吗?当我进行复杂度计算时,我总是计算除了已经输入的空间之外我将需要的“额外”空间。否则空间复杂性将始终为O(n)或更差。
答案 0 :(得分:52)
MergeSort time Complexity是O(nlgn),这是一项基础知识。 合并排序空间复杂度将始终为O(n),包括数组。 如果你绘制空间树,似乎空间复杂度为O(nlgn)。但是,由于代码是深度优先代码,因此您将始终只沿树的一个分支进行扩展,因此,所需的总空间使用量将始终受O(3n)= O(n)的限制。
例如,如果你绘制空间树,它似乎是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)=&gt;空间复杂度为O(nlogn + n)= O(nlogn)。 但是,实际代码中并非如此,因为它不是并行执行的。例如,在N = 16的情况下,这是mergesort的代码执行的方式。 N = 16.
16
/
8
/
4
/
2
/ \
1 1
注意使用的空间数是多少32 = 2n = 2 * 16&lt; 3n个
然后它向上合并
16
/
8
/
4
/ \
2 2
/ \
1 1
是34 < 3N。 然后它向上合并
16
/
8
/ \
4 4
/
2
/ \
1 1
36&lt; 16 * 3 = 48
然后它向上合并
16
/ \
8 8
/ \
4 4
/ \
2 2
/\
1 1
16 + 16 + 14 = 46&lt; 3 * n = 48
在较大的情况下,n = 64
64
/ \
32 32
/ \
16 16
/ \
8 8
/ \
4 4
/ \
2 2
/\
1 1
是64 * 3 <= 3 * n = 3 * 64
您可以通过归纳证明这一点。
因此,空间复杂度始终受O(3n)= O(n)的限制,即使您使用数组实现,只要在合并后清理已用空间并且不是并行执行代码而是顺序执行。
我的实施示例如下:
templace<class X>
void mergesort(X a[], int n) // X is a type using templates
{
if (n==1)
{
return;
}
int q, p;
q = n/2;
p = n/2;
//if(n % 2 == 1) p++; // increment by 1
if(n & 0x1) p++; // increment by 1
// note: doing and operator is much faster in hardware than calculating the mod (%)
X b[q];
int i = 0;
for (i = 0; i < q; i++)
{
b[i] = a[i];
}
mergesort(b, i);
// do mergesort here to save space
// http://stackoverflow.com/questions/10342890/merge-sort-time-and-space-complexity/28641693#28641693
// After returning from previous mergesort only do you create the next array.
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;
}
希望这有帮助!=)
很快,Chee Loong,多伦多大学
答案 1 :(得分:16)
a)是的 - 在一个完美的世界中,你必须进行大小为n,n / 2,n / 4的log n合并...(或者更好地表示1,2,3 ... n / 4, n / 2,n - 它们不能并行化),得到O(n)。它仍然是O(n log n)。在不那么完美的世界中,你没有无限数量的处理器和上下文切换和同步抵消任何潜在的收益。
b)空间复杂度始终为Ω(n),因为您必须将元素存储在某处。在使用数组和链表实现中的O(1)的实现中,额外的空间复杂度可以是O(n)。在实践中,使用列表的实现需要额外的空间用于列表指针,所以除非你已经在内存中有列表,否则它无关紧要。
修改强> 如果你计算堆栈帧,那么它是O(n)+ O(log n),所以在数组的情况下仍然是O(n)。如果是列表,则为O(log n)附加内存。
c)列表只需要在合并过程中更改一些指针。这需要不断增加内存。
d)这就是为什么在合并 - 排序复杂性分析中,人们会提到“额外的空间需求”或类似的东西。很明显,你必须将元素存储在某个地方,但最好提一下“额外的记忆”来保持纯粹主义者的存在。
答案 2 :(得分:1)
a)是的,当然,并行化合并排序可能非常有益。它仍然是nlogn,但你的常数应该显着降低。
b)链表的空间复杂度应为O(n),或更具体地为O(n)+ O(logn)。请注意,这是一个+,而不是*。在进行渐近分析时,不要过多关注常数。
c)在渐近分析中,只有等式中的主导项很重要,所以我们有一个+而不是一个*的事实使它成为O(n)。如果我们复制全部的子列表,我相信这将是O(nlogn)空间 - 但是基于智能链接列表的合并排序可以共享列表的区域。答案 3 :(得分:1)
合并排序的最坏情况表现: O(n log n), 合并排序的最佳案例表现: O(n log n)典型,O(n)自然变体, 合并排序的平均性能: O(n log n), 合并排序的最坏情况空间复杂度:О(n)总计,O(n)辅助
答案 4 :(得分:1)
简单而明智的思维。
总水平(L)= log2(N)。 在最后一级,节点数= N。
第1步:让我们假设节点(i)的所有级别为x(i)。
第2步:,因此时间复杂度= x1 + x2 + x3 + x4 + .... + x(L-1)+ N(对于i = L);
第3步:实际上,我们知道x1,x2,x3,x4 ...,x(L-1) 第4步:,所以让我们考虑x1 = x2 = x3 = ... = x(L-1)= N 第5步:所以时间复杂度=(N + N + N + ..(L)次) 时间复杂度= O(N * L);
放L = log(N); 时间复杂度= O(N * log(N)) 我们在合并时使用额外的数组, 空间复杂度:O(N)。 提示:大O(x)时间意味着,x是我们可以肯定地说出的最小时间,并证明它在平均情况下永远不会超过x
答案 5 :(得分:0)
空间复杂度:如果在每个级别创建子阵列/子列表,则为其nlogn(logn级别* n每个级别所需的空间=&gt; logn * n)。 如果没有,并且考虑堆栈空间,它将为LinkedList登录,而n(n + logn = n)则为Array。 时间复杂度:最坏和平均情况下的nlogn
答案 6 :(得分:0)
合并排序空间的复杂度为O(nlogn)
,考虑到它最多可以进行O(logn)
个递归,并且每次递归都有O(n)
的额外空间用于存储需要重新分配的合并数组。
对于那些说O(n)
的人,请不要忘记它是O(n)
的到达堆栈帧深度。
答案 7 :(得分:0)
对于最佳和最坏情况,复杂度均为O(nlog(n))。 尽管在每个步骤中都需要额外的N个数组大小,所以 空间复杂度为O(n + n)为O(2n),因为我们删除了用于计算复杂度的常数,所以它为O(n)