【问题标题】:Why is this std::sort comparison failing?为什么这个 std::sort 比较失败?
【发布时间】:2019-10-23 22:22:10
【问题描述】:

我有一个无符号整数向量的向量。父向量的每个元素都是三个无符号整数的向量。我主要想按子向量的第一个元素的降序对父向量进行排序,但我也想按第三个元素的 升序 顺序对具有相同第一个元素的任何子向量进行排序。我最初使用以下代码进行此操作:

sort(league_vector.begin(), league_vector.end());
reverse(league_vector.begin(), league_vector.end());
sort(league_vector.begin(), league_vector.end(),
   [](const std::vector<unsigned int>& a, const std::vector<unsigned int>& b) {return a[0] == b[0] && a[2] < b[2];});

所以只是排序,然后反转,整个事情,这将按第一个元素排序。然后使用 lambda 函数进行自定义排序,该函数仅在第三个元素较小 并且 第一个元素相等时才返回 true。

当我在父向量中的元素数量相对较少(大约 50 个或更少)时,这似乎工作得很好,但是当我有更多的元素时,最终的排序会变得非常混乱,根本没有明显的模式。

我已将其替换为单个自定义排序:

sort(league_vector.begin(), league_vector.end(),
   [](const std::vector<unsigned int>& a, const std::vector<unsigned int>& b)
   {return ((a[0] > b[0]) || (a[0] == b[0] && a[2] < b[2]));});

因此,当第一个元素较大或第三个元素较小且第一个元素相同时,这将返回 true。这似乎工作正常,所以我只是使用它,但我无法弄清楚第一种方法有什么问题。特别是第一种方法似乎在某些时候有效,而第二种比较无论如何只是第一种比较的扩展。

【问题讨论】:

  • a[0] == b[0] &amp;&amp; a[2] &lt; b[2] 是否满足严格的弱排序?我认为不会。此外,您的第二个比较器也很奇怪,您在 [0] 上检查 >,但在 [2] 上检查
  • @O'Neil 实际上,这不是问题所在。只有当a &lt; b &amp;&amp; a &gt; b 时,不对称才会被打破。如果!(a &lt; b) &amp;&amp; !(b &lt; a),那只是意味着两个元素相等。但是,这种比较不尊重不可比性的传递性。
  • 文档对此进行了解释,尽管它没有为尚未了解的人详细介绍:cplusplus.com/reference/algorithm/sort
  • @Borgleader 对于您问题的第二部分,这是经过深思熟虑的。我基本上是在对“获胜”的数量进行排名,较高的数字位于列表的顶部。但是,当该数字出现平局时,我希望他们排名第一,所以在这种情况下,它是较低的数字。

标签: c++ sorting vector std


【解决方案1】:

首先,std::sort 不是一个稳定的排序,这意味着它不会保留等价元素的顺序。如果您想要稳定的排序,请使用std::stable_sort。此外,您的自定义比较功能没有意义。让我们分析一下它的行为:

如果a[0] 等于b[0],您的函数将返回比较a[2]b[2] 的结果。但是,如果 a[0] 不等于 b[0],您的函数将始终返回 false。由于等价被定义为!(a &lt; b) &amp;&amp; !(b &lt; a),根据你的比较函数,任何两个具有不同第一个元素的向量都是相等的。

这个函数也不是一个有效的比较函数,因为它不满足严格的弱排序。任何具有不同第一个元素的两个向量都相等,但具有相同第一个元素的两个向量不一定相等。这意味着如果a = {1, 2, 3}b = {2, 3, 4}c = {1, 3, 4}a == bb == ca != c

【讨论】:

  • 谢谢。我不得不通读几遍,但我想我已经明白了。我认为根本问题是/我并没有真正理解排序算法的实际工作原理。因此,如果我正确地跟随了你,我的第二个比较函数实际上应该没问题,因为具有不同第一个元素的向量现在永远不会相等。只有当第一个和第三个元素相同时,它们才会相等,这是正确的(尽管在这种特殊情况下这永远不会发生,因为第三个元素都是唯一值)。这也应该满足严格的弱排序。
  • 唯一的困惑是当向量只有几十个元素时,第一种方法实际上似乎有效的频率。我想我只是幸运地从前两个排序操作中保留了等效元素的顺序。
  • @MyDarts 是的,你的第二个函数是正确的。可以这样想:如果第一个元素不相等,我们只返回比较这些元素的结果。如果第一个元素相等,我们通过返回比较第三个元素的结果来打破平局。你也可以这样写:a[0] != b[0] ? a[0] &lt; b[0] : a[2] &lt; b[2].
【解决方案2】:

为什么您的第一次尝试失败了?我们举个具体的例子,重点关注一下这个比较,做一个简单的测试来解释为什么这个是无效的。

这里以两个联赛向量为例:

std::vector<std::vector<unsigned int>> league_vector = {{1,2,3,4}, {2,3,4,5}, {4,5,6,7}};

现在把这个给std::sort

std::sort(league_vector.begin(), league_vector.end(),
          [](const std::vector<unsigned int>& a, 
          const std::vector<unsigned int>& b) {return a[0] == b[0] && a[2] < b[2];});

专注于此:

a[0] == b[0]

假设std::sort 按此顺序比较league_vector 中的前两个向量

a={1,2,3,4} b={2,3,4,5}

您的比较函数将返回false,因为a[0] != b[0]

那么如果编译器做了一个切换,然后给你这个,只是为了看看你的函数是不是模棱两可的:

a={2,3,4,5} b={1,2,3,4}

换句话说,一个简单的值切换。由于a[0] != b[0],您再次返回false

这有什么意义呢,您在第一次测试中说a 应该在b 之后,而在第二次切换值的测试中,a 应该在b 之后?

排序算法理所当然地变得混乱,并将值置于某种非正统的顺序中。

请注意,Visual Studio 编译器执行我描述的这个测试,其中比较函数给出ab,检查返回值,然后检查ba 并检查返回值。如果如指出的那样存在不一致,则调试运行时会断言“无效比较”(或类似)条件。

【讨论】:

  • 其实,相同元素的比较函数在两个方向都返回false并没有什么问题。这只是意味着元素是平等的。当a == bb == ca != c 时出现问题,其中相等是使用OP 的比较运算符定义的。
猜你喜欢
  • 2012-04-17
  • 1970-01-01
  • 2011-10-16
  • 2021-05-05
  • 1970-01-01
  • 1970-01-01
  • 2010-10-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多