ruby – 高效的置换树算法

我试图编写一个算法来解决任何数独.然而,我原来的方法是有缺陷的,因为我没有意识到一个结构良好的数独应该只有一个解决方案.

因此,结果算法没有针对数独求解进行优化,并且更为通用:它递归地生成当前数独布局的每个可能允许的结果/布局.因此,该算法在理论上可以找到空白数独的每个解决方案(但我/我们/人类可能不会看到输出).

我的第一个问题是:对于这种类型的搜索算法,什么是最佳方法 – 如何改进我的算法?总体战略是:

>在分支点尝试所有可能的解决方案
>如果无法解决单元格,则终止分支
>如果达到解决方案,则终止分支

结构和方法改编自为解决SICP(https://mitpress.mit.edu/sicp/full-text/book/book-Z-H-15.html)中的八皇后问题而提出的时间解耦解决方案,似乎这是一种反向跟踪算法(将“时间”状态推入机器中),在我的情况下这是真的吗?

我的第二个问题是,在处理递归算法时,如何使用Ruby> = 2.x Lazy功能生成可枚举?

期望的结果是做一些像solve(sudoku).first(x)这样的东西来检索第一个x解决方案.即解决方案存储为流/序列.我遇到的困难是递归生成嵌套的枚举,我正在努力寻找一种方法来传递整个树的强制方法.

我已在下面列出了密钥代码,但完整的方法套件可以在这里找到:https://gist.github.com/djtango/fe9322748cf8a055fc0e

def solve(sudoku)
  guess(simplify_sudoku(sudoku))
end

def guess(sudoku, row = 0, column = 0)
  return sudoku.flatten                 if end_of_grid?(row, column)
  return guess(sudoku, row + 1, 0)      if end_of_column?(column)
  return guess(sudoku, row, column + 1) if filled?(sudoku, row, column)
  return nil                            if insoluble?(sudoku, row, column)
  permissible_values(sudoku, row, column).map do |value|
    guess(update_sudoku(sudoku, value, row, column), row, column + 1)
  end
end

def simplify_sudoku(sudoku)
  return sudoku if no_easy_solutions?(sudoku)
  simplify_sudoku(fill_in(sudoku))
end

def fill_in(sudoku)
  new_sudoku = copy_sudoku(sudoku)
  new_sudoku.map.with_index do |row, row_index|
    row.map.with_index do |column, column_index|
      solution = permissible_values(new_sudoku, row_index, column_index)
      easy_and_not_filled?(solution, new_sudoku, row_index, column_index) ? [solution.first] : column
    end
  end
end

在尝试实现延迟评估时,引入惰性方法的最自然点是猜测方法,如下所示:

  permissible_values(sudoku, row, column).map do |value|
    guess(update_sudoku(sudoku, value, row, column), row, column + 1)
  end

但是在实现这个时,结果是:

solve(blank_sdk)
 => #<Enumerator::Lazy: #<Enumerator::Lazy: [1, 2, 3, 4, 5, 6, 7, 8, 9]>:map> 

solve(blank_sdk).first(10)
 => [#<Enumerator::Lazy: #<Enumerator::Lazy: [2, 3, 4, 5, 6, 7, 8, 9]>:map>, #<Enumerator::Lazy: #<Enumerator::Lazy: [1, 3, 4, 5, 6, 7, 8, 9]>:map>, #<Enumerator::Lazy: #<Enumerator::Lazy: [1, 2, 4, 5, 6, 7, 8, 9]>:map>, #<Enumerator::Lazy: #<Enumerator::Lazy: [1, 2, 3, 5, 6, 7, 8, 9]>:map>, #<Enumerator::Lazy: #<Enumerator::Lazy: [1, 2, 3, 4, 6, 7, 8, 9]>:map>, #<Enumerator::Lazy: #<Enumerator::Lazy: [1, 2, 3, 4, 5, 7, 8, 9]>:map>, #<Enumerator::Lazy: #<Enumerator::Lazy: [1, 2, 3, 4, 5, 6, 8, 9]>:map>, #<Enumerator::Lazy: #<Enumerator::Lazy: [1, 2, 3, 4, 5, 6, 7, 9]>:map>, #<Enumerator::Lazy: #<Enumerator::Lazy: [1, 2, 3, 4, 5, 6, 7, 8]>:map>] 

solve(blank_sdk).first(1000000000000)
 => [#<Enumerator::Lazy: #<Enumerator::Lazy: [2, 3, 4, 5, 6, 7, 8, 9]>:map>, #<Enumerator::Lazy: #<Enumerator::Lazy: [1, 3, 4, 5, 6, 7, 8, 9]>:map>, #<Enumerator::Lazy: #<Enumerator::Lazy: [1, 2, 4, 5, 6, 7, 8, 9]>:map>, #<Enumerator::Lazy: #<Enumerator::Lazy: [1, 2, 3, 5, 6, 7, 8, 9]>:map>, #<Enumerator::Lazy: #<Enumerator::Lazy: [1, 2, 3, 4, 6, 7, 8, 9]>:map>, #<Enumerator::Lazy: #<Enumerator::Lazy: [1, 2, 3, 4, 5, 7, 8, 9]>:map>, #<Enumerator::Lazy: #<Enumerator::Lazy: [1, 2, 3, 4, 5, 6, 8, 9]>:map>, #<Enumerator::Lazy: #<Enumerator::Lazy: [1, 2, 3, 4, 5, 6, 7, 9]>:map>, #<Enumerator::Lazy: #<Enumerator::Lazy: [1, 2, 3, 4, 5, 6, 7, 8]>:map>] 

最佳答案 您无需在每个分支点尝试所有可能的解决方案.您可以通过找到剩余选项数最少的单元格并尝试其中的变体来做得更好.这样,您可以快速设置仅剩下一个选项的所有单元格.当你需要尝试不同的解决方案时,你可能只会尝试一些变种(2-3),而不是所有的可能性,所以它应该更快.如果它们都不适用于该单元格,则可以停止,因为没有解决方案.

点赞