我正在寻找确定长值是否为完美平方(即其平方根是另一个整数)的最快方法:

我使用内置的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^64)行为结束。要真正做到快速,我们需要一种快速的方法来获得一个合理的初始值,这意味着我们需要进入机器语言。如果一个处理器在奔腾中提供了一个像POPCNT这样的指令,它对前导零进行计数,我们可以使用它来获得一个具有一半有效位的起始值。小心地,我们可以找到一个固定数量的牛顿步数,这将总是足够的。(因此,前面提到了需要循环并具有非常快的执行。)

第二种解决方案是通过浮点设备,它可能具有快速的sqrt计算(如i87协处理器)。即使通过exp()和log()进行偏移,也可能比牛顿退化为二进制搜索更快。这有一个棘手的方面,即依赖于处理器的分析,以确定后续是否需要改进。

第三种解决方案解决了一个稍有不同的问题,但很值得一提,因为问题中描述了情况。如果你想为稍有不同的数字计算很多平方根,你可以使用牛顿迭代,如果你从来没有重新初始化起始值,但只需将其保留在之前的计算停止的地方。我已经在至少一个欧拉问题中成功地使用了这一方法。

其他回答

不知道最快,但最简单的方法是以正常方式取平方根,将结果乘以自身,看看它是否与原始值匹配。

由于我们在这里讨论的是整数,fasted可能涉及一个集合,您可以在其中进行查找。

可能是该问题的最佳算法是快速整数平方根算法https://stackoverflow.com/a/51585204/5191852

@Kde声称牛顿法的三次迭代对于32位整数的精度为±1就足够了。当然,64位整数需要更多的迭代,可能是6或7。

如果你想要速度,考虑到整数的大小是有限的,我想最快的方法是(a)按大小划分参数(例如,按最大位集划分类别),然后对照该范围内的完美平方数组检查值。

static boolean isPerfectSquare (int input) {
  return Math.sqrt(input) == (int) Math.sqrt(input);
}

如果输入的平方根的整数值等于双倍值,则返回该值。这意味着它是一个整数,它将返回true。否则,将返回false。

为了记录在案,另一种方法是使用素分解。如果分解的每个因子都是偶数,那么这个数就是一个完美的平方。所以你想要的是看看一个数是否可以分解成质数平方的乘积。当然,你不需要获得这样的分解,只是为了看看它是否存在。

首先建立一个小于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。