【发布时间】:2014-01-26 18:00:16
【问题描述】:
这是C++入门第五版问题3.26的一个问题,我不知道它们之间的区别? 可能是第二个可以避免溢出。
【问题讨论】:
-
它甚至发生在最好的情况下:Jon Bentley 在 Programming Pearls 中的二分搜索包含溢出错误。
标签: c++ algorithm binary-search integer-overflow
这是C++入门第五版问题3.26的一个问题,我不知道它们之间的区别? 可能是第二个可以避免溢出。
【问题讨论】:
标签: c++ algorithm binary-search integer-overflow
可能是第二个可以避免溢出。
没错。不能保证beg+end 是可表示的;但在第二种情况下,中间值以及预期结果不大于end,因此没有溢出的危险。
第二种形式也可用于仿射类型,如指针和其他随机访问迭代器,它们可以相减得到距离,但不能相加。
【讨论】:
一般情况下,这两个表达式都是无效的。例如,第一个表达式是无效的,因为没有针对指针或迭代器的 + 这样的操作。 如果使用非随机访问迭代器,则第二个表达式无效。例如,当使用双向迭代器时。
所以 C++ 中的正确构造如下所示
mid = std::next( beg, std::distance( beg, end ) / 2 );
【讨论】:
如果我们在更通用的设置中考虑这两行,与二分搜索无关,则可以进行以下观察:
您是正确的,第二种形式试图避免的问题是溢出,试图表示一个大于最大可表示数的数字。
对于单个数字 beg 和 end 的大小没有限制,因此它们可能都大于最大可表示数字的一半。添加它们意味着中间结果(beg+end)可以溢出。
第二种解决方案似乎消除了溢出的风险,但引入了另一种解决方案。如果这些值是有符号值,则它们的差值可能会再次上溢(或下溢,具体取决于它们的符号)。无符号值没有问题。
还有一个您没有发布的解决方案:
mid = beg/2 + end/2
这解决了上溢和下溢的所有问题,但引入了一个新问题,即精度损失。如果使用整数值,除以 2 可以得到 0.5 的结果,将它们相加意味着 mid 可以被 1 关闭:
mid = 3/2 + 5/2; // mid is 3, instead of the 4 expected
使用浮点值还有其他精度问题。
回到手头的问题,二分查找,很容易看出 beg 和 end 是无符号值,所以第二个解决方案总是会给出正确的结果。
【讨论】:
答案在书中:
"因为从end返回的迭代器不代表一个元素,它可能 不能增加或取消引用。”
从图形上看,它是一个不对称的范围, [开始,结束) 或半开范围。
来自 Accelerated C++,第 28 页,Koenig。
【讨论】: