SICP包含一个部分完整的n-queens解决方案示例,通过在最后一行中移动每个可能的皇后位置的树,在下一行中生成更多可能的位置以组合到目前为止的结果,过滤保留的可能性只有最新的女王是安全的,并递归重复。
这个策略在大约n = 11之后爆发,最大递归误差。
我已经实现了一个替代策略,它从第一列执行更智能的树步行,从未使用的行列表生成可能的位置,将每个位置列表放到尚未使用的行的更新列表中。过滤那些被认为是安全的对,并递归映射这些对以用于下一列。这不会爆炸(到目前为止),但n = 12需要一分钟,n = 13需要大约10分钟才能解决。
(define (queens board-size)
(let loop ((k 1) (pp-pair (cons '() (enumerate-interval 1 board-size))))
(let ((position (car pp-pair))
(potential-rows (cdr pp-pair)))
(if (> k board-size)
(list position)
(flatmap (lambda (pp-pair) (loop (++ k) pp-pair))
(filter (lambda (pp-pair) (safe? k (car pp-pair))) ;keep only safe
(map (lambda (new-row)
(cons (adjoin-position new-row k position)
(remove-row new-row potential-rows))) ;make pp-pair
potential-rows)))))))
;auxiliary functions not listed
不是真正寻找代码,而是对一两个策略的简单解释,这些策略不那么天真,并且通过功能方法可以很好地点击。
答案 0 :(得分:3)
我可以为您提供代码的简化,因此可以更快地运行。我们首先重命名一些变量以提高可读性(YMMV),
(define (queens board-size)
(let loop ((k 1) (pd (cons () (enumerate-interval 1 board-size))))
(let ((position (car pd))
(domain (cdr pd)))
(if (> k board-size)
(list position)
(flatmap (lambda (pd) (loop (1+ k) pd))
(filter (lambda (pd) (safe? k (car pd))) ;keep only safe NewPositions
(map (lambda (row)
(cons (adjoin-position row k position) ;NewPosition
(remove-row row domain))) ;make new PD for each Row in D
domain))))))) ; D
现在, filter f (map g d) == flatmap (\x->let y=g x in [y|f y]) d
(在那里使用了一些Haskell语法),即我们可以融合 {{1}将map
合并为一个filter
:
flatmap
然后, (flatmap (lambda (pd) (loop (1+ k) pd))
(flatmap (lambda (row) ;keep only safe NewPositions
(let ( (p (adjoin-position row k position))
(d (remove-row row domain)))
(if (safe? k p) (list (cons p d)) ())))
domain))
(其中flatmap h (flatmap g d) == flatmap (h <=< g) d
是从右到左的Kleisli组合运算符,但谁在乎),所以我们可以融合两个<=<
只有一个,
flatmap
所以简化的代码是
(flatmap
(lambda (row) ;keep only safe NewPositions
(let ((p (adjoin-position row k position)))
(if (safe? k p)
(loop (1+ k) (cons p (remove-row row domain)))
())))
domain)
答案 1 :(得分:1)
这是我第二次想出来的。不确定它的速度要快得多。虽然相当漂亮。
(define (n-queens n)
(let loop ((k 1) (r 1) (dangers (starting-dangers n)) (res '()) (solutions '()))
(cond ((> k n) (cons res solutions))
((> r n) solutions)
((safe? r k dangers)
(let ((this (loop (+ k 1) 1 (update-dangers r k dangers)
(cons (cons r k) res) solutions)))
(loop k (+ r 1) dangers res this)))
(else (loop k (+ r 1) dangers res solutions)))))
重要的是使用let语句来序列化递归,将深度限制为n。解决方案向后出现(可能通过在r和k上转n-> 1而不是1&gt; n来修复)但是向后设置与frowards设置相同。
(define (starting-dangers n)
(list (list)
(list (- n))
(list (+ (* 2 n) 1))))
;;instead of terminating in null list, terminate in term that cant threaten
小的改进,危险可能来自一排,一条向下的对角线,或一条向上的对角线,随着电路板的发展跟踪每一条。
(define (safe? r k dangers)
(and (let loop ((rdangers (rdang dangers)))
(cond ((null? rdangers) #t)
((= r (car rdangers))
#f)
(else (loop (cdr rdangers)))))
(let ((ddiag (- k r)))
(let loop ((ddangers (ddang dangers)))
(if (<= (car ddangers) ddiag)
(if (= (car ddangers) ddiag)
#f
#t)
(loop (cdr ddangers)))))
(let ((udiag (+ k r)))
(let loop ((udangers (udang dangers)))
(if (>= (car udangers) udiag)
(if (= (car udangers) udiag)
#f
#t)
(loop (cdr udangers)))))))
格式变化的中等改进,只需要进行一次比较以检查与之前的两种。不要认为keeiping diagonals排序花了我任何东西,但我不认为它节省时间。
(define (update-dangers r k dangers)
(list
(cons r (rdang dangers))
(insert (- k r) (ddang dangers) >)
(insert (+ k r) (udang dangers) <)))
(define (insert x sL pred)
(let loop ((L sL))
(cond ((null? L) (list x))
((pred x (car L))
(cons x L))
(else (cons (car L)
(loop (cdr L)))))))
(define (rdang dangers)
(car dangers))
(define (ddang dangers)
(cadr dangers))
(define (udang dangers)
(caddr dangers))