为什么"quick sorting"算法的这两种变体在性能上差异如此之大?

Why do these two variations on the "quick sorting" algorithm differ so much in performance?

本文关键字:性能 如此之 算法 sorting quick 为什么 两种      更新时间:2023-10-16

我最初想出了一些排序算法来编码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++向量的范围构造函数。每次代码到达这一点时,当它创建leftright时,它遍历整个vec并将每个元素逐个复制到两个新向量中。

在更新、更快的代码中,没有一个元素被复制到新的向量中;整个算法发生在原始数字存在的确切内存空间中。

向量是数组,因此在结束位置以外的位置插入和删除元素是通过将所有位置之后的元素重新定位到其新位置来完成的。