骑士旅游回溯Lisp

时间:2016-02-23 17:27:45

标签: lisp backtracking

我在为此程序生成正确的输出时遇到一些问题。我的输出几乎是正确的,但缺少一些步骤。我的代码如下:

(defun kt (x y m n)                       ;set the board
  (setq totalmoves (* m n))     ;find the total moves
  (setq steps 1)                         ;count steps
  (setq lst (list (list x y)))  ;list visited with initial points
  (findPath x y totalmoves steps m n lst)       ;start tour with initial points
)

(defun findPath (x y totalMoves steps m n lst)
  (cond
  ((null lst) NIL)
  ((= steps totalMoves) lst)                ;if steps equals than total moves, then solution is complete
  ;try to solve the rest recursively
  ;1- down and right
  ((canMove (+ x 2) (+ y 1) m n lst)
    (findPath (+ x 2) (+ y 1) totalMoves (+ steps 1) m n (appendList (+ x 2)        (+ y 1) lst))

)
;2- right and down
((canMove (+ x 1) (+ y 2) m n lst)
     (findPath (+ x 1) (+ y 2) totalMoves (+ steps 1) m n (appendList (+ x 1) (+ y 2) lst))

)
;3- right ups
((canMove (- x 1) (+ y 2) m n lst)
     (findPath (- x 1) (+ y 2) totalMoves (+ steps 1) m n (appendList(- x 1) (+ y 2) lst))

)
;4- up and right
((canMove(- x 2) (+ y 1) m n lst)
     (findPath(- x 2) (+ y 1) totalMoves (+ steps 1) m n (appendList(- x 2) (+ y 1) lst))
)
;5 - up and left
((canMove(- x 2) (- y 1) m n lst)
     (findPath(- x 2) (- y 1) totalMoves (+ steps 1) m n (appendList(- x 2) (- y 1) lst))
)
;6- left and up 
((canMove(- x 1) (- y 2) m n lst)
     (findPath(- x 1) (- y 2) totalMoves (+ steps 1) m n (appendList(- x 1) (- y 2) lst))
)
;7- left and down
((canMove(+ x 1) (- y 2) m n lst)
     (findPath(+ x 1) (- y 2) totalMoves (+ steps 1) m n (appendList(+ x 1) (- y 2) lst))
)
;8- down and left
((canMove(+ x 2) (- y 1) m n lst)
     (findPath(+ x 2) (- y 1) totalMoves (+ steps 1) m n (appendList(+ x 2) (- y 1) lst))
)
(t 
 (findPath (car(car(reverse lst))) (car(cdr(car(reverse lst)))) totalMoves steps m n (reverse(cdr (reverse lst))))
  )
)
)

(defun appendList (x y lst)
  (setq lst (reverse(append (list (list x y)) (reverse lst))))
)

(defun check-visited (x y lst)
  (cond
    ((null lst) 1)              ;if nth else to check in list
    ((equal (list x y) (car lst)) NIL)    ;check if curr is visited
    ((check-visited x y (cdr lst)))        ;recurse
  )
)

(defun canMove (x y m n lst)
  (if (and (<= 1 x)         ;for the correct state, all conditions below must be met          
       (<= x m)    ;height is more than or equal to x
       (<= 1 y)
       (<= y n)     ;width is more than or equal to y
       (equal (check-visited x y lst) 1)
  ) 
  1 NIL ;if all above conds are true, return true else return nil
)
)

测试代码是

kt 1 1 5 5

输出为((1 1) (3 2) (5 3) (4 5) (2 4) (1 2) (3 3) (5 4) (3 5) (1 4) (2 2) (4 3) (5 5) (3 4) (1 5) (2 3) (4 4) (2 5) (1 3) (2 1) (4 2))

此处列出了21个步骤,但它应该有25个。

1 个答案:

答案 0 :(得分:1)

