全递归(Exhaustive Recursive)、回溯(Backtracking)与搜索
无论是子集(组合)问题还是排列问题,如果要输出其所有可能的递归结果,就称之为全递归,其相当于访问相应递归树的所有叶子结点。有一类建立在递归树上的问题是只要获取所有结果中的一个或几个就可以得到相应解了,这类问题用部分递归(Partial Recursive)就可以求解。它相当于在根据相关条件的约束下,访问到递归树的某一个或几个叶子结点。一旦找到想要的解,就终止所有其他的递归调用并返回结果即是所谓的回溯法(Backtracking),带返回值的递归函数就用来执行此类任务。
比较经典的8皇后问题,就是用所谓的回溯法来解的,8皇后属于排列问题,因此回溯法也是在此基础上展开的。给出算法:
bool solve(Grid<bool>& board, int col)
{
if(col >= board.numCols()) return true;
for( int rowTotry = 0; rowTotry<board.numRows(); rowTotry++)
{
if( IsSafe(board, rowTotry, col) )
{
PlaceQueen( board, rowTotry, col );
if( solve(board, col+1) ) return true;
RemoveQueen( board, rowTotry, col );
}
}
return false;
}
需要说明的是:Grid是一个二维数组用来表示棋盘,numCols()与numRow()分别返回棋盘的列与行;在8x8的国际象棋棋盘上最多只能放8个皇后,所以在递归过程中,只要有一个皇后摆放失败,就无需再继续而返回上级决策过程重新选取新的决策路径。如果是9x9的棋盘呢?那就不能在循环体中立即返回了,需要对以上代码加以修改。
用递归方法求解“ 数独 ”问题,是另一种在排列上的回溯算法。如下:
bool SolveSudoKu(Grid<int>& grid)
{
int row, col;
if( !FindUnassignedLocation(grid, row, col) )
return ture;
for( int num=1; num<=9; num++ )
{
if( NoConflicts(grid, row, col, num) )
{
grid( row, col ) = num; // 试着对格子赋值
if( SolveSudoKu(grid) ) return true;
grid( row, col ) = UNASSIGNED;
}
}
return false;
}
还有一种求解字母公式的问题,也可以通过排列上的回溯法来求解。
如有以下待求解问题:
SEND
+ MORE
----------------
MONEY
算法如下:
bool DumbSolve( puzzleT pussle, string lettersToAssign )
{
if( lettersToAssign == "" )
return PuzzleSolved( puzzle );
for( int digit = 0; digit<=9; digit++ )
{
if( AssignLetter( letterToAssign[0], digit ) )
{
if( DumbSolve( puzzle, letterToAssign.Substr(1) ) ) return true;
UnassignLetter( lettersToAssign[0], digit );
}
}
return false;
}
以上的这些问题的算法实际上是在一个排列的递归问题所构建的递归树上,逐一偿试每一条可能的路径,当找到一个符合要求的解时,就中止所有新的偿试,立即返回。所以这种方法又称为搜索算法。当然搜索算法并不只是针对排列类的问题的。
另外,在本例的字母公式中如果逐一偿试所有可能,效率就太低了。实际上,公式中还隐含着一些条件,(比如当个位上的D和E确定后Y值也就确定了,并且加数与被加数中的D也确定了)这些条件对搜索过程形成了一定的制约,或者说对搜索过程中的选择决策作出了限制,如果能充分利用这些条件,就可以直接跳过对明显无效的路径的搜索,减少搜索的盲目性,因而必会提高算法的效率。
之前在其他几篇文章中讨论的“水杯量水问题”、“棋盘空格移动问题”、“n人决斗问题” 实际上用的都是搜索算法。在处理人机博奕的问题上,一般采用的也是递归决策树上的搜索算法。
本文介绍了全递归(Exhaustive Recursive)和部分递归的概念,重点讨论了回溯法(Backtracking)在解决子集、排列问题中的应用,如8皇后问题和数独求解。回溯法是一种在递归树上寻找解的策略,当找到符合条件的解时,会终止其他递归调用。此外,文章还提及了搜索算法在字母公式问题中的应用,强调了利用问题约束条件来提高算法效率的重要性。
1501

被折叠的 条评论
为什么被折叠?



