最有效的算法,用于在椭圆内找到整数点

时间:2015-08-20 14:40:18

标签: algorithm haskell

我试图找到各种3D椭圆内的所有整数格点。

我希望我的程序取一个整数N,并计算形式ax ^ 2 +的椭圆内的所有格点由^ 2 + cz ^ 2 = n,其中a,b,c是固定整数和n介于1和N之间。此程序应返回格式的N个元组(n,numlatticePointsWithinEllipse n)。

我目前正在通过计算椭圆上的点ax ^ 2 +乘以^ 2 + cz ^ 2 = m,对于介于0和n之间的m,然后在m上求和。我最初也只是看x,y和z都是正面的,然后通过置换它们的符号来加入底片。

理想情况下,我希望在数小时内达到N = 1,000,000+的数量

以x ^ 2 + y ^ 2 + 3z ^ 2 = N为例,这里是我目前使用的Haskell代码:

import System.Environment

isqrt :: Int -> Int
isqrt 0 = 0
isqrt 1 = 1
isqrt n = head $ dropWhile (\x -> x*x > n) $ iterate (\x -> (x + n `div` x) `div` 2) (n `div` 2)

latticePointsWithoutNegatives :: Int -> [[Int]]
latticePointsWithoutNegatives 0 = [[0,0,0]]
latticePointsWithoutNegatives n = [[x,y,z] | x<-[0.. isqrt n], y<- [0.. isqrt (n - x^2)], z<-[max 0 (isqrt ((n-x^2 -y^2) `div` 3))], x^2 +y^2 + z^2 ==n]

latticePoints :: Int -> [[Int]]
latticePoints n = [ zipWith (*) [x1,x2,x3] y |  [x1,x2,x3] <- (latticePointsWithoutNegatives n), y <- [[a,b,c] | a <- (if x1 == 0 then [0] else [-1,1]), b<-(if x2 == 0 then [0] else [-1,1]), c<-(if x3 == 0 then [0] else [-1,1])]]

latticePointsUpTo :: Int -> Int
latticePointsUpTo n = sum [length (latticePoints x) | x<-[0..n]]

listResults :: Int -> [(Int, Int)]
listResults n = [(x, latticePointsUpTo x) | x<- [1..n]]

main = do
    args <- getArgs
    let cleanArgs = read (head args)
    print (listResults cleanArgs)

我用

编译了这个
ghc -O2 latticePointsTest

但使用PowerShell&#34; Measure-Command&#34;命令,我得到以下结果:

Measure-Command{./latticePointsTest 10}
TotalMilliseconds : 12.0901

Measure-Command{./latticePointsTest 100}
TotalMilliseconds : 12.0901

 Measure-Command{./latticePointsTest 1000}
TotalMilliseconds : 31120.4503

再向上几个数量级会使我们达到天数,而不是几小时或几分钟。

我使用的算法有什么根本原因吗?我的代码不能很好地扩展,有什么核心原因吗?任何指导将不胜感激。我可能还想在&#34; latticePoints&#34;之间处理数据。和&#34; latticePointsUpTo&#34;,所以我不能完全依赖聪明的数字理论计数技术 - 我需要保留基础元组。

1 个答案:

答案 0 :(得分:4)

我会尝试一些事情:

isqrt对您正在工作的值范围无效。只需使用浮点sqrt函数:

isqrt = floor $ sqrt ((fromIntegral n) :: Double)

或者,不要计算整数平方根,而是在列表推导中使用这样的逻辑:

x <- takeWhile (\x -> x*x <= n) [0..],
y <- takeWhile (\y -> y*y <= n - x*x) [0..]

另外,我会使用x*x之类的表达式而不是x^2

最后,为什么不用这样的方式计算解决方案的数量:

sols a b c n =
  length [ () | x <- takeWhile (\x -> a*x*x <= n) [0..]
              , y <- takeWhile (\y -> a*x*x+b*y*y <= n) [0..]
              , z <- takeWhile (\z -> a*x*x+b*y*y+c*z*z <= n) [0..]
         ]

这并不能完全计算出您想要的相同答案,因为它没有考虑正面和负面解决方案,但您可以轻松修改它以计算您的答案。我们的想法是使用一个列表理解而不是迭代n的各种值并求和。

最后,我认为使用floorsqrt来计算积分平方根在这种情况下是完全安全的。此代码通过对{x * x)== x的所有x <= 3037000499:

sqrt来验证整数平方根
testAll :: Int -> IO ()
testAll n =
  print $ head [ (x,a) | x <- [n,n-1 .. 1], let a = floor $ sqrt (fromIntegral (x*x) :: Double), a /= x ]

main = testAll 3037000499

注意我在64位GHC上运行它 - 否则只使用Int64而不是Int,因为在任何一种情况下双打都是64位。只需一分钟左右来验证。

这表明如果y <= 3037000499 ^ 2,取得sqrt y的最低限度将永远不会得到错误答案。