Recursion Lua第四版编程中的八皇后难题

Recursion Lua第四版编程中的八皇后难题,recursion,lua,backtracking,Recursion,Lua,Backtracking,我目前正在阅读Lua第四版的编程,我已经被困在“第2章:插曲:八皇后之谜”的第一个练习中 示例代码如下所示: 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

我目前正在阅读Lua第四版的编程,我已经被困在“第2章:插曲:八皇后之谜”的第一个练习中

示例代码如下所示:

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:修改八皇后程序,使其在 打印第一个解决方案

-- 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)
在本课程结束时,
a
包含所有可能的解决方案;我不知道是否应该修改
addqueen(n,c)
,使
a
只包含一个可能的解决方案,或者是否应该修改
printsolution(a)
,使其只打印第一个可能的解决方案

尽管我不确定是否完全理解回溯,但我试图实现这两个假设,但都没有成功,因此非常感谢任何帮助

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

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


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

清单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相当于清单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中的代码与原始实现进行比较,区别在于它在将所有八个皇后放置在板上之后进行验证,而原始实现在每次添加皇后时都进行验证,如果新添加的皇后导致冲突,则不会进一步进入下一行。这就是“回溯”的全部含义,即它进行“暴力”搜索,一旦找到一个不会导致解决方案的节点,它就会放弃搜索分支,并且它必须到达搜索树的一个叶子以确定它是一个有效的解决方案

-- 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)
回到清单1中的修改

(1) 当函数到达这一点时,它将到达搜索树的一个叶子并找到一个有效的解决方案,因此让它返回true表示成功

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


(3) 这是函数在完成for循环后返回的点。修改(1)和(2)后,这意味着当函数到达这一点时,它无法找到解决方案,因此让它显式返回false表示失败。

您尝试了什么?我们可以得到一些您的代码,这样我们可以更容易地提供帮助吗?基本上,我尝试修改这两个函数的for循环条件。而且,我认为我没有正确的回溯思维;确切地说,我不理解
a
的最终状态-它是一个表格表吗?@Tolga-数组
a
在每个新的解决方案上都会被重写<代码>a不包含以前的解决方案。它只是长度为8的数组。这不是一张桌子的桌子。亲爱的伊戈尔,谢谢你的澄清;下面的答案详细阐述了这一点,并为我提供了解决方案。不过,我仍然需要一些解释:如果你认为你能帮上忙,请参阅我在回答后刚刚提交的评论。:)亲爱的保罗,非常感谢你的帮助,它确实有效!不过我还是有点困惑:为什么对
addqueen({},1)
的一个调用就可以生成多个解决方案,而其定义中没有
os.exit()
?它是否等同于
,而true do addqueen({},1)end
?此外,我们如何确定这些不同的解决方案是不同的排列,以及当定义中没有
os.exit()
时,打破循环的边缘条件是什么?@Tolga-这实际上是一个,因此它生成所有的解决方案。亲爱的@xmartin,很抱歉耽搁了您的时间,感谢您花时间写下如此完整和详细的解释,这非常有帮助@托尔加,很高兴知道这有帮助。:-)