合并排序时间和空间复杂性

时间:2012-04-26 23:37:39

标签: algorithm time-complexity space-complexity

让我们以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)或更差。

8 个答案:

答案 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)