合并排序无法在N logN中工作
Merge sort failing to work in N logN
我写了一个合并排序算法,但它在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毫秒。同一个样本按std::sort排序,时间为0.668毫秒,我知道排序的复杂度为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;
假设您的实现是正确的,两个O(N logN)不一定会在相同的时间内运行。无症状复杂性是衡量运行程序所需的资源随着输入量的增加而增加的程度。举个例子,下面的循环都是O(1),因为每个循环总是运行恒定数量的步骤:
for (i = 0; i < 10; i++) {
printf("%dn", i);
}
for (i = 0; i < 1000000000; i++) {
printf("%dn", i);
}
但毫无疑问,第二场比赛需要更长的时间。事实上,这两个循环之间的运行时差距将明显大于您观察到的排序算法与std::sort
之间的差距。这是因为渐近分析忽略了常数。
此外,无症状复杂性通常适用于平均或最坏情况。对于大小相等的输入,相同的算法可以在或多或少的时间内运行,这取决于数据。
更不用说std::sort
很可能不是单个排序算法。根据阵列的大小,它可能使用不同的策略。事实上,std::sort
的一些实现使用混合算法。
分析程序复杂性的正确方法是阅读代码。对于数值方法,最接近的方法是运行您的程序,而不将其与其他程序进行比较,以获得不同大小的几个输入。画一张图,观察曲线。
在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
}
}
}
- QSqlquery prepare()和bindvalue()不工作
- 芬威克树(BIT).找到具有给定累积频率的最小索引,单位为 O(logN)
- 导入库可以跨dll版本工作吗
- 以螺旋方式打印矩阵的程序.(工作不好)
- 对象指针在c++中是如何工作的
- 为什么在Windows上的VS 2019和Clang 9中"size_t"在没有标题的情况下工作
- VSOMEIP-2个设备之间的通信(TCP/UDP)不工作
- 为字符串中每 N 个字符插入空格的函数没有按照我认为的方式工作?
- C++为线程工作动态地分割例程
- 为什么我的 std::ref 无法按预期工作?
- 布尔比较运算符是如何在C++中工作的
- SampleConsensusPrerejective(ext.RANSAC)是如何真正工作的
- 不确定要在我的main中放入什么才能使我的代码正常工作
- 为什么std::condition_variable notify_all的工作速度比notify_one快(对于随机请
- <<操作员在下面的行中工作
- 有人能解释一下为什么下界是这样工作的吗C++的
- ExtractIconEx:可以工作,但偶尔会崩溃
- C++中的memset函数工作不正常
- 当我在第一个循环中使用"auto"时,它工作正常,但是使用"int"它会给出错误,为什么?
- 合并排序无法在N logN中工作