基于 C++ 中的多个事物进行排序

Posted

技术标签:

【中文标题】基于 C++ 中的多个事物进行排序【英文标题】:Sort based on multiple things in C++ 【发布时间】:2010-08-26 11:37:51 【问题描述】:
struct Record

    char Surname[20];
    char Initial;
    unsigned short int Gender; //0 = male | 1 = female
    unsigned short int Age;
;
Record X[100];

如何使用 Quicksort 将值按年龄增长进行排序,女性在男性之前,姓氏按字母顺序排列?我有一个:

bool CompareData(const int& A, const int& B)

    return Records[A].Age < Records[B].Age; //this sorts by age atm

【问题讨论】:

一定要快速排序吗? 我已经实现了快速排序,但是我不确定如何提醒 CompareData() 正确排序。 【参考方案1】:

一般模式是:

bool CompareData(const T& a, const T& b) 
 
   if (a.PrimaryCondition < b.PrimaryCondition) return true;
   if (b.PrimaryCondition < a.PrimaryCondition) return false;

   // a=b for primary condition, go to secondary
   if (a.SecondaryCondition < b.SecondaryCondition) return true;
   if (b.SecondaryCondition < a.SecondaryCondition) return false;

   // ...

   return false;
 

其中&lt; 表示所需排序顺序中的“小于”,您可能需要为此使用自定义比较运算符(例如,strcmp 用于字符串,或者如果您想要降序排序,则反转&lt;) (感谢哈利指出这一点)

我在所有条件下都使用了&lt;,因为有时这是唯一可用的比较操作,例如当您必须使用未知数据类型的比较谓词时。

[编辑] 注意:最后一行return false 处理ab 被认为对比较器相等的情况。

想象一下a.PrimaryCondition==b.PrimaryConditiona.SecondaryCondition==b.SecondaryCondition - 在这种情况下,前面的条件都没有返回任何值。

【讨论】:

@Tyler:如果你想颠倒顺序,你交换return true;return false;你不要开始使用operator &gt;,因为正如peterchen所说,C++标准算法建立的实践库是排序由&lt; 定义,只有&lt; @peterchen false 到底支持什么?我尝试true 以保持排序的稳定性,但结果搞砸了。 @gustafbstrom:排序谓词需要返回true当且仅当a &lt; b,即false代表a==b。有关详细信息,请参阅此处:en.cppreference.com/w/cpp/concept/Compare --- 返回 truehere 弄乱您的结果。试想一下:相等的测试是!(a&lt;b) &amp;&amp; !(b&lt;a)。您的 return true 将无法通过该测试。 @peterchen 有道理,感谢您抽出宝贵时间。 另外:即使您可以断言所有元素都是唯一的(因此在逻辑上不可能相等)我仍然观察到 ab 可以引用同一个对象(至少,似乎没有相反的要求),因此返回false 仍然是必不可少的,原因与@peterchen 上述相同。【参考方案2】:
bool CompareData(const int& A, const int& B)

    return (Records[A].Age < Records[B].Age) ||
           ((Records[A].Age == Records[B].Age) && (Records[A].Gender > Records[B].Gender)) || 
           ((Records[A].Age == Records[B].Age) && (Records[A].Gender == Records[B].Gender) &&
              (strcmp(Records[A].Surname, Records[B].Surname) < 0));

这首先按年龄进行比较,如果 A 应该根据年龄出现在 B 之前,则返回 true。

如果年龄相等,则按性别进行比较,如果根据性别(A 是女性,B 是男性),A 应该出现在 B 之前,则返回 true。

如果年龄和性别相同,则按姓氏进行比较(使用strcmp,尽管如果您使用std::string 而不是char 数组,您可能只使用&lt;),并返回true如果 A 应该按姓氏字母顺序出现在 B 之前。

【讨论】:

谢谢 :) 这真的很有帮助。【参考方案3】:

所有唱歌跳舞比较器的另一个选择是确保您的排序是稳定的排序(快速排序不一定是稳定的),并且每次使用不同的比较器进行多次排序。

例如

bool CompareAge (const record& l, const record& r)

  return l.age < r.age;


bool CompareGender (const record& l, const record& r)

  return l.gender < r.gender;


std::stable_sort(X, X+100, &CompareGender);
std::stable_sort(X, X+100, &CompareAge);

