我已经写了两个版本的nqueens问题,我认为他们应该有类似的效率,但事实并非如此。我认为这是由于Haskell的懒惰评估行为。有人可以解释它如何适用于以下示例,
nqueens1 n 1 = [[i] | i <- [1..n]]
nqueens1 n k = [ i:q | i <- [1..n], q <- (nqueens1 n (k - 1)), isSafe i q k]
isSafe i q n = isSafeHelper i (zip q [(n-1),(n-2)..1])
where isSafeHelper i [] = True
isSafeHelper i (x:xs) = (i /= fst x) && abs(i - (fst x)) /= abs(n - (snd x)) &&
isSafeHelper i xs
nqueens2 n 1 = [[i] | i <- [1..n]]
nqueens2 n k = [ i:q | i <- [1..n], q <- boards, isSafe i q k]
where boards = nqueens2 n (k-1)
您可以通过调用nqueens1 8 8或nqueens2 8 8来评估它们,以便为8号板进行评估。
虽然nqueens2非常有效,但nqueens1存在性能问题。我相信这是因为递归调用(nqueens n(k-1))被多次评估。根据我对Haskells懒惰评估的理解,情况应该不是这样。
请帮助我理解这种行为。
提前致谢。
答案 0 :(得分:10)
是的,递归调用会被多次评估。具体而言,对i
的每个值进行一次评估。
如果您想避免这种情况,可以重新排列生成器,以便q <-
部分位于i <-
部分之前:
[ i:q | q <- nqueens2 n (k - 1), i <- [1..n], isSafe i q k]
但是这会改变结果的顺序。如果这是不可接受的,你可以使用let
计算一次递归调用的结果,然后像这样使用它:
[ i:q | let rec = nqueens2 n (k - 1), i <- [1..n], q <- rec, isSafe i q k]