我正在寻找确定长值是否为完美平方(即其平方根是另一个整数)的最快方法:
我使用内置的Math.sqrt()以简单的方式完成了这项工作函数,但我想知道是否有一种方法可以通过将自己限制为仅限整数的域。维护查找表是不切实际的(因为平方小于263的231.5个整数)。
下面是我现在做的非常简单明了的方法:
public final static boolean isPerfectSquare(long n)
{
if (n < 0)
return false;
long tst = (long)(Math.sqrt(n) + 0.5);
return tst*tst == n;
}
注意:我在许多Project Euler问题中都使用了这个函数。因此,其他人将永远不必维护此代码。而这种微优化实际上可能会有所不同,因为挑战的一部分是在不到一分钟的时间内完成每一个算法,而在某些问题中,这个函数需要调用数百万次。
我尝试了不同的解决方案:
经过详尽的测试,我发现不需要在Math.sqrt()的结果上加0.5,至少在我的机器上是这样。快速平方根逆运算速度更快,但对于n>=410881,它给出了错误的结果。然而,正如BobbyShaftoe所建议的,我们可以在n<410881时使用FISR黑客。牛顿的方法比Math.sqrt()慢得多。这可能是因为Math.sqr()使用了类似于牛顿方法的东西,但在硬件中实现,所以比Java快得多。此外,牛顿法仍然需要使用双精度。一个经过修改的牛顿方法使用了一些技巧,因此只涉及整数数学,需要一些技巧来避免溢出(我希望这个函数可以处理所有64位有符号的正整数),而且它仍然比math.sqrt()慢。二元斩更慢。这是有意义的,因为二进制斩波平均需要16次才能找到64位数字的平方根。根据John的测试,在C++中使用or语句比使用switch更快,但在Java和C#中,or和switch之间似乎没有区别。我还尝试创建一个查找表(作为64个布尔值的私有静态数组)。然后,我只说if(lookup[(int)(n&0x3F)]){test}else return false;,而不是switch或or语句;。令我惊讶的是,这(只是稍微)慢了一些。这是因为在Java中检查数组边界。
为了记录在案,另一种方法是使用素分解。如果分解的每个因子都是偶数,那么这个数就是一个完美的平方。所以你想要的是看看一个数是否可以分解成质数平方的乘积。当然,你不需要获得这样的分解,只是为了看看它是否存在。
首先建立一个小于2^32的素数平方表。这远远小于一个包含所有整数的表,直到这个极限。
解决方案如下:
boolean isPerfectSquare(long number)
{
if (number < 0) return false;
if (number < 2) return true;
for (int i = 0; ; i++)
{
long square = squareTable[i];
if (square > number) return false;
while (number % square == 0)
{
number /= square;
}
if (number == 1) return true;
}
}
我想这有点神秘。它所做的是在每一步中检查质数的平方除以输入数。如果这样做了,那么它将尽可能地将数字除以平方,以从素数分解中删除这个平方。如果通过这个过程,我们得到1,那么输入数是素数平方的分解。如果平方比数字本身大,那么这个平方或任何更大的平方都无法分割它,所以数字不能是素数平方的分解。
考虑到现在的sqrt是在硬件中完成的,并且需要在这里计算素数,我想这个解决方案要慢得多。但正如mrzl在他的回答中所说,它应该比sqrt的解决方案给出更好的结果,sqrt的工作时间不会超过2^54。
如果你做了一个二进制斩试图找到“正确”的平方根,你可以很容易地检测到你得到的值是否足够接近:
(n+1)^2 = n^2 + 2n + 1
(n-1)^2 = n^2 - 2n + 1
因此,在计算了n^2之后,选项如下:
n ^2=目标:已完成,返回truen^2+2n+1>target>n^2:你很接近,但并不完美:return falsen^2-2n+1<目标<n^2:同上目标<n^2-2n+1:低位n上的二进制斩波目标>n^2+2n+1:较高n上的二进制斩波
(抱歉,这使用n作为您当前的猜测,并将其作为参数的目标。对此感到困惑深表歉意!)
我不知道这是否会更快,但值得一试。
编辑:二进制斩不必接受整个整数范围,或者(2^x)^2=2^(2x),所以一旦你在目标中找到了最高位(这可以用一个小技巧来完成;我完全忘记了怎么做),你就可以快速得到一系列可能的答案。请注意,一个简单的二进制斩仍然只需要31或32次迭代。