比qsort更快的c++排序类

C++ sorting classes faster than qsort

本文关键字:c++ 排序 qsort      更新时间:2023-10-16

我有一个class

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; 

我使用operator与string进行二进制搜索和operator<使用Zaposlenik进行排序>

我不能改变头类,我只能在。cpp中编写代码。

我还有

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

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

我使用qsort和bsearch,它太慢了。上传时不能超过3秒

我已经编写了重载操作符,我相信它们很好,但显然qsort可以更快。

Vector按字符串prezme排序,名称从"aaaa"到"ZZZZ",所以4个字母的大字母和小字母组合。

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

谁能告诉我哪种排序比qsort更快?请记住,我对main没有任何控制,并且在创建成员时无法计数。

注:类中还有其他函数,但它们对这部分没有任何意义。它也有搜索功能,但我相信这是最快的。

三件事:

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

  • 覆盖swap为您的类,使它可以有效地交换,而不是通过临时变量复制。但是,这需要更改头文件(因为您需要访问私有变量)。

  • 由于排序字符串的长度是固定的4,因此使用不同的排序算法将是有益的。经典的选择是基数排序,这很容易实现。从你的一些评论来看,你的教授似乎希望你实现这个。

自动分级服务器,将500000 Zaposlenik输入矢量zaposlenici,然后进行200000次搜索。我用了qsort和bsearch,但是太慢了。

只是澄清一下,您没有在每次调用搜索之前调用qsort,对吗?因为二分查找只有在列表已经排序的情况下才快速。如果你在每次搜索之前对列表进行排序,你会得到糟糕的性能。

考虑到您概述的约束(所有字符串都是四个字符长),我刚刚测试了std::sort与自定义桶排序,在一百万个元素上,桶排序快了8倍。提示:四个字符的字符串可以用4 * 6 = 24位编码,因此您将需要16.777.216个桶来计数。

有几件事需要考虑。第一次和首先,不能std::vector<Zaposlenik>上使用qsortqsort在交换时使用memcpy复制对象,并且memcpy只适用于具有琐碎副本的对象。使用qsort在这种情况下可能会更快,因为它不会正确地复制对象。你必须使用std::sort;其他都不行

这样做了:速度(或者至少是你可以参加的派对)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 );
}

此函数将使用系统优化的swapstd::string。为了确保std::sort使用它,您还应该提供一个重载的自由函数swap(在与Zaposlenik相同的名称空间),它调用您的成员功能:

void
swap( Zaposlenik& lhs, Zaposlenik& rhs )
{
    lhs.swap( rhs );
}

原因:std::sort调用了一个自由函数swap

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

无论如何,这是我建议的方法:因为你是基于字符串排序的,实现一个专门的Trie版本,当按字典顺序排序序列时,你不能打败Trie的性能。你可以完全在你的。cpp文件中实现这个数据结构,并在你的Firma::sort方法中实例化它。

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


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

Edit:如果你知道你只会遇到a..z和a..z中的字符,那么你的基本字母大小为2*26 = 52而不是256。因此,Trie的每个节点中的查找表的大小只需为52(也就是说,每个节点最多可以有52个子节点)。