ruby - 高效的置换树算法

标签 ruby algorithm recursion sudoku

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

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

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

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

结构和方法改编自为解决 SICP 中的八皇后问题而概述的时间解耦解决方案 (https://mitpress.mit.edu/sicp/full-text/book/book-Z-H-15.html),这似乎是一种回溯算法(“时间”状态被推送进入机器),我的情况是这样吗?

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

期望的结果是执行类似 solve(sudoku).first(x) 的操作来检索前 x 个解决方案。 IE。解决方案存储为流/序列。我遇到的困难是递归生成嵌套的枚举,我正在努力寻找一种将强制方法传递到整个树的方法。

我在下面包含了关键代码,但完整的方法套件可以在这里找到: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),而不是所有的可能性,所以它应该更快。如果它们都不适用于该单元格,您可以停止,因为没有解决方案。

关于ruby - 高效的置换树算法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33999905/

相关文章:

ruby-on-rails - Rails 5 Assets 未在生产中加载

java - Java递归中返回值的用法

algorithm - 国际象棋 - 在防止检查时避免无限递归

haskell - 递归 IO 函数中的内存泄漏 - PAP

ruby-on-rails - ruby 正则表达式 - 如何搜索一个词但如果另一个词存在则不搜索(在 url/uri 中)。?

ruby-on-rails - Rails 控制台找不到 activesupport-4.1.2,即使它已安装

regex - 如何在提取匹配项时验证 Ruby 中字符串的格式?

java - 你如何找到 0 和 1 的二维矩阵中的矩形数?

java - 用 O(n) 算法找到子集中三重乘积的最小值和最大值

c++ - 分而治之排序的正确性证明