标题已经说明了问题....
选择容器是否会影响默认的std::sort算法的速度?例如,如果我使用list,排序算法只是切换节点指针还是同时切换节点中的所有数据?
选择确实会产生影响,但预测哪种容器最有效是非常困难的。最好的方法是使用对您的应用程序最易于使用的容器(可能是std::vector),查看该容器是否足够快速地进行排序,如果是,则坚持使用它。如果不是,请对您的排序问题进行性能分析,并根据分析数据选择不同的容器。
作为一名前讲师和前培训师,我有时会感到个人责任,因为人们普遍认为链表具有神秘的性能增强属性。从一个知道真相的人那里得到这个结论:链表出现在很多教科书和教程中的唯一原因是因为对于编写这些书籍和教程的人来说,拥有一个可以说明指针、动态内存管理、递归、搜索和排序的数据结构非常方便——这与效率无关。
我认为 std::sort
在列表上不起作用,因为它需要随机访问迭代器,而 list<>
不提供这种类型的迭代器。请注意,list<>
提供了一个名为 sort
的方法,但它与 std::sort
完全不同。
容器的选择确实很重要。STL 的 std::sort
依赖于迭代器来抽象化容器存储数据的方式。它只使用您提供的迭代器来移动元素。这些迭代器在访问和分配元素方面工作得越快,std::sort
就会运行得越快。
std::list
绝对不是std::sort()
的好选择,因为 std::sort()
需要随机存取迭代器。 std::map
和其他关联容器也不行,因为无法强制执行元素的位置;也就是说,元素在 map 中的位置不能通过特定位置插入或交换来强制执行。 在标准容器中,我们只能选择使用std::vector
和std::deque
。
std::sort()
像其他标准算法一样,仅通过交换元素值(*t = *s
)来操作。即使列表可以神奇地支持O(1)访问,链接也不会被重新组织,而是它们的值会被交换。
由于std::sort()
不会改变容器的大小,在运行时性能方面,使用std::vector
或者 std::deque
应该没有区别。原始数组排序速度也应该很快,可能甚至比标准容器更快--但我并不希望这种差异足以证明使用它们的价值。
这取决于元素类型。
如果您只存储指针(或POD),那么vector将是最快的。如果您存储对象,则list的排序会更快,因为它会交换节点而不是物理元素。
#include <iostream>
#include <vector>
#include <deque>
#include <array>
#include <list>
#include <iterator>
#include <cstdlib>
#include <algorithm>
#include "Timer.h"
constexpr int SIZE = 1005000;
using namespace std;
void test();
int main(){
cout<<"array allocates "<<static_cast<double>(SIZE)/(1024*1024)<<" MB\n";
test();
return 0;
}
void test(){
int values[SIZE];
int size = 0;
//init values to sort:
do{
values[size++] = rand() % 100000;
}while(size < SIZE);
//feed array with values:
array<int, SIZE> container_1;
for(int i = 0; i < SIZE; i++)
container_1.at(i) = values[i];
//feed vector with values
vector<int> container_2(begin(values), end(values));
list<int> container_3(begin(values), end(values));
deque<int> container_4(begin(values), end(values));
//meassure sorting time for containers
{
Timer t1("sort array");
sort(container_1.begin(), container_1.end());
}
{
Timer t2("sort vector");
sort(container_2.begin(), container_2.end());
}
{
Timer t3("sort list");
container_3.sort();
}
{
Timer t4("sort deque");
sort(container_4.begin(), container_4.end());
}
}
计时器的代码如下:
#include <chrono>
#include <string>
#include <iostream>
using namespace std;
class Timer{
public:
Timer(string name = "unnamed") : mName(name){ mStart = chrono::system_clock::now();}
~Timer(){cout<<"action "<<mName<<" took: "<<
chrono::duration_cast<chrono::milliseconds>(
chrono::system_clock::now() - mStart).count()<<"ms"<<endl;}
private:
chrono::system_clock::time_point mStart;
string mName;
};
向量。
始终使用向量作为默认容器。它具有任何其他容器中最低的空间开销和最快的访问速度(除了其他优点,如与 C 兼容的布局和随机访问迭代器)。
现在,问问自己 - 你的容器还需要做什么?你需要强大的异常保证吗?列表、集合和映射可能是更好的选择(虽然它们都有自己的排序例程)。你需要经常向容器前面添加元素吗?考虑双端队列。你的容器是否需要始终保持排序?集合和映射可能更适合。
最后,确定对于你来说“最佳”是什么,然后选择最合适的容器并测量其在满足你需求方面的表现。
这确实很重要,因为不同的容器具有不同的内存访问模式等,这可能会起到作用。
然而,std::sort
无法在 std::list<>::iterators
上工作,因为它们不是 RandomAccessIterators。此外,虽然可以为 std::list<>
实现一个特化版本来洗牌节点指针,但它可能会产生奇怪和令人惊讶的语义后果 - 例如,如果您在向量中的排序范围内拥有迭代器,则其值将在排序后更改,而这在此特化中不成立。
std::sort需要随机访问迭代器,因此您唯一可以使用的选项是vector或deque。它将交换值,并且猜测vector可能比deque执行得稍微快一些,因为它通常具有更简单的底层数据结构。不过差异可能非常微小。
如果您使用std::list,则有一个专门化(std::list::sort),应该交换指针而不是值。但是,由于它不是随机访问,它将使用归并排序而不是快速排序,这可能意味着算法本身会慢一些。
无论如何,我认为答案通常是vector。如果每个元素都有大型类,因此复制开销占主导地位,则list可能会击败它。或者,您可以在vector中存储对它们的指针,并提供自定义谓词以适当地对它们进行排序。