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 :(得分:1)
这里的问题至多是不变因素 - 不是渐近的表现。 IOW存在进入过早微优化的强大风险。也就是说,当它不是那么不成熟时,仍然值得学习,所以...
首先,高性能排序是AFAICT始终混合算法。像插入排序那样糟糕的O(n ^ 2)算法实际上非常好,只要n足够小,所以实际的排序算法倾向于在n变小时切换策略。如果你正在排序一百万件物品并且你的战略转换发生在n = 10,那么听起来你可能只是为了获得一个简单的10项目排序,但是在你的百万件物品排序中这种情况发生了大约100,000次因此累积的好处可以很好地还清。
对于硬编码的嵌入式嵌套ifs中的n个非常小的n的情况,使得对于每个置换,应用预定的最佳移动/交换集合。例如,对于n = 3,项目有6种可能的排序 - 6种要检测的情况,6种移动/交换要弄清楚。对于n = 4,现在已有24个案例,所以已经非常痛苦,并且不要忘记太多的代码也会减慢速度。
一旦你确定算法本身是正确的,看看哪些工作可以从内循环中移出。这有双重好处 - 首先,这项工作不那么频繁。其次,内部循环本身变得更简单,因此编译器可以更好地优化它。价格当然是代码的整体复杂性增加,并且要注意 - 编译器应该已经为你做了一些这样的事情,所以很容易不仅浪费时间让编译器为它工作,而且通过获取来引入bug这是错误的,并阻止其他编译器优化,最终导致代码变慢。
回归测试,测量前后的性能,如果有疑问,请检查生成的机器代码等等。
一个显而易见的目标 - 拥有一个循环并且在每次迭代时检查合并的一个/另一个源是否耗尽是否方便,但这也是低效的。因此,当任何一个源耗尽时,检测一次并使用特殊情况循环来完成对其他源的处理。
另外,为了避免对每个循环进行耗尽检查,如果具有最少项目的源数组具有k个项目,则可以安全地进行k次迭代而不进行耗尽检查。所以有一个外部循环,你在那里检查k是什么,以及一个没有用尽检查的k项内循环。
第二点使得内部循环对于大多数合并保持简单,对结束的利益较少(因为每次检查时k变小)。它还增加了额外的复杂性 - 外部循环 - 所以它不是免费的。第一点意味着你有一个简单的内循环,可以在最后完成。
另一种方法可能是让内循环只从一个源获取,直到它耗尽或下一个项太大,然后另一个内循环从另一个源获取。
最后,非常非常难以击败std::sort
,除非您对特定平台(基于GPU的排序?)或您的数据(O(n)整数 - 有所了解 - 只排序算法?)。基本上,编写标准库实现的人已经尝试了所有这些以及更多。
答案 1 :(得分:0)
您的代码过于复杂。合并排序有两个部分:分割和合并。每个都应该是一个功能。
您还应该拥有顶级功能(用户将使用的公共界面)。
为了获得最大速度,您应该在顶层功能中分配一次所需的所有额外内存,然后将其传递给下级功能使用。 (使用向量并将其调整为数据长度。)
当(结束 - 开始== 0)时不要触底 - 这是一对浪费的函数调用。当他们的差异 2 时停止。对两者进行排序并开始重新启动树。
您的合并算法应该都在一个函数中,您可以稍微清理它。基本合并算法是:
希望这有帮助。
答案 2 :(得分:0)
首先,请注意Hoare有充分理由将其命名为“quicksort” - 它明显快于当时已知的其他类型(包括Merge-sort)。换句话说,不要指望合并排序能够跟上快速排序(作为一般规则),几乎不管它的实施情况如何。
那就是说,你可以通常比你在这里实现更好的合并排序。特别是,合并排序可以“自下而上”而不是“自上而下”实现。现在,你的函数基本上将数组分成两半,然后调用自己对数组的每一半进行排序。
所以,你做了很多递归调用,基本上每个都做的是计算需要排序的数组部分中间的索引。这为相对简单的计算增加了相当多的开销。
最后,无论如何,结果几乎已成定局。你总是继续进行递归调用,直到分区只有一个或两个元素(或左右)。
就你需要的额外空间来说,一个与输入相同大小的额外数组就足够了(即使这不是绝对必要的,但是就地合并的技术增加了复杂性,所以它们更难以写得正确,无论如何都要慢一点。)
既然你知道你要拆分数组,直到你只得到两个项目,你可以从那里开始。只需取出第一对元素,如果它们出现故障就更换它们。继续使用数组的其余部分。
然后你处于合并阶段:将第一对与第二对合并,第三对与第四对合并,依此类推。继续进行数组的其余部分。重复该过程,直到您对整个数组进行排序。
您通常不希望在分区中只使用两个项目。您通常希望首先在(例如)10个项目上使用插入排序,然后进入合并阶段(尽管精确的大小并不重要)。
答案 3 :(得分:0)
这些例子相当快,在我的系统上,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
}
}
}
自上而下。共同递归调用(... AtoA,... 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
}
}
}