排序算法是否应该在比较函数中传递相同的元素

时间:2016-08-16 04:08:25

标签: c++ algorithm sorting libstdc++ libc++

std :: sort的libcxx(llvm版本的c ++标准 library)用相同的元素调用比较谓词,即 比较函子的两个参数都指向相同的位置 要排序的序列。一个简化的例子来说明这一点。

$ cat a.cc
#include <algorithm>
#include <vector>
#include <cassert>

int main(int argc, char** argv) {
  int size = 100;
  std::vector<int> v(size);

  // Elements in v are unique.
  for (int i = 0; i < size; ++i)
    v[i] = i;

  std::sort(v.begin(), v.end(),
            [&](int x, int y) { assert(x != y); return x < y; });

  return 0;
}

$ clang++ -std=c++11 -stdlib=libc++ a.cc -o a.out
$ ./a.out
a.out: a.cc:14: auto main(int, char **)::(anonymous class)::operator()(int, int) const: Assertion `x != y' failed.
./go.sh: line 5: 19447 Aborted                 (core dumped) ./a.out

与libstdc ++一起使用。

$ clang++ -std=c++11 -stdlib=libstdc++ a.cc -o a.out
$ ./a.out

可以用相同的元素调用比较函数。这不是多余的。

2 个答案:

答案 0 :(得分:2)

据推测,在标准库作者看来,做一个保证返回错误的测试要比不断检查相等的索引以及比较元素更快。这可能是因为pivot元素被用作循环标记。

当然允许以这种方式调用比较函数,并且不允许代码中的assert

答案 1 :(得分:2)

我可以就这个问题说一些权威,因为我是编写这段代码的人。

以下是您的示例中断言的比较:

https://github.com/llvm-mirror/libcxx/blob/master/include/algorithm#L3994-L3995

随着时间的推移,链接很可能会过时(指向错误的行),我也会在这里引用代码:

            // __m still guards upward moving __i
            while (__comp(*__i, *__m))
                ++__i;

这被称为“无人看守”的循环,因为没有检查迭代器__i在序列结束时运行,因为它是递增的。这样做的原因是因为这个算法的不变量是在这一点上知道__i <= __m(它也在这个引用之上的3行注释中)。

如果您查看此引号上方的代码,您会看到以下注释:

    // The search going up is known to be guarded but the search coming down isn't.
    // Prime the downward search with a guard.

因此,在我们达到这一点之前,完成了对序列的保护搜索。也就是说,这个测试:

if (__i == --__j)

在此测试找到较低的防护之后,算法然后跳转到无防护的循环,每次迭代只有一次测试,否则每次迭代会有两次测试(对迭代器的测试和对取消引用的值的测试)迭代器)。

使用“无人看守的循环”是元素与自身进行比较的原因。在开发期间,我测量了循环中一次额外比较的成本比在循环中每次迭代包含两次比较更好。

当然这是一项工程权衡。如果与比较迭代器本身的成本相比,比较函数变得非常昂贵,那么可能会得出不同的结论。

这个答案与rici's answer完全一致,这也是正确的(我已经赞成它)。我添加了我的声音,因为我可以放弃“大概”并指向算法中的特定代码行。