为啥我的合并排序不像 O (n * lg n))?

Posted

技术标签:

【中文标题】为啥我的合并排序不像 O (n * lg n))?【英文标题】:Why is my merge sort not behaving like a O (n * lg n))?为什么我的合并排序不像 O (n * lg n))? 【发布时间】:2014-04-19 04:22:22 【问题描述】:

我根据Cormen's Book 上的伪代码实现了这种合并排序。我正在复制它,因为它很短:

void merge(vector<double> &array, int start, int mid, int end) 
    int i = start;
    int j = mid + 1;
    int k = start;
    vector<double> b(array.size());

    while (i <= mid && j <= end) 
        if (array[i] <= array[j])
            b[k++] = array[i++];
        else
            b[k++] = array[j++];
    

    while(i <= mid)
        b[k++] = array[i++];

    while(j <= end)
        b[k++] = array[j++];

    for (k = start; k <= end; k++)
        array[k] = b[k];

这部分应该是O(n)

另一个应该是 O(n*lg n) ,其中 lg 是 2 基数

void mergeSort(vector<double> &array, int start, int end) 
    if (start < end) 
        int mid = (end - start) / 2 + start;
        mergeSort(array, start, mid);
        mergeSort(array, mid + 1, end);
        merge(array, start, mid, end);
    

我对大小的随机向量实例进行了一些实验:1000(10^3)​​、10000(10^4)、50000(5*10^4)、100000(10^5)、250000(2.5*10^ 5), 500000(5*10^5)。每个大小有 30 个实例。 这是我对每个实例大小的平均结果:

1000 - ~0.000 s
10000 - 0.344 s
50000 - 20.456 s
100000 - 59.083 s
250000 - 360.814 s
500000 - 1729.245 s

运行合并排序时,我从 linux time 命令(占用用户时间)中获取的所有经过时间。 可见,这不是 O(n*lg n) 行为。我在这里缺少什么? 我不知道这是否相关,但我的系统配置是:

OS: Fedora 18 - 64 bits
Processor: Intel® Core™ i3 CPU M 380 @ 2.53GHz × 4
Memory: 2.8 Gi

B

【问题讨论】:

您运行的是调试代码还是优化代码?差异可能很大,因为您的某些循环可以并行化。从理论上讲,算法merge 在比较方面是O(n):但你做的远不止这些。你做了大量的访问、递增、赋值,甚至分配了一个新的向量。所有这些都会影响性能(更具体地说,缓存不佳的内存和冷内存会损害性能)。 尝试将vector&lt;double&gt; b(array.size()); 更改为vector&lt;double&gt; b(end-start);,并使用k=0。你的 b 比它应该的大得多,不确定这是问题所在。 这可能是你的问题,vector 填充构造具有线性时间。 【参考方案1】:

这是罪魁祸首:

vector<double> b(array.size());

假设您从一百万个条目的向量开始。对 mergeSort 的初始调用将在包含 50 万个条目的向量上调用 mergeSort,但只对其前 250,000 个元素进行排序。 (然后它将在下半场重复。)对mergeSort 的下一次调用将接收完整的 500,000 个元素数组并调用mergeSort 对数组的第一个和第二个 125,000 个元素进行排序。等等。在此过程中,mergeSort 每次都会收到包含 50 万个条目的向量,但只会对一个子集进行排序。最终,您将调用merge,其中将在每次调用时分配并初始化一个包含 50 万个元素的临时数组。

结果是 n2*log(n) 行为。这不是指数行为,但仍然不好。

我看到了三种不同的解决方案:

分配该临时b 一次并将其作为参数传递给mergeSortmerge。 在merge 中分配一个大小为end-start+1 的临时数组。现在您必须使用偏移量来处理 b[0] 对应于 array[start] 的事实。 就地合并。你不需要在这里临时。但是,这是不平凡的,将使算法成为 O(N*(log(N))^2) 算法。

【讨论】:

只是指出就地合并是不平凡的(尽管可能)并使算法变慢。【参考方案2】:

relocation of the vectors 似乎占用了很多时间。添加到向量不是 O(1) 操作。尝试将向量更改为基本的 C 类型数组,您会注意到不同之处。此外,我从这些值中看到,它绝不是指数级的。可能是更高的多项式。

【讨论】:

以上是关于为啥我的合并排序不像 O (n * lg n))?的主要内容,如果未能解决你的问题,请参考以下文章

java 给出了很多整数范围;合并重叠范围。通过对范围进行排序来确定运行时间,因此时间复杂度为n lg(n)。

时间复杂度

为啥以下算法(循环排序?!)的时间复杂度是 O(n)?

为啥冒泡排序是 O(n^2)?

为啥要对字符串进行排序 O(n log n)? [复制]

为啥计数排序的时间和空间复杂度是 O(n + k) 而不是 O(max(n, k))?