在二分查找等算法中,我们经常需要计算区间的中间位置,一般可以使用 mid = (left + right) / 2
这种常规写法,但在一些场景下会采用 mid = ((right - left) >> 1) + left
这样的写法,以下是具体原因:
避免整数溢出
在许多编程语言里,整数类型的取值范围是有限的。当 left
和 right
都非常大时,使用 (left + right) / 2
可能会导致整数溢出。
例如,在 JavaScript 中,Number
类型是双精度 64 位浮点数,虽然能表示很大的数,但如果 left
和 right
足够大,它们相加的结果可能会超出安全整数范围(Number.MAX_SAFE_INTEGER
,即 2**53 - 1
),从而造成精度丢失。
以下是一个示例代码,展示了溢出情况:
let left = Number.MAX_SAFE_INTEGER - 1;
let right = Number.MAX_SAFE_INTEGER;
// 一般写法会发生溢出问题
let mid1 = (left + right) / 2;
// 但是换成这个写法就不会溢出
let mid2 = ((right - left) >> 1) + left;
console.log(mid1);
console.log(mid2);
在上述代码中,(left + right) / 2
可能会得到错误的结果,而 ((right - left) >> 1) + left
能避免这个问题。因为 right - left
的值通常不会很大,不会超出整数表示范围。
位运算的性能优势
>>
是右移运算符,(right - left) >> 1
等价于 Math.floor((right - left) / 2)
。位运算在计算机底层的执行速度通常比除法运算要快,因为它直接操作二进制位,不需要进行复杂的数学运算。所以使用 ((right - left) >> 1) + left
不仅能避免溢出,还可能在性能上有一定提升。
综上所述,mid = ((right - left) >> 1) + left
这种写法既可以避免整数溢出问题,又可能提高代码的执行效率,因此在实际编程中被广泛应用于二分查找当中。