我目前正在阅读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>
即使我不确定完全理解回溯,但我试图实现这两个假设都没有成功,所以任何帮助都会非常感激。
答案 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表示失败。