2.42

练习 2.42 “八皇后谜题”问的是怎样将八个皇后摆在国际象棋盘上,使得任意一个皇后都不能攻击另一个皇后(也就是说,任意两个皇后都不在同一行、同一列或者同一对角线上)。一个可能的解如图2-8所示。解决这一谜题的一种方法按一个方向处理棋盘,每次在第一列里放一个皇后。如果现在已经放好了k-1个皇后,第k个皇后就必须放在不会被已民在棋盘上的任何皇后攻击的位置上。我们可以递归地描述这一过程:假定我们已经生成了在棋盘的前k-1列中放置k-1个皇后的所有可能方式,现在需要的就是对于其中的每种方式,生成出将下一个皇后放在第k列中每一行的扩充集合。而后过滤它们,只留下能使位于第k列的皇后与其他皇后相安无事的那些扩充。这样就能产生出将k个皇后放置在前k列的所有格局的序列,继续这一过程,我们将能产生出这一谜题的所有解,而不是一个解。

将这一解法实现为一个过程queens,令它返回在n x n棋盘上放n个皇后的所有解的序列。queens内部的过程queen-cols,返回在棋盘的前k列中放皇后的所有格局的序列。

(define (queens board-size)
    (define (queen-cols k)
        (if (= k 0)
            (list empty-board)
            (filter
                (lambda (positions) (safe? k positions))
                (flatmap
                    (lambda (rest-of-queens)
                        (map (lambda (new-row)
                            (adjoin-position new-row k rest-of-queens))
                        (enumerate-interval 1 board-size)))
                    (queen-cols (- k 1))))))
    (queen-cols board-size))

这个过程里的 rest-of-queens 是在前k-1列放置k-1个皇后的一种方式,new-row是在第k列放置所考虑的行编号。请完成这一程序,为此需要实现一种棋盘格局集合的表示方式;还要实现过程adjoin-position,它将一个新的行列格局加入一个格局集合;empty-board,它表示空的格局集合。你还需要写出过程safe?,它能确定在一个格局中,在第k列的皇后相对于其他列的皇后是否为安全的(请注意,我们只需要检查新皇后是否安全——其他皇后已经保证相安无事了)。


首先,需要实现一种棋盘格局集合的表示方式。如同矩阵一样,采用嵌套 list 来表示,并且用 1 表示有皇后,用 0 表示没有皇后。比如,对于图2-8的图,对于的表示如下:

(define eight-by-eight
    (list
        (list 0 0 0 0 0 1 0 0)
        (list 0 0 1 0 0 0 0 0) 
        (list 1 0 0 0 0 0 0 0)
        (list 0 0 0 0 0 0 1 0)
        (list 0 0 0 0 1 0 0 0)
        (list 0 0 0 0 0 0 0 1)
        (list 0 1 0 0 0 0 0 0)
        (list 0 0 0 1 0 0 0 0)
    )
)

然后,定义 accumulate

(define (accumulate op initial sequence)
    (if (null? sequence)
        initial
        (op (car sequence)
            (accumulate op initial (cdr sequence))) 
    )
)

对于棋盘上已经放置了 k-1 个皇后的前 k-1 个列,再在第 k 列的 new-row 处再置一个皇后,就是将该位置的元素设置为 1:

(define (enumerate-interval low high)
    (if (> low high)
        '()
        (cons low (enumerate-interval (+ low 1) high))))
(define (adjoin-position new-row k rest-of-queens)
    (map
        (lambda (i) 
            (map
                (lambda (j) 
                    (if (and (= new-row i) (= k j)) 
                        1
                        (list-ref (list-ref rest-of-queens (- i 1)) (- j 1)) 
                    )
                )
                (enumerate-interval 1 (max 1 (length rest-of-queens)))
            )
        )
        (enumerate-interval 1 (max 1 (length rest-of-queens)))
    )
)

(adjoin-position 8 8 eight-by-eight)

空的格局集合可以定义为全0的 list:

(define 
    (make-empty-board board-size)
    (map
        (lambda (i) 
            (map 
                (lambda (j) 0)
                (enumerate-interval 1 board-size)
            )
        )
        (enumerate-interval 1 board-size)
    )
)

(make-empty-board 2)

要定义 safe?,可以考虑对第 k 列,看其按列的和是否不大于1,然后看相关行的和是否不大于1,以及两个对角线方向的和是否均不大于1。

由于是向最后的一列再添加一个皇后,因此列上的检查可以省略。所以要检查的是所有行,以及左上对角所有位置和左下对角所有位置。其他位置已经不用检查了。

(define (row-safe? positions)
    (=  1 
        (accumulate * 1 
            (map
                (lambda (row) 
                    (if (<= (accumulate + 0 row) 1) 
                        1 
                        0
                    )
                )
                positions
            )
        )
    )
)

(row-safe? eight-by-eight)
(define (get-element positions i j)
    (list-ref 
        (list-ref positions (- i 1))
        (- j 1))
)

(get-element eight-by-eight 3 3 )
(define (upper-left-safe? k positions)
    (accumulate (lambda (x y) (and x y)) #t (map
        (lambda (i) 
            (<= (accumulate + 0 (map
                (lambda (j)
                    (get-element positions (- (+ i j) k) j)
                )
                (reverse 
                    (enumerate-interval 
                        (max 
                            1 
                            (+ (- k i) 1)
                        ) 
                        k
                    )
                ) 
            )) 1 )
        )

        (enumerate-interval 1 (length positions))
    )) 
)  

(upper-left-safe? 2 eight-by-eight)
(define (lower-left-safe? k positions)
    (accumulate (lambda (x y) (and x y)) #t (map
        (lambda (i) 
            (<= (accumulate + 0 (map
                (lambda (j) (get-element positions (+ i (- k j)) j))  
                (reverse (enumerate-interval 
                    (max 1 (- k (- (length positions) i))) 
                    k  
                ))
            )) 1)
        )
        (enumerate-interval 1 (length positions))
    ))
)

(lower-left-safe? 3 eight-by-eight)

safe? 定义如下:

(define (safe? k positions)
    (and 
        (row-safe? positions)
        (upper-left-safe? k positions)
        (lower-left-safe? k positions)
    )
)

(safe? 8 eight-by-eight)

最后,定义一下 queens

(define (flatmap proc seq)
    (accumulate append '()  (map proc seq)))

(define (queens board-size)
    (define empty-board 
        (make-empty-board board-size)
    )

    (define (queen-cols k)
        (if (= k 0)
            (list empty-board)
            (filter
                (lambda (positions) (safe? k positions))
                (flatmap
                    (lambda (rest-of-queens)
                        (map (lambda (new-row)
                            (adjoin-position new-row k rest-of-queens))
                        (enumerate-interval 1 board-size)))
                    (queen-cols (- k 1))))))
    (queen-cols board-size))

(queens 5)

results matching ""

    No results matching ""