C ++排序类比qsort更快

时间:2013-04-29 12:59:52

标签: c++ sorting optimization

我有一个班级

class Zaposlenik { 
private:
    string prezime; 
    string funkcija; 
    double placa; 
public:
    bool operator==(const string& prezime) const; 
    bool operator<(const string &prezime) const; 
    bool operator<(const Zaposlenik &other) const; 

我使用带字符串的运算符进行二进制搜索和运算符&lt;与Zaposlenik进行分类

我无法更改头文件类我只能在.cpp中编写代码。

我也有

class Firma { 
private: 
vector<Zaposlenik> zaposlenici; 
public: 
void sort();

我也不能改变那个类,我必须为它编写.cpp。 我将2 .cpp上传到自动评分服务器,该服务器将500 000 Zaposlenik输入到矢量zaposlenici,然后进行2 000 000次搜索。

我使用qsort和bsearch而且速度太慢了。我上传时不能超过3秒。

我写了重载的运算符,我相信它们很好,但显然qsort可以更快。

Vector按字符串prezime排序,名称从“aaaa”到“ZZZZ”,所以4个字母组合大小写字母。

string funkcija;double placa;对排序没有任何意义。

有人能告诉我哪种比qsort快吗?请记住,我对主要内容没有任何控制权,因此我无法统计成员。

P.S。类中还有其他函数,但它们对此部分没有任何意义。 还有Bsearch的功能,但这个功能和我相信的一样快。

4 个答案:

答案 0 :(得分:9)

三件事:

  • 使用std::sort代替std::qsort,它更快,因为它可以内联对比较运算符的调用(如果您在标题中定义它或启用链接时优化)。

  • Override swap为您的班级,以便它可以有效地交换,而不是通过临时变量复制。但是,这需要更改标头(因为您需要访问私有变量)。

  • 由于您的排序字符串具有固定长度4,因此不同的排序算法将是有益的。经典的选择,相当容易实现,是radix sort。从你的一些评论来看,你的教授似乎希望你实现这一点。

答案 1 :(得分:3)

  

自动评分服务器,将500 000 Zaposlenik输入到矢量zaposlenici,然后进行2 000 000次搜索。我使用qsort和bsearch而且速度太慢了。

为了澄清一下,你没有在每次打电话给bsearch之前打电话给qsort,对吧?因为如果列表已经排序,则二进制搜索速度很快。如果您在每次搜索之前对列表进行排序,那么您的表现就会非常糟糕。

鉴于您列出的约束(所有字符串长度为四个字符),我只测试了std::sort与自定义存储桶排序,而在一百万个元素上,存储桶排序速度提高了8倍。提示:四个字符的字符串可以用4 * 6 = 24位编码,因此您需要16.777.216个桶进行计数。

答案 2 :(得分:2)

有几件事需要考虑。首先和 最重要的是,无法qsort上使用std::vector<Zaposlenik>qsort使用memcpy在交换时复制对象,以及 memcpy仅适用于具有普通副本的对象。使用qsort 在这种情况下可能会更快,因为它没有正确复制 对象。您必须使用std::sort;没有别的办法。

做到这一点:速度(或者至少是你可以参加的派对 ({1}}的影响力取决于两件事:速度 你的比较,以及交换的速度。你没有表现出什么 std::sort的确如此, 所以我们只能猜测。如果它执行的任何操作超过bool Zaposlenik::operator<( Zaposlenik const& other ) const;,那么您应该单独编写 比较函数,并用它调用return prezime, other.prezime )。另一个 方面是交换:std::sort最终使用std::sort, 其默认实现将类似于:

std::swap

对于许多课程,这涉及大量额外的复制;对于 例如,template <typename T> void std::sort( T& lhs, T& rhs ) { T tmp( lhs ); lhs = rhs; rhs = lhs; } ,这将做三个深刻的副本 字符串,可能涉及动态分配和释放 记忆。但是,std::string具有成员函数std::string; 在许多情况下,它可以通过交换实现交换 指针,而不是通过深层复制。这可能导致 显着加快。你的班级swap没有 然而,要优化Zaposlenik的任何事情,都要深入了解 副本。您应该提供成员函数std::swap

swap

此功能将使用系统优化的交换 void Zaposlenik::swap( Zaposlenik& other ) { swap( prezime, other.prezime ); swap( funkcija, other.funkcija ); swap( placa, other.placa ); } 。为了确保std::string使用它,您 还应该提供一个重载的自由函数std::sort(在 与swap)相同的命名空间,它会调用您的成员 功能:

Zaposlenik

原因是:void swap( Zaposlenik& lhs, Zaposlenik& rhs ) { lhs.swap( rhs ); } 调用自由函数std::sort

答案 3 :(得分:1)

这里的问题实际上是你对类头的限制。我怀疑瓶颈是排序时的交换操作还是词法字符串比较(或可能两者)。如果您根本无法更改类定义,那么改进它将会很棘手,因为您必须在实现中添加大量辅助代码,并且一切都变得比以前更加复杂。

无论如何,这是我建议的方法:由于你是基于字符串进行排序,实现自己的Trie的专用版本,在按字典顺序排序序列时,你无法击败Trie的性能。您可以在.cpp文件中完全实现此数据结构,并使用Firma::sort方法对其进行实例化。

由于您似乎专注于速度,您可能愿意在内存消耗方面进行权衡。因此,您将Treap中的每个节点实现为std::vector<std::shared_ptr<Trie>>,其初始化为256(所有插槽初始化为nullptr)或std::array<std::shared_ptr<Trie>,256>。 您现在基本上将每个字符串插入到数据结构中,然后再次将它们全部读出。这种方法在所有字符串的总大小上是线性的(因而是最优的)。


旁注: 注意,当遍历Trie时(即,当写Firma::zaposlenici成员时),每个节点处的256时隙表产生256的常数因子。如果您正在处理ASCII,可以将表大小减小到128或将单个字节分成半字节,从而产生2 * 16而不是256的开销。

编辑:如果您知道只会遇到来自a..z和A..Z的字符,那么您的基本字母大小为2 * 26 = 52而不是256.所以你的Trie的每个节点中的查找表只需要大小为52(即每个节点最多可以有52个子节点)。