【问题标题】:Lexicographical ordering of multiple doubles多个双打的字典顺序
【发布时间】:2011-03-15 21:07:51
【问题描述】:

考虑一个双精度类型的类

class path_cost {
   double length;
   double time;
};

如果我想按字典顺序排列path_costs 的列表,我遇到了问题。继续阅读:)

如果我像这样在相等性测试中使用完全相等

bool operator<(const path_cost& rhs) const {
   if (length == rhs.length) return time < rhs.time;
   return length < rhs.length;
}

生成的顺序很可能是错误的,因为一个小的偏差(例如由于长度计算中的数值不准确)可能会导致长度测试失败,因此例如

{ 231.00000000000001, 40 } &lt; { 231.00000000000002, 10 }

错误地持有。

如果我也可以使用这样的容差

bool operator<(const path_cost& rhs) const {
   if (std::fabs(length-rhs.length)<1-e6)) return time < rhs.time;
   return length < rhs.length;
}

那么排序算法可能会严重失败,因为

有什么想法吗?解决方案?我考虑过对实线进行分区,以便每个分区内的数字被认为是相等的,但这仍然留下太多相等测试失败但不应该失败的情况。

(James Curran 更新,希望能解释问题): 给定数字:

  • A = {231.0000001200, 10}
  • B = {231.0000000500, 40}
  • C = {231.0000000100, 60}

    • A.Length 和 B.Length 相差 7-e7,因此我们使用时间,而 A
    • B.Length & C.Length 相差 4-e7,所以我们使用时间,B
    • A.Length 和 C.Length 相差 1.1-e6,因此我们使用长度,并且 A > C。

(由 Esben Mose Hansen 更新) 这不是纯粹的理论。当给定非传递排序运算符时,标准排序算法往往会崩溃或更糟。这正是我一直在努力解决的问题(男孩调试起来很有趣;))

【问题讨论】:

    标签: algorithm numeric


    【解决方案1】:

    我不熟悉您的应用程序,但我敢打赌,您的图表中点之间的距离差异比浮点数的舍入误差大很多数量级。因此,如果两个条目仅在舍入误差上有所不同,它们本质上是相同的,它们在列表中出现的顺序没有区别。从常识的角度来看,我认为没有理由担心。

    【讨论】:

    • 正如我上面写的,问题是当传递性失败时,常见的排序算法不再定义良好,就我而言,它们只是崩溃。
    • 如果您不尝试调整数字,就不会出现这种故障。如果它们始终偏离少量,它们将始终如一且正确地分类。浮点数没有什么神秘之处,它们在比较中不会改变它们的值。摘要:不要乱用数字,不会有传递性问题。
    • 我没有调整任何数字,所以这不是问题。我记得,问题是一个排序范围被推断为包含一个值,但实际上并没有。因此它最终跑出了范围的末端,并进入了未分配的内存=>崩溃。您会注意到,例如std::sort 要求排序运算符是“严格的弱排序”,这意味着传递性。
    【解决方案2】:

    普通的doubles 永远无法获得 100% 的精度。你说你害怕使用公差会影响你程序的正确性。你真的测试过这个吗?您的程序实际需要什么级别的精度?

    在最常见的应用程序中,我发现1e-9 之类的容差就足够了。当然,这一切都取决于您的应用程序。您可以估计所需的准确度水平,然后将容差设置为可接受的值。

    如果即使这样也失败了,这意味着double 根本不足以满足您的目的。这种情况不太可能发生,但如果您需要非常高精度的计算,就会出现这种情况。在这种情况下,您必须使用任意精度包(例如 Java 中的 BigDecimal 或 C 中的 GMP 之类的东西)。同样,只有在没有其他方法时才选择此选项。

    【讨论】:

    • 我试过了,还是失败了。问题是,在一定的概率下,传递性失败,应用程序段错误。我有一个解决方法(涉及在中间结果中使用 long double),但我想知道一般问题。当然,有人以前看过这个问题吗?公差与我们使用的非常接近。这些数字部分来自标签设置/A* 算法,部分来自硬币或 LP 解决方案。
    【解决方案3】:

    我不认为你将能够做你想做的事。本质上,您似乎是在说,在某些情况下,您想忽略 a>b 并假装 a=b 的事实。我很确定您可以构造一个证明,说明当差值小于某个值时 a 和 b 是否相等,那么 a 和 b 对于 a 和 b 的所有值都是等价的。大致如下:

    对于 C 和两个数字 A 和 B 的容差,其中不失一般性 A > B 则存在 D(n) = B+n*(C/10) 其中 0&lt;=n&lt;=(10*(A-B))/(C) 使得 D(n) 在 D(n-1) 的容差范围内和 D(n+1),因此等价于它们。另外 D(0) 是 B 并且 D((10*(A-B))/(C))=A 所以 A 和 B 可以说是等价的。

    我认为解决该问题的唯一方法是使用分区方法。像乘以 10^6 然后很好地转换为 int shoudl 分区,但这意味着如果您有 1.00001*10^-6 和 0.999999*10^-6 那么它们将出现在不同的分区中,这可能是不希望的.

    然后问题就变成了查看您的数据以找出如何最好地对其进行分区,这是我无能为力的,因为我对您的数据一无所知。 :)

    附:算法是否真的在给定算法时崩溃,或者只是在遇到特定的无法解决的情况时崩溃?

    【讨论】:

    • 排序算法非常非常偶然地崩溃 --- 我们只在一个数据集上遇到问题,并且只有在使用 -O2 编译时(这与 80 位内部表示协处理器,我们怀疑)。发生崩溃是因为算法在由于排序而本不应该出现的情况下超出了列表的一端。
    • 这是有道理的。我想知道是否有一些魔法使它能够以某种方式测试比较功能,看看它是否可行。如果它这样做,我会印象深刻的。 ;-) 感谢您提出一个有趣的问题。我以前真的没有想过这种事情。 :)
    【解决方案4】:

    我能想到两种解决方案。

    您可以谨慎选择在比较不传递时不会失败的排序算法。例如,快速排序不应该失败,至少如果你自己实现它。 (如果您担心快速排序的最坏情况,您可以先随机化列表,然后对其进行排序。)

    或者您可以扩展您的公差补丁,使其成为等价关系并恢复传递性。有标准的union-find algorithms 来完成与等价关系的任何关系。应用 union-find 后,您可以将每个等价类中的长度替换为一致值(例如平均值),然后进行您想做的排序。修改浮点数以防止虚假的重新排序感觉有点奇怪,但它应该可以工作。


    实际上,Moron 说得很好。您可以先按长度排序,而不是联合和查找,然后将公差范围内的邻居链接在一起,然后在第二个键上的每个组内进行子排序。这与我的第二个建议的结果相同,但实现起来更简单。

    【讨论】:

      【解决方案5】:

      你真的只想要一个比较函数吗?

      您为什么不先按长度排序,然后将这些对分组为您认为相同的长度,然后在每个组中按时间排序?

      一旦按长度排序,您就可以应用所需的任何启发式方法来确定长度的“相等性”并进行分组。

      【讨论】:

      • 这其实也不错,很简单。这需要一些工作才能做到恰到好处,但绝对有可能做到。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-04-10
      • 1970-01-01
      • 1970-01-01
      • 2021-06-11
      • 1970-01-01
      • 2021-06-24
      相关资源
      最近更新 更多