42

我们以这种合并排序的实现为例

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(n lg(n))。并行化(1)和(2)会带来任何实际收益吗?从理论上讲,似乎在将它们并行化之后,您最终也会进入O(n lg(n)). 但实际上我们能获得任何收益吗?

b) 这里合并排序的空间复杂度是O(n)。但是,如果我选择使用链表执行就地合并排序(不确定是否可以合理地使用数组完成),空间复杂度是否会变为O(lg(n)),因为您必须考虑递归堆栈帧的大小?我们可以将O(lg(n))其视为常数,因为它不能超过 64 吗?我可能在几个地方误解了这一点。64到底有什么意义?

c)比较排序算法 - Cprogramming.com说合并排序需要使用链表的恒定空间。如何?他们对待O(lg(n))恒定吗?

d)添加以获得更清晰。对于空间复杂度计算,假设输入数组或列表已经在内存中是否公平?当我进行复杂性计算时,我总是计算除了输入已经占用的空间之外我需要的“额外”空间。否则空间复杂度将永远是O(n)或者更糟。

4

7 回答 7

104

MergeSort 时间复杂度是 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) => 空间复杂度是 O(nlogn + n) = O(nlogn)。但是,实际代码中并非如此,因为它不是并行执行的。例如,在 N = 16 的情况下,这就是合并排序代码的执行方式。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

在更大的情况下,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;
}

希望这会有所帮助!=)

顺志龙,

多伦多大学

于 2015-02-21T03:17:05.773 回答
33

a)是的-在一个完美的世界中,您必须进行 log n 大小为 n、n/2、n/4 的合并 ...(或者更好地说是 1、2、3 ... n/4、n/2 , n - 它们不能并行化),这给出了 O(n)。它仍然是 O(n log n)。在不那么完美的世界中,您没有无限数量的处理器,上下文切换和同步抵消了任何潜在的收益。

b) 空间复杂度始终为 Ω(n),因为您必须将元素存储在某处。额外的空间复杂度在使用数组的实现中可能是 O(n),在链表实现中可能是 O(1)。在实践中,使用列表的实现需要额外的空间用于列表指针,因此除非您已经在内存中拥有该列表,否则这无关紧要。

编辑 如果你计算堆栈帧,那么它是 O(n)+ O(log n) ,所以在数组的情况下仍然是 O(n) 。在列表的情况下,它是 O(log n) 额外的内存。

c) 列表只需要在合并过程中改变一些指针。这需要不断增加的内存。

d)这就是为什么在合并排序复杂性分析中人们提到“额外的空间要求”或类似的东西。很明显,您必须将元素存储在某个地方,但最好提及“附加内存”以阻止纯粹主义者。

于 2012-04-27T00:06:52.297 回答
1

a) 是的,当然,并行化归并排序是非常有益的。它仍然是 nlogn,但您的常数应该会显着降低。

b) 链表的空间复杂度应该是 O(n),或者更具体地说是 O(n) + O(logn)。请注意,这是一个 +,而不是 *。在进行渐近分析时,不要过多关注常数。

c) 在渐近分析中,只有方程中的主导项很重要,所以我们有一个 + 而不是一个 * 的事实使它成为 O(n)。如果我们全部复制子列表,我相信这将是 O(nlogn) 空间 - 但是基于智能链表的合并排序可以共享列表的区域。

于 2012-04-27T00:01:02.397 回答
1

合并排序的最坏情况性能: O(n log n),合并排序的最佳情况性能:O(n log n) 通常,O(n) 自然变体,合并排序的平均性能:O(n log n) , 归并排序的最坏情况空间复杂度:О(n) 总,O(n) 辅助

于 2017-07-19T06:53:16.883 回答
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) < N

第 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 的最小时间

于 2020-10-13T04:22:03.267 回答
0

对于最好和最坏的情况,复杂度都是 O(nlog(n)) 。虽然每一步都需要额外的 N 大小的数组,所以空间复杂度是 O(n+n) 是 O(2n),因为我们删除了计算复杂度的常数值,所以它是 O(n)

于 2019-08-21T13:34:08.853 回答
-1

合并排序空间复杂度是O(nlogn),考虑到它最多可以进行O(logn)递归并且对于每个递归都有额外的空间O(n)来存储需要重新分配的合并数组,这是非常明显的。对于那些说O(n)请不要忘记它是O(n)为了到达堆栈帧深度的人。

于 2019-07-18T08:17:05.917 回答