我还能在哪里改进此合并排序
Where else can I improve this Merge Sort
virtual Indexed<T>& sort(Indexed<T>& data)
{
//base case
Vector<T> temp1, temp2, temp3;
//for (int i = 0; i < data.getSize(); i++)
//{
// cout << data.getElementAt(i) << ", ";
//}
mergeSortHelper(data, 0, data.getSize() - 1, temp1, temp2);
for (int i = 0; i < data.getSize(); i++)
{
cout << data.getElementAt(i) << ", ";
}
cout << endl;
return data;
}
void mergeSortHelper(Indexed<T>& data, int begin, int end, Vector<T> &left, Vector<T> &right)
{
//base case, if there is only 1 item in each side, merge them and return
if ((begin - end) == 0)
{
//left.setElementAt(data.getElementAt(begin), 0);
//left = storage;
return;
}
else
{
//Vector<T> right;
int midPoint = (end + begin) / 2;
mergeSortHelper(data, begin, midPoint, left, right);
mergeSortHelper(data, midPoint + 1, end, left, right);
//perform merging
int i = 0, leftCounter = 0, leftLimit = (midPoint - begin) + 1, rightCounter = 0, rightLimit = (end - (midPoint + 1)) + 1;
for (int i = 0; i < leftLimit; i++)
{
if (i < leftLimit)
left.setElementAt(data.getElementAt(begin + i), i);
if (i < rightLimit)
right.setElementAt(data.getElementAt(midPoint + 1 + i), i);
//cout << left.getElementAt(i) << ", ";
}
while (leftCounter < leftLimit || rightCounter < rightLimit)
{
if (leftCounter >= leftLimit)
{
//done sorting the left side
data.setElementAt(right.getElementAt(rightCounter), begin + i);
//temp.setElementAt(right.getElementAt(rightCounter), i);
rightCounter++;
}
else if (rightCounter >= rightLimit)
{
//done sorting the right side
data.setElementAt(left.getElementAt(leftCounter), begin + i);
//temp.setElementAt(left.getElementAt(leftCounter), i);
leftCounter++;
}
else
{
if (left.getElementAt(leftCounter) < right.getElementAt(rightCounter))
{
data.setElementAt(left.getElementAt(leftCounter), begin + i);
//temp.setElementAt(left.getElementAt(leftCounter), i);
leftCounter++;
}
else if (left.getElementAt(leftCounter) > right.getElementAt(rightCounter))
{
data.setElementAt(right.getElementAt(rightCounter), begin + i);
//temp.setElementAt(right.getElementAt(rightCounter), i);
rightCounter++;
}
else
{
data.setElementAt(right.getElementAt(rightCounter), begin + i);
//temp.setElementAt(right.getElementAt(rightCounter), i);
i++;
data.setElementAt(left.getElementAt(leftCounter), begin + i);
//temp.setElementAt(left.getElementAt(leftCounter), i);
leftCounter++;
rightCounter++;
}
}
i++;
}
}
//return temp;
我还能在哪些地方改进以加快排序速度?我一直在花很多时间在这上面,只是让它正常工作。但是正如我从类中回忆的那样,快速排序和合并排序是 O(NlogN(,但我上面的 quickSort 代码效率不高,对相同数量的项目进行排序需要 2.5 倍的时间。
你的代码过于复杂。合并排序有两个部分:分割和合并。每个都应该是一个函数。
您还应该有一个顶级函数(您的用户将使用的公共接口(。
为了获得最大速度,您应该在顶级函数中分配一次所需的所有额外内存,并将其向下传递以供较低级别的函数使用。(使用矢量并将其调整为数据长度。
当(结束 - 开始 == 0( - 这是一对浪费的函数调用时不要触底。当它们相差 2 时停止。对两者进行排序并开始备份树。
您的合并算法应该全部位于单个函数中,您可以对其进行一些清理。基本的合并算法是:
- 而 A 和 B 都有元素:
- 如果 A 的第一个元素小于 B 的第一个元素:
- 删除 A 并将其附加到结果中
- 否则删除 B 并将其附加到结果中
- 如果 A 的第一个元素小于 B 的第一个元素:
- 将任何仍具有元素(A 或 B(的序列附加到结果中
希望这有帮助。
首先,请注意,Hoare将他的新排序命名为"quicksort"是有充分理由的 - 它比当时已知的其他排序(包括Merge-sort(快得多。换句话说,不要指望合并排序跟上快速排序(作为一般规则(,几乎不管它的实现效果如何。
也就是说,您通常可以实现比此处更好的合并排序。特别是,合并排序可以"自下而上"而不是"自上而下"实现。现在,您的函数基本上将数组分成两半,然后调用自身对数组的每一部分进行排序。
因此,您执行了很多递归调用,基本上每个调用所做的只是计算需要排序的数组部分中间的索引。这给相对琐碎的计算增加了相当多的开销。
最后,无论如何,这样做的结果几乎已成定局。你总是不断地进行递归调用,直到一个分区只有一个或两个元素(左右(。
就您需要的额外空间而言,一个与输入大小相同的额外数组就足够了(即使这不是绝对必要的,但就地合并技术增加了复杂性,因此它们更难正确编写,并且无论如何运行速度较慢(。
由于您知道要拆分数组,直到您只得到两个项目,因此您可以首先从那里开始。只需获取第一对元素,如果它们出现故障,请交换它们。继续对阵列的其余部分进行配对。
然后你处于合并阶段:将第一对与第二对合并,将第三对与第四对合并,依此类推。继续执行阵列的其余部分。重复该过程,直到对整个数组进行排序。
不过,您通常不希望从一个分区中的两个项目开始。您通常希望从一次对(例如(10 个项目使用插入排序开始,然后进入合并阶段(尽管精确大小并不重要(。
这里的问题最多是恒定因素 - 而不是渐近性能。IOW有很大的风险进入过早的微优化。也就是说,当它不是那么过早时,仍然值得学习如何,所以......
首先,高性能排序是AFAICT始终是混合算法。那些可怕的 O(n^2( 算法,如插入排序,实际上非常好,前提是 n 足够小,所以当 n 变小时,实用的排序算法倾向于切换策略。如果你正在对一百万个项目进行排序,并且你的策略切换发生在 n=10 时,这听起来可能是你对微不足道的 10 个项目排序的唯一好处,但在你的百万项目排序过程中,这种情况会发生大约 100,000 次,所以累积的好处可以很好地得到回报。
对于硬编码的嵌套 if 集,甚至还有非常小的 n 的情况,以便对于每个排列,应用一组预先确定的最佳移动/交换。例如,对于 n=3,有 6 种可能的物品排序 - 6 种情况需要检测,6 组移动/交换需要弄清楚。对于 n=4,现在是 24 种情况,所以已经变得非常痛苦了,不要忘记太多的代码也会减慢速度。
一旦你确定算法本身是正确的,看看哪些工作可以从内部循环中移出。这有双重好处 - 首先,工作完成的频率较低。其次,内部循环本身变得更简单,因此编译器可以更好地优化它。当然,代价是代码的整体复杂性增加,并且要注意 - 编译器应该已经为您做了一些工作,因此不仅很容易浪费时间让编译器为其工作,而且很容易通过弄错来引入错误并阻止其他编译器优化并最终得到较慢的代码。
回归测试,测量前后的性能,如果有疑问,请检查生成的机器代码等。
不过,一个明显的目标 - 有一个循环很方便,并且在每次迭代时检查要合并的一个/另一个源是否耗尽,但这也是低效的。因此,当其中一个源耗尽时,请检测一次并使用特殊情况循环来完成对另一个源的处理。
此外,为了避免在每个循环中都执行耗尽检查,如果 中项目最少的源数组有 k 个项目,则可以安全地执行 k 次迭代,而无需进行耗尽检查。因此,有一个外部循环,您可以在其中检查 k 是什么,以及一个内部循环来检查 k 项,而无需进行穷竭检查。
第二点使内部循环在大部分合并中保持简单,到最后的好处较小(因为每次检查 k 时都会变小(。它还增加了额外的复杂性 - 一个外部循环 - 所以它不是免费的。第一点意味着你有一个简单的内部循环,用于在最后完成。
另一种方法可能是让一个内部循环仅从一个源获取,直到它耗尽或下一个项目太大,然后另一个内部循环从另一个源获取。
最后,除非你对你的特定平台(基于GPU的排序?(或你的数据(仅O(n(整数排序算法?(有一些特别的了解,否则很难击败std::sort
。基本上,编写标准库实现的人已经尝试了所有这些以及更多。
这些例子相当快,在我的系统上,Intel 2600K 3.4GHz,对400万个伪随机32位无符号整数进行排序只需要不到0.4秒的时间。自下而上使用迭代来生成用于合并运行的索引,而自上而下使用递归来生成索引,这是一个很小的开销,因为大部分时间都花在合并数据上,而不是生成索引上。
自下而上:
template <typename T>
void BottomUpMergeSort(T a[], T b[], size_t n);
template <typename T>
void BottomUpCopy(T a[], T b[], size_t ll, size_t rr);
template <typename T>
void BottomUpMerge(T a[], T b[], size_t ll, size_t rr, size_t ee);
template <typename T>
void MergeSort(T a[], size_t n) // entry function
{
if(n < 2) // if size < 2 return
return;
T *b = new T[n];
BottomUpMergeSort(a, b, n);
delete[] b;
}
size_t GetPassCount(size_t n) // return # passes
{
size_t i = 0;
for(size_t s = 1; s < n; s <<= 1)
i += 1;
return(i);
}
template <typename T>
void BottomUpMergeSort(T a[], T b[], size_t n)
{
size_t s = 1; // run size
if(GetPassCount(n) & 1){ // if odd number of passes
for(s = 1; s < n; s += 2) // swap in place for 1st pass
if(a[s] < a[s-1])
std::swap(a[s], a[s-1]);
s = 2;
}
while(s < n){ // while not done
size_t ee = 0; // reset end index
while(ee < n){ // merge pairs of runs
size_t ll = ee; // ll = start of left run
size_t rr = ll+s; // rr = start of right run
if(rr >= n){ // if only left run
rr = n;
BottomUpCopy(a, b, ll, rr); // copy left run
break; // end of pass
}
ee = rr+s; // ee = end of right run
if(ee > n)
ee = n;
BottomUpMerge(a, b, ll, rr, ee);
}
std::swap(a, b); // swap a and b
s <<= 1; // double the run size
}
}
template <typename T>
void BottomUpCopy(T a[], T b[], size_t ll, size_t rr)
{
while(ll < rr){ // copy left run
b[ll] = a[ll];
ll++;
}
}
template <typename T>
void BottomUpMerge(T a[], T 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
}
}
}
自上而下。共递归调用 (...阿托,...AtoB( 避免不必要的数据复制。TopDownMerge(( 与 BottomUpMerge(( 相同。
template <typename T>
void TopDownSplitMergeAtoA(T a[], T b[], size_t ll, size_t ee);
template <typename T>
void TopDownSplitMergeAtoB(T a[], T b[], size_t ll, size_t ee);
template <typename T>
void TopDownMerge(T a[], T b[], size_t ll, size_t rr, size_t ee);
template <typename T>
void MergeSort(T a[], size_t n) // entry function
{
if(n < 2) // if size < 2 return
return;
T *b = new T[n];
TopDownSplitMergeAtoA(a, b, 0, n);
delete[] b;
}
template <typename T>
void TopDownSplitMergeAtoA(T a[], T 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
}
template <typename T>
void TopDownSplitMergeAtoB(T a[], T 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
}
template <typename T>
void TopDownMerge(T a[], T 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
}
}
}
- 使用C++程序合并排序没有得到正确的输出
- 用于合并排序的合并函数
- 合并排序不排序自创建数组类 c++
- 仅在大型阵列上出现合并排序分段错误
- C++合并排序可视化工具
- 没有输出的合并排序我做错了什么?
- 字符串上的合并排序上的 Seg 错误
- 使用向量在 c++ 中合并排序实现
- 我无法让我的合并排序实现运行
- 合并排序:分段错误核心转储
- 合并排序中的错误计数:计数反转
- C++ 多线程 - 与线程合并排序的算法替代
- C++中合并排序算法的奇怪行为
- 合并排序没有给我任何输出
- C++ 中的合并排序
- 数组为此合并排序函数提供了正确的输出,但向量给出了不正确的输出.出了什么问题?
- 在实现合并排序代码时无法计算所有反转
- 在C++中使用迭代器合并排序
- 运行合并排序递归算法时EXC_BAD_ACCESS错误
- 我的C++合并排序代码不起作用。我在这里错过了什么?