为什么"quick sorting"算法的这两种变体在性能上差异如此之大?
Why do these two variations on the "quick sorting" algorithm differ so much in performance?
我最初想出了一些排序算法来编码C++用于练习。人们告诉我这非常低效(事实上,对几百个数字进行排序需要~10秒(。算法是记住向量中的第一个元素("枢轴"(,然后解析所有其他元素,如果枢轴较小,则将每个元素移动到枢轴的左侧,或者不执行任何其他操作。这会将列表拆分为较小的列表进行排序;其余的通过递归完成。
所以现在我知道将列表一分为二并像这样进行递归本质上是快速排序所做的(尽管在如何进行分区方面有很多变化(。我不明白为什么我的原始代码效率如此低下,所以我写了一个新的代码。有人提到这是因为insert((和erase((函数,所以我确保不使用它们,而是使用swap((。
旧(慢(:
void sort(vector<T>& vec){
int size = vec.size();
if (size <= 1){ //this is the most basic case
return;
}
T pivot = vec[0];
int index = 0; //to help split the list later
for (int i = 1; i < size; ++i){ //moving (or not moving) the elements
if (vec[i] < pivot){
vec.insert(vec.begin(), vec[i]);
vec.erase(vec.begin() + i + 1);
++index;
}
}
if (index == 0){ //in case the 0th element is the smallest
vec.erase(vec.begin());
sort(vec);
vec.insert(vec.begin(), pivot);
}
else if(index == size - 1){ //in case the 0th element is the largest
vec.pop_back();
sort(vec);
vec.push_back(pivot);
}
//here is the main recursive portion
vector<T> left = vector<T>(vec.begin(), vec.begin() + index);
sort(left);
vector<T> right = vector<T>(vec.begin() + index + 1, vec.end());
sort(right);
//concatenating the sorted lists together
left.push_back(pivot);
left.insert(left.end(), right.begin(), right.end());
vec = left;
}
新(快速(:
template <typename T>
void quickSort(vector<T>& vec, const int& left, const int& right){
if (left >= right){ //basic case
return;
}
T pivot = vec[left];
int j = left; //j will be the final index of the pivot before the next iteration
for (int i = left + 1; i <= right; ++i){
if (vec[i] < pivot){
swap(vec[i], vec[j]); //swapping the pivot and lesser element
++j;
swap(vec[i], vec[j]); //sending the pivot next to its original spot so it doesn't go the to right of any greater element
}
}
//recursion
quickSort(vec, left, j - 1);
quickSort(vec, j + 1, right);
}
性能的差异是疯狂的;较新的版本可以在不到一秒钟的时间内对数万个数字进行排序,而第一个版本则无法用100个数字做到这一点。erase(( 和 insert(( 到底在做什么来减慢它的速度?真的是 erase(( 和 insert(( 导致了瓶颈,还是我缺少其他东西?
首先,是的,insert()
和erase()
会比swap()
慢得多。
insert()
,在最好的情况下,需要将插入矢量的位置之后的每个元素移动到矢量中的下一个位置。想想如果你把自己推到拥挤的人群中间会发生什么——你身后的每个人都必须后退一步,为你腾出空间。在最坏的情况下,由于插入矢量会增加矢量的大小,因此矢量可能会耗尽其当前内存位置的空间,从而导致整个矢量(逐个元素(复制到新空间中,在该空间中它有空间容纳新插入的项目。当向量中间的元素被erase()
'd时,它之后的每个元素都必须被复制并向上移动一个空格;就像你离开这条线,你后面的每个人都会向前走一步。相比之下,swap()
仅移动要交换的两个元素。
除此之外,我还注意到两个代码示例之间的另一个重大效率改进:
在第一个代码示例中,您有:
vector<T> left = vector<T>(vec.begin(), vec.begin() + index);
sort(left);
vector<T> right = vector<T>(vec.begin() + index + 1, vec.end());
sort(right);
它使用C++向量的范围构造函数。每次代码到达这一点时,当它创建left
和right
时,它遍历整个vec
并将每个元素逐个复制到两个新向量中。
在更新、更快的代码中,没有一个元素被复制到新的向量中;整个算法发生在原始数字存在的确切内存空间中。
向量是数组,因此在结束位置以外的位置插入和删除元素是通过将所有位置之后的元素重新定位到其新位置来完成的。
- 删除一个线程上有数百万个字符串的大型哈希映射会影响另一个线程的性能
- 为什么比较运算符如此快速
- 为什么堆栈和堆在内存中分离得如此之多?
- 为什么 Lisp 中 1000 阶乘的计算如此之快(并显示正确的结果)?
- 为什么C++线程/未来开销如此之大
- 为什么"quick sorting"算法的这两种变体在性能上差异如此之大?
- 为什么整数的垃圾价值如此之大
- Python vs CPP:为什么速度差异如此之大
- F#性能:是什么让这个代码如此缓慢
- C++-为什么标记化器从文件中读取行的速度如此之慢
- 如何确定为什么我的库中的文本大小如此之大
- 为什么在使用std::bind和lambda时生成的对象如此之大
- 为什么从内存映射文件中读取速度如此之快
- 为什么我的C++程序在从长double切换到float128时速度如此之慢
- 为什么可执行文件在使用静态库编译后会增长如此之多
- 为什么ucontext的开销如此之高
- 为什么我看到这些函数之间有如此巨大的性能差异
- 为什么分配数组后的堆如此之大?
- Win32 resource.hpp为什么资源ID如此之高
- 如何找出为什么我的DLL增长如此之快