📜  解决密码算法难题|回溯8

📅  最后修改于: 2021-04-23 21:59:24             🧑  作者: Mango

报纸和杂志通常具有以下形式的隐式算术难题:

SEND
+ MORE
--------
 MONEY
-------- 

此处的目标是为每个字母分配一个0到9之间的数字,以便正确计算该算术。规则是,所有出现的字母必须分配一个相同的数字,并且不能将一个数字分配给一个以上的字母。

  • 首先,创建所有需要分配以传递给Solve的字符的列表
  • 如果分配了所有字符,则解决难题后返回true,否则返回false
  • 否则,请考虑第一个未分配的字符
  • 用于(在未使用的数字中的所有可能的选择)

    做出选择,然后递归尝试分配其余字符
    如果递归成功,则返回true
    如果成功,请取消分配并尝试输入其他数字

  • 如果尝试了所有数字并且没有任何效果,则返回false触发回溯
/* ExhaustiveSolve
* ---------------
* This is the "not-very-smart" version of cryptarithmetic solver. It takes
* the puzzle itself (with the 3 strings for the two addends and sum) and a
* string of letters as yet unassigned. If no more letters to assign
* then we've hit a base-case, if the current letter-to-digit mapping solves
* the puzzle, we're done, otherwise we return false to trigger backtracking
* If we have letters to assign, we take the first letter from that list, and
* try assigning it the digits from 0 to 9 and then recursively working
* through solving puzzle from here. If we manage to make a good assignment
* that works, we've succeeded, else we need to unassign that choice and try
* another digit. This version is easy to write, since it uses a simple
* approach (quite similar to permutations if you think about it) but it is
* not so smart because it doesn't take into account the structure of the
* puzzle constraints (for example, once the two digits for the addends have
* been assigned, there is no reason to try anything other than the correct
* digit for the sum) yet it tries a lot of useless combos regardless
*/
bool ExhaustiveSolve(puzzleT puzzle, string lettersToAssign)
{
    if (lettersToAssign.empty()) // no more choices to make
        return PuzzleSolved(puzzle); // checks arithmetic to see if works
    for (int digit = 0; digit <= 9; digit++)   // try all digits
    {
        if (AssignLetterToDigit(lettersToAssign[0], digit))
        {
            if (ExhaustiveSolve(puzzle, lettersToAssign.substr(1)))
                return true;
            UnassignLetterFromDigit(lettersToAssign[0], digit);
        }
    }
    return false;  // nothing worked, need to backtrack
}

上面的算法实际上与置换算法有很多共通之处,它几乎只是创建了从字符到数字的映射的所有排列,并尝试每一种直到成功完成一项或全部尝试为止。对于大难题,这可能需要一段时间。
一种更智能的算法可以考虑难题的结构,并避免沿死胡同走下去。例如,如果我们从每个人的位置开始分配字符,然后向左移动,则在每个阶段,我们都可以在继续进行之前验证到目前为止的正确性。这肯定会使代码复杂化,但会极大地提高效率,从而使解决大型难题更为可行。

在这种情况下,伪代码下方具有更多特殊情况,但总体设计相同

  • 首先检查最上面一行的最右边数字,进位为0
  • 如果我们超出了难题的最左位,如果没有进位,则返回true,否则返回false
  • 如果我们当前正在尝试在加数之一中分配一个字符
    如果已经分配了char,则只需在该行下面的行上重复进行,即可将总和添加值
    如果未分配,则
    • 用于(在未使用的数字中的所有可能的选择)
      做出选择,然后在该选择的下一行,如果成功,则返回true
      如果成功,请取消分配并尝试输入其他数字
    • 如果没有分配触发回溯,则返回false
  • 否则,如果尝试在总和中分配一个字符
  • 如果分配了字符且匹配正确,
    在进位左侧的下一列重复,如果成功返回true,
  • 如果分配的字符不匹配,则返回false
  • 如果未分配char和正确的数字,则返回false
  • 如果未分配char和未使用正确的数字,
    分配它,并在进位的下一列上重复出现,如果成功返回true
  • 返回false触发回溯

来源:
http://see.stanford.edu/materials/icspacs106b/H19-RecBacktrackExamples.pdf