我编写了一个合并排序算法,但无法在N log N中排序。 排序数组列表的代码是:
void merge(int start, int mid, int end) {
int i,j,q;
for (i=start; i<=end; i++)
l[i]=list[i];
i=start; j=mid+1; q=start;
while (i<=mid and j<=end) {
if (l[i]<l[j])
list[q++]=l[i++];
else
list[q++]=l[j++];
}
while (i<=mid)
list[q++]=l[i++];
}
void mergesort(int start, int end) {
int mid;
if (start<end) {
mid=(start+end)/2;
mergesort(start, mid);
mergesort(mid+1, end);
merge(start, mid, end);
}
}
但是,如果我例如排序7800数字,则运行时间约为1.243毫秒。相同的样本在0.668 ms内按std :: sort排序,我知道sort的复杂度为N logN。我的代码出了什么问题?我似乎无法浪费时间。
时间测量:
#include <time.h>
clock_t start = clock();
//SORTING ALGORITHM HERE//
clock_t stop = clock();
double elapsed =(stop - start) * 1000.0 / CLOCKS_PER_SEC;
答案 0 :(得分:4)
假设您的实现是正确的,两个O(N logN)不一定会在相同的时间内运行。渐近复杂性衡量运行程序所需的资源随着输入变得非常大而增长的程度。举一个例子,以下循环都是O(1),因为它们中的每一个总是运行一定数量的步骤:
for (i = 0; i < 10; i++) {
printf("%d\n", i);
}
for (i = 0; i < 1000000000; i++) {
printf("%d\n", i);
}
但毫无疑问,第二个需要更长的时间才能运行。事实上,这两个循环之间的运行时间差异将明显大于排序算法与std::sort
所观察到的差距。那是因为渐近分析忽略了常数。
此外,渐近复杂度通常用于平均或最差情况。对于相同大小的输入,相同的算法可以在或多或少的时间内运行,具体取决于数据。
更不用说std::sort
很可能不是单一的排序算法。它可能根据数组的大小使用不同的策略。事实上,std::sort
use a mixture of algorithms。
分析程序复杂性的正确方法是阅读代码。对于数值方法,您可以做的最接近的是运行您的程序,而不将其与其他程序进行比较,以获得不同大小的多个输入。绘制图表并观察曲线。
答案 1 :(得分:0)
在Visual Studio的情况下,std :: sort()是快速排序,堆排序(仅防止最坏情况O(n ^ 2)时间复杂度)和插入排序的混合,而std :: stable_sort (),是合并排序和插入排序的混合。两者都相当快,但可以编写更快的代码。问题中的示例代码是在每次合并之前复制数据,这会消耗时间。这可以通过一次性分配工作缓冲区,并根据递归级别切换合并方向,使用一对相互递归函数(如下所示)或布尔参数来控制合并方向(未在示例中使用)来避免下文)。
自上而下合并排序的示例C ++代码,合理优化(自下而上合并排序会略快,因为它会跳过用于生成索引的递归,而不是使用迭代)。
// prototypes
void TopDownSplitMergeAtoA(int a[], int b[], size_t ll, size_t ee);
void TopDownSplitMergeAtoB(int a[], int b[], size_t ll, size_t ee);
void TopDownMerge(int a[], int b[], size_t ll, size_t rr, size_t ee);
void MergeSort(int a[], size_t n) // entry function
{
if(n < 2) // if size < 2 return
return;
int *b = new int[n];
TopDownSplitMergeAtoA(a, b, 0, n);
delete[] b;
}
void TopDownSplitMergeAtoA(int a[], int b[], size_t ll, size_t ee)
{
if((ee - ll) == 1) // if size == 1 return
return;
size_t rr = (ll + ee)>>1; // midpoint, start of right half
TopDownSplitMergeAtoB(a, b, ll, rr);
TopDownSplitMergeAtoB(a, b, rr, ee);
TopDownMerge(b, a, ll, rr, ee); // merge b to a
}
void TopDownSplitMergeAtoB(int a[], int b[], size_t ll, size_t ee)
{
if((ee - ll) == 1){ // if size == 1 copy a to b
b[ll] = a[ll];
return;
}
size_t rr = (ll + ee)>>1; // midpoint, start of right half
TopDownSplitMergeAtoA(a, b, ll, rr);
TopDownSplitMergeAtoA(a, b, rr, ee);
TopDownMerge(a, b, ll, rr, ee); // merge a to b
}
void TopDownMerge(int a[], int b[], size_t ll, size_t rr, size_t ee)
{
size_t o = ll; // b[] index
size_t l = ll; // a[] left index
size_t r = rr; // a[] right index
while(1){ // merge data
if(a[l] <= a[r]){ // if a[l] <= a[r]
b[o++] = a[l++]; // copy a[l]
if(l < rr) // if not end of left run
continue; // continue (back to while)
while(r < ee) // else copy rest of right run
b[o++] = a[r++];
break; // and return
} else { // else a[l] > a[r]
b[o++] = a[r++]; // copy a[r]
if(r < ee) // if not end of right run
continue; // continue (back to while)
while(l < rr) // else copy rest of left run
b[o++] = a[l++];
break; // and return
}
}
}