您的方法不正确,因为函数findPath没有尝试某个位置的所有可能移动,但是,使用cond,只尝试第一个可能的移动(在cond中,执行第一个非nil分支,并通过返回对应findPath调用的值来终止语句。所以,你的函数只产生最长的巡回演出而没有回溯,这恰好是21次移动。

为了获得正确的解决方案,你必须尝试所有可能的移动,返回第一个,通过递归调用findPath,产生正确数量的移动。在Common Lisp中,可以使用orand运算符来完成:

  1. or,带有 n 操作数,返回第一个操作数的值nil,如果存在,否则返回nil :所以如果我们安排将or的所有递归调用放入findPath,如果其中一个返回正确的最终值,则or表达式终止返回该值;

  2. and如果其任何操作数为nil则返回nil,否则,如果所有操作数均不是nil,则返回最后一个值操作数。因此我们可以通过首先检查是否可以移动来使用它,如果这是真的,则通过递归调用findPath来执行移动。如果调用返回nil,则该移动无效,否则我们找到了正确的游览。

  3. 这是新功能:

    (defun findPath (x y totalMoves steps m n lst)
      (if (= steps totalMoves)
          lst           ; if the steps are equal to total moves, then a solution is found
                        ; else try recursively all the possible moves from x y
                        ; 1- down and right
          (or (and (canMove (+ x 2) (+ y 1) m n lst)
                   (findPath (+ x 2) (+ y 1) totalMoves (+ steps 1) m n (appendList (+ x 2) (+ y 1) lst)))
                        ; 2- right and down
              (and (canMove (+ x 1) (+ y 2) m n lst)
                   (findPath (+ x 1) (+ y 2) totalMoves (+ steps 1) m n (appendList (+ x 1) (+ y 2) lst)))
                        ; 3- right ups
              (and (canMove (- x 1) (+ y 2) m n lst)
                   (findPath (- x 1) (+ y 2) totalMoves (+ steps 1) m n (appendList (- x 1) (+ y 2) lst)))
                        ; 4- up and right
              (and (canMove (- x 2) (+ y 1) m n lst)
                   (findPath (- x 2) (+ y 1) totalMoves (+ steps 1) m n (appendList (- x 2) (+ y 1) lst)))
                        ; 5 - up and left
              (and (canMove (- x 2) (- y 1) m n lst)
                   (findPath (- x 2) (- y 1) totalMoves (+ steps 1) m n (appendList (- x 2) (- y 1) lst)))
                        ; 6- left and up 
              (and (canMove (- x 1) (- y 2) m n lst)
                   (findPath (- x 1) (- y 2) totalMoves (+ steps 1) m n (appendList (- x 1) (- y 2) lst)))
                        ; 7- left and down
              (and (canMove (+ x 1) (- y 2) m n lst)
                   (findPath (+ x 1) (- y 2) totalMoves (+ steps 1) m n (appendList (+ x 1) (- y 2) lst)))
                        ; 8- down and left
              (and (canMove (+ x 2) (- y 1) m n lst)
                   (findPath (+ x 2) (- y 1) totalMoves (+ steps 1) m n (appendList (+ x 2) (- y 1) lst))))))
    

    最后,关于代码的一些注释。

    不要使用setq初始化之前未声明的变量

    let可用于声明和初始化局部变量,因此可以用这种方式定义函数kt

    (defun kt (x y m n)                            ; set the board
      (let ((totalmoves (* m n))                   ; find the total moves
            (steps 1)                              ; count steps
            (lst (list (list x y))))               ; list visited with initial points
        (findPath x y totalmoves steps m n lst)))  ; start tour with initial points
    

    尽量保持代码简单

    函数appendList的定义是将一个元素列表附加到另一个元素的反向,并反转结果。这相当于将第一个列表附加到第二个列表,即:

    (defun appendList (x y lst)
      (append lst (list (list x y))))
    

    使用generalized booleans简化条件

    例如,函数check-visitedcanMove可以集成在一个更简单的函数中:

    (defun canMove (x y m n lst)
      (and (<= 1 x m)      ;for the correct state, all conditions below must be met          
           (<= 1 y n)
           (not (member (list x y) lst :test 'equal))))
    

    尝试对代码进行分解,或在不需要时不重复类似的代码

    函数findPath有很多重复,可以使用loop来消除(thereis相当于{or loop 1}}):

    (defun findPath (x y totalMoves steps m n lst)
      (if (= steps totalMoves)
          lst 
          (loop for (x-inc y-inc) in '((+2 +1) (+1 +2) (-1 +2) (-2 +1) (-2 -1) (-1 -2) (+1 -2) (+2 -1))
            for x1 = (+ x x-inc)
            for y1 = (+ y y-inc)
            thereis (and (canMove x1 y1 m n lst)
                         (findPath x1 y1 totalMoves (1+ steps) m n (appendList x1 y1 lst))))))
    

    使用所使用语言的典型惯例

    在Common Lisp中,避免使用camelCase,更喜欢使用破折号分隔的名称和动词的经典表示法,例如find-path代替findPath,或can-move代替{{1}等等。