【问题标题】:Does C++ qsort ever compare an element with itself?C++ qsort 是否曾经将元素与自身进行比较?
【发布时间】:2018-02-21 07:30:06
【问题描述】:

我需要使用 qsort 对数组进行稳定排序。为了保证结果稳定,我在比较函数中增加了一个条件:

int compare(const void *p1, const void *p2)
{
    if(*(const Data*)p1 < *(const Data*)p2)
        return -1;
    if(*(const Data*)p2 < *(const Data*)p1)
        return 1;
    else
        return p1<p2 ? -1 : 1;
}

如果 qsort 从不调用 compare(p,p),这将起作用。否则我需要使用更复杂的条件。问题是,qsort 是否曾经使用重复指针调用 compare(),还是总是比较不同的指针?

更新:

我用 Ideone C++ 编译器检查了这个:https://ideone.com/l026kM

对于 cmets 中的小示例 { 8, 8, 1, 1 },提供的 qsort() 实现不会更改指针的顺序,也不会为同一元素调用 compare。这似乎是合理的,因为每次反向交换都会影响性能,因为它需要稍后再交换回来。我将使用随机生成的数组和不同的编译器对此进行测试。

更新:

在 Ideone 上针对 100000 个随机数组进行测试,其中重复键的份额至少为 80%。结果是 100% 稳定的排序数组。这是链接:https://ideone.com/KOYbgJ

VC++Express 2008 编译器失败 稳定排序,因为指针的顺序改变了。这基本上说明了 VC++ 实现与 GCC 实现的不同之处在于它不保持指针顺序。

【问题讨论】:

  • 听说过std::stable_sort吗?
  • 你为什么还要在所谓的 C++ 程序中使用 C 库的 qsort?
  • 顺便说一句,我认为这是一个 XY 问题。比较器的属性不影响排序算法的稳定性保证。
  • @bkxp 学习一些关于排序的理论。如果元素的排序键取决于它们的位置,则无法排序。
  • @bkxp 当然可以。它对性能意味着什么?没有。原始复制和赋值都可能导致完全相同的机器代码。

标签: c++ qsort


【解决方案1】:

任何没有明确禁止的事情都是隐式允许的;特别是,我记得调试版本中的 VC++ 在实际执行std::sort 之前明确测试了相同元素上的比较器(以及其他健全性测试)。不知道qsort是不是也这样,不过是允许的。

但最重要的是,您的比较器违反了qsort 规定的要求;特别是:

当相同的对象(由size 字节组成,无论它们在数组中的当前位置如何)多次传递给比较函数时,结果应相互一致。也就是说,对于qsort,它们应在数组上定义一个总排序,对于bsearch,相同的对象应始终以相同的方式与键进行比较。

(C99,§7.20.5 ¶4,强调)

最后,正如 Daniel Langr 所述,即使采用“宽容”的实施方式,也不一定能实现您所追求的目标。

也就是说:扔掉这些杂物,使用真正稳定的排序算法,库已经提供了它(std::stable_sort)。


此外,由于 qsort 避免了赋值运算符,这件事的重点似乎是比 std::stable_sortstd::sort 更快地排序:

顺便说一句,qsort 对于便宜的比较器通常比std::sort 慢,因为它需要对每个比较进行间接调用,而在std::sort 中,仿函数被内联。 并且复制通常也更慢,因为std::sort 必须使用memcpy(必须调用它,然后在运行时确定大小并相应地复制),而内联赋值被内联(同样,它更便宜如果您的元素很小,否则几乎相同,因为普通可分配类型的合成分配/副本通常归结为memcpy

(chat link)

【讨论】:

  • 但是我仍然可以比较元素的原始索引,如果它们包含在元素值中,对吧?这不是杂乱无章的事情,必须确保使用 qsort 进行稳定排序。
  • 是的,这是允许的,因为它的数据不依赖于容器中的当前位置。
【解决方案2】:

如果 qsort 从不调用 compare(p,p),这将起作用。

不,这不能保证快速排序的稳定性。如果有就更好了:)


考虑 (8,8,1,1) 相对于枢轴 5 的分区。首先,将外部 8 与外部 1 交换,然后将内部 8 与内部 1 交换。之后 1 和 8 的顺序都发生了变化在没有比较具有相同键的元素的情况下进行分区。


可以更明确地写成如下:

partition(8_left, 8_right, 1_left, 1_right) -&gt; (1_right, 1_left, 8_right, 8_left)

【讨论】:

  • 为什么不呢?如果两个元素相等,则优先选择最早的元素。
  • @bkxp 如果你所有的元素都有不同的键,那么不要关心稳定性。当排序的数据包含重复键时,稳定性很重要。
  • 此外,它打破了严格的弱排序,导致未定义的行为,因为两个等效元素之间的排序取决于它们当前在数组中的位置而不是它们的值。对于符合标准的&lt;,在交换它们之前和之后都有 a[0]
  • @bkxp 当然,整个分区,即快速排序是基于这样的交换。
  • @bkxp 不,不会。你读过我的回答吗?交换将较小的元素放在较大的元素之前,并且稳定性仍然被破坏。那里没有比较指针,因为两次比较中的键不同。
猜你喜欢
  • 1970-01-01
  • 2013-01-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-11-04
  • 1970-01-01
  • 2023-01-19
  • 2021-12-17
相关资源
最近更新 更多