Lua第四版编程中的八女王之谜

时间:2018-02-06 15:34:59

标签: recursion lua backtracking

我目前正在阅读Lua第四版中的编程,我已经坚持第一次练习"第二章插曲:八皇后拼图。 "

示例代码如下:

N = 8 -- board size

-- check whether position (n, c) is free from attacks
function isplaceok (a, n ,c)
    for i = 1, n - 1 do -- for each queen already placed
        if (a[i] == c) or           -- same column?
        (a[i] - i == c - n) or      -- same diagonal?
        (a[i] + i == c + n) then    -- same diagonal?
            return false -- place can be attacked
        end
    end
    return true -- no attacks; place is OK
end

-- print a board
function printsolution (a)
    for i = 1, N do                 -- for each row
        for j = 1, N do             -- and for each column
            -- write "X" or "-" plus a space
            io.write(a[i] == j and "X" or "-", " ")
        end
        io.write("\n")
    end
    io.write("\n")
end

-- add to board 'a' all queens from 'n' to 'N'
function addqueen (a, n)
    if n > N then -- all queens have been placed?
        printsolution(a)
    else -- try to place n-th queen
        for c = 1, N do
            if isplaceok(a, n, c) then
                a[n] = c -- place n-th queen at column 'c'
                addqueen(a, n + 1)
            end
        end
    end
end

-- run the program
addqueen({}, 1)

代码非常评论,而且这本书很明确,但我无法回答第一个问题:

  

练习2.1:修改八皇后程序,使其在之后停止   打印第一个解决方案。

在此计划结束时,a包含所有可能的解决方案;我无法确定是否应修改addqueen (n, c)以便a仅包含一个可能的解决方案,或者是否应修改printsolution (a)以便它只打印第一个可能的解决方案?< / p>

即使我不确定完全理解回溯,但我试图实现这两个假设都没有成功,所以任何帮助都会非常感激。

2 个答案:

答案 0 :(得分:1)

  

在本程序结束时,a包含所有可能的解决方案

据我了解解决方案,a从不包含所有可能的解决方案;它包括一个完整的解决方案或一个算法正在处理的不完整/不正确的解决方案。该算法的编写方式简单地列举了可能的解决方案,尽可能早地跳过那些产生冲突的解决方案(例如,如果第一和第二个皇后在同一条线上,那么第二个皇后将被移动而不检查其他皇后的位置,因为他们无论如何都不满足解决方案。)

因此,要在打印第一个解决方案后停止,您只需在os.exit()行之后添加printsolution(a)

答案 1 :(得分:0)

清单1是实现需求的替代方法。分别用(1),(2)和(3)评论的三行是对书中原始实现的修改,并且在问题中列出。通过这些修改,如果函数返回true,则找到解决方案并且a包含解决方案。

-- Listing 1
function addqueen (a, n)
  if n > N then    -- all queens have been placed?
    return true  -- (1)
  else  -- try to place n-th queen
    for c = 1, N do
      if isplaceok(a, n, c) then
        a[n] = c    -- place n-th queen at column 'c'
        if addqueen(a, n + 1) then return true end  -- (2)
      end
    end
    return false -- (3)
  end
end

-- run the program
a = {1}
if not addqueen(a, 2) then print("failed") end
printsolution(a)

a = {1, 4}
if not addqueen(a, 3) then print("failed") end
printsolution(a)

让我从本书的练习2.2开始,根据我过去的经验向其他人解释“回溯”算法,可能有助于更好地理解原始实现和我的修改。

练习2.2需要首先生成所有可能的排列。清单2中提供了一个简单直观的解决方案,它使用嵌套的for循环生成所有排列,并在最内层循环中逐个验证它们。虽然它满足练习2.2的要求,但代码确实看起来很笨拙。它也是硬编码解决8x8板。

-- Listing 2
local function allsolutions (a)
  -- generate all possible permutations
  for c1 = 1, N do
    a[1] = c1
    for c2 = 1, N do
      a[2] = c2
      for c3 = 1, N do
        a[3] = c3
        for c4 = 1, N do
          a[4] = c4
          for c5 = 1, N do
            a[5] = c5
            for c6 = 1, N do
              a[6] = c6
              for c7 = 1, N do
                a[7] = c7
                for c8 = 1, N do
                  a[8] = c8
                  -- validate the permutation
                  local valid
                  for r = 2, N do  -- start from 2nd row
                    valid = isplaceok(a, r, a[r])
                    if not valid then break end
                  end
                  if valid then printsolution(a) end
                end
              end
            end
          end
        end
      end
    end
  end
end

-- run the program
allsolutions({})

当N = 8时,清单3等同于List 2. else-end块中的for循环执行清单2中的整个嵌套for循环。使用递归调用使代码不仅紧凑,而且灵活,即它能够用预先设置的行解决NxN板和板。但是,递归调用有时会引起混淆。希望列表2中的代码有所帮助。

-- Listing 3
local function addqueen (a, n)
  n = n or 1
  if n > N then
    -- verify the permutation
    local valid
    for r = 2, N do  -- start from 2nd row
      valid = isplaceok(a, r, a[r])
      if not valid then break end
    end
    if valid then printsolution(a) end
  else
    -- generate all possible permutations
    for c = 1, N do
      a[n] = c
      addqueen(a, n + 1)
    end
  end
end

-- run the program
addqueen({})     -- empty board, equivalent allsolutions({})
addqueen({1}, 2) -- a queen in 1st row and 1st column

将清单3中的代码与原始实现进行比较,不同之处在于它在将所有8个皇后放置在板上之后进行验证,而原始实现在每次添加皇后时都会验证,并且不会进一步进行下一步如果新添加的女王引起冲突则排。这就是“回溯”的意思,即它做“强力”搜索,一旦找到一个不会导致解决方案的节点就放弃搜索分支,它必须到达搜索树的一个叶子到确定它是一个有效的解决方案。

回到清单1中的修改。

(1)当函数到达此点时,它到达搜索树的叶子并找到有效的解,所以让它返回true表示成功。

(2)这是阻止该功能进一步搜索的要点。在最初的实现中,无论递归调用发生了什么,for循环都会继续。在修改(1)到位的情况下,如果找到解,则递归调用返回true,函数需要停止并传回成功的信号;否则,它继续for循环,寻找其他可能的解决方案。

(3)这是完成for循环后函数返回的点。修改(1)和(2)到位后,意味着当函数到达此点时未能找到解,所以让它显式返回false表示失败。