这可能会稍微慢一些,但可以让您更灵活地使用排序顺序

【讨论】:

但是快速排序不是稳定的。 (编辑后)正如我所说,std::sort 不稳定;你应该改用std::stable_sort 可以的。取决于实现 根据实现来回答根本不是一个好的答案。 ;) std::sort 也不能​​保证稳定 -> 改为 stable_sort 技术上的快速排序可能是稳定的,具体取决于您选择枢轴的方式,它只是通常不使用稳定的实现,因为它们速度较慢。由于 OP 正在实施他们自己的快速排序,我认为最好不要假设【参考方案4】:

简单的C++解决方案是

struct Record 
    std::string Surname;
    char Initial;
    unsigned short int Gender; //0 = male | 1 = female
    unsigned short int Age;

    operator<(Record const& rhs) const 
        return std::tie(Gender, Age, Surname) < std::tie(rhs.Gender, rhs.Age, rhs.Surname);
;

但是,std::tie 直接按字段值排序。这意味着您不能使用 char[20] 并且男性将首先排序。一个简单的变体解决了这个问题:

struct Record 
    char Surname[20];
    char Initial;
    unsigned short int Gender; //0 = male | 1 = female
    unsigned short int Age;

    operator<(Record const& rhs) const 
        return std::make_tuple(~Gender, Age, std::string(Surname)) <
               std::make_tuple(~rhs.Gender, rhs.Age, std::string(rhs.Surname));
;

使用make_tuple,我们可以传递表达式。

【讨论】:

您还可以混合使用这些方法来尽可能避免复制,首先将转换后的值存储到函数局部变量中,然后返回 std::tie 的结果,尽可能使用现有成员和转换后的值除此以外。或者反转它,使用std::make_tuple,但将未修改的参数包装在std::ref/std::cref 中,以使生成的tuple 包含引用而不是值的副本。在这种情况下,没有真正的需要(因为只有一个成员是非值类型,并且必须将其转换为 string [或在 C++17 上,string_view]),但在其他情况下很有用。跨度> 如果你想混合引用和表达式,你也可以使用std::forward_as_tuple【参考方案5】:

最好像这样实现比较器:

bool CompareRecords(const Record& a, const Record& b)

    if (a.Age < b.Age)
        return true;
    else if (a.Age > b.Age)
        return false;

    if (a.Gender < b.Gender)
        return true;
    else if (a.Gender > b.Gender)
        return false;

    if (strcmp(a.Surname, b.Surname) < 0)
        return true;

    return false;

这使您可以轻松使用std::sort 算法。排序本身将如下所示:

std::sort(X, X + 100, &CompareRecords);

编辑

您甚至可能希望为此结构实现operator &lt;——在这种情况下,您通常可以将Record 结构的两个对象与运算符&lt; 进行比较。然后就不需要将第三个参数添加到std::sort。好吧,有了它并实现了operator ==,您可以进行所有可能的比较。 :)

【讨论】:

所以 std::sort 比使用我自己的 Quicksort 更好? 是的,std::sort 是更好的解决方案——只要您不想学习如何实现 Quicksort 本身。但在那种情况下,这样的排序结构并不是一个好主意。 ;) @Johnny std::sort 比您自己的快速排序更好,主要是因为您不必编写它。除非您编程的目的是自学排序算法,否则您通常应该使用已经存在的算法。 但是我的 CompareData 返回 bool @Johnny 关于std::sort,您的函数的问题不是它不返回bool,而是它没有将const Record&amp; 作为参数。 std::sort 函数需要为其比较器提供实际的记录结构以进行比较,而不是像您的函数现在采用的那样对记录数组进行索引。

以上是关于基于 C++ 中的多个事物进行排序的主要内容,如果未能解决你的问题,请参考以下文章

基于多个列对包含numpy文本数组中的数字的列进行排序

pandas读取csv数据sort_index函数基于多层行索引对数据排序(设置level参数基于多层索引中的多个层行索引进行数据排序ascending参数指定不同层的排序方向)

如何在 Iphone SDK 中基于单个数组对多个数组进行排序

是否可以使用归并排序 (C#) 基于多个条件对数组进行排序?

基于其他 int 数组的 C++ 排序

内部排序算法总结(上)C++实现