回溯算法也叫试探算法,是一种系统的搜素问题解的方法。回溯算法的基本思想:在节点出选择一条 路向前走,能进则进,不能进则退回上一个节点,选另一条路走。为了能应用回溯算法,所要求的解必须能表示成n元组(x1,x2,x3,x4,...xn),其中xi是取自某个有穷集Si。通常,所求解的问题要取一个使某一规范函数P(x1,x2,x3,x4.....xn)取极小值(或者极大值或满足该规范函数的所有向量)。
回溯算法的一般步骤:
1 针对所给问题,定义问题的解空间,它至少包含问题的一个(最优)解。
2 确定易于搜索的解空间结构,使得能用回溯法方便地搜索整个解空间 。
当生成问题状态空间树以后,就可以生成问题状态。然后找出问题状态中的那些是解状态,接着根据解状态确定那些解状态是答案状态。根据DFS深度优先搜索算法,从根节点开始扩展其他节点。如果一个已扩展节点而他的子节点还没有全部被扩展,则这个节点叫作活节点。当前正在扩展其儿子节点的节点叫作E节点(正在扩展节点)。不再进一步扩展或者其子节点已被扩展的节点叫做死节点。根据DFS搜索方法,如果遇到了一个死节点,就向上回溯到最近的一个活节点。然后让这个活节点变成E节点。如此一直递归的在状态空间树中搜索下去,直到找到一个解,或者没有活节点。
经典例题:
(1) N皇后问题
在一个NXN的棋盘上放置N个皇后,且是每两个皇后之间不能攻击。也就是任意两个皇后不能在,同行,同列,同对角线。给出一个放着皇后的方法。
分析:
N皇后问题是一个经典的组合问题,首先给棋盘行和列都编上1~N的号码。这些皇后们也可以编上1~N的号码。由于每个皇后都在不同的行上面,为了不失一般性。故约定皇后i放在第i行。所以NxN的棋盘用一个NxN的二维数组chess_board[N][N]表示,N个皇后用一个大小为N的一维数组queen[N]表示。其中queen[i]表示皇后i所在的列上。
<1>确定解空间
由显示条件 0<= i <N 可知解空间有 (N的N次方)个 N元组表示。然而这个解空间真的是很大。实质上就是枚举策略解决此问题。当考虑到此问题的两个隐式约束条件 [1].任意两个皇后在不同的列,[2].任意两个皇后在不同的对角线上。所以解空间就变为N!个元组。
<2>确定空间结构
由于每个解空间里面的元素都是一个N元组,所以可以用一条树的路径表示此元组。所有的元组构成了一棵树,这棵树就是解空间的结构。 由于N皇后的解空间结构不容易画出,下面列子是4皇后的接空间结构。
这棵树种包含4!=24个叶子节点,节点按照深度优先检索编号的。
<3> 代码
#include<iostream>
using namespace std;
int chess_size = 0; // 定义棋盘的大小,初始化为0
int *queens; //queens[i]数组下标i表示第i个皇后在第i行。queens[i]的值k,表示第i个皇后在chess_board的第i行,第k列。
char **chess_board; //chess_size*chess_size的棋盘,'.'表示没有皇后。'#'表示此位置放置皇后。
/*
*初始化一个棋盘,动态生成chess_board大小为chess_size*chess_size 的二维字符数组,动态queens大小为chess_size的一维整型数组。
*/
bool initial_chess_board();
bool is_place(int number ); //判断queens[number]是否可以是第number的放置位置,可以返回true 否则返回 false。
void print_chess_board(); //打印最后成功放置的棋盘。
bool set_place(); //执行放置回溯算法,
int main()
{
cout<<"please input the chessboard size:";
cin>>chess_size; //输入棋盘的大小
initial_chess_board( ); //初始化棋盘
set_place(); //执行放置策略
return 0;
}
bool initial_chess_board()
{
//动态生成queens的一维数组
queens = new int[chess_size];
if(queens == NULL)
return false;
//动态生产二维字符数组棋盘
chess_board = new char * [chess_size];
if(chess_board == NULL)
return -1;
for(int i=0; i < chess_size; i++)
{
chess_board[i] = new char[chess_size];
if(chess_board[i] == NULL)
return false;
}
//初始化棋盘和皇后所在位置
for(int i=0; i < chess_size ; i++)
{
queens[i] = -1; //初始值为-1,因为列的有效行 0<= i <chess_size .为了在回溯算法.
for(int j=0 ; j < chess_size ; j++)
{
chess_board[i][j] = '.';
}
}
return true;
}
bool is_place(int number)
{
for(int i=0 ; i < number ; i++)
{ /*判断是否在同列,或同对角线*/
if( queens[i] == queens[number] || (queens[i] - queens[number]) == (i - number) ||(queens[i] - queens[number]) == (number - i))
{
return false;
}
}
return true;
}
void print_chess_board( )
{
for(int i=0; i < chess_size ; i++)
{
cout<<endl;
for(int j=0; j < chess_size ; j++ )
{
if(queens[i] == j)
{
cout<<"#"<<" ";
}
else
{
cout<<chess_board[i][j]<<" ";
}
}
}
cout<<endl;
}
bool set_place()
{
int number = 0;
queens[0] = -1;
while (number >= 0)
{
queens[number]++;
while(queens[number] < chess_size && !is_place(number))
{
queens[number]++;
}
if( queens[number] < chess_size )
{
if( number == (chess_size - 1) )
{
print_chess_board();
return true;
}
else
{
number++;
queens[number]= -1;
}
}
else
{
number--; /*此行不能安置皇后,要回溯*/
}
}
return false;
}
参考资料:
计算机算法基础(第三版).余祥宣
来源:oschina
链接:https://my.oschina.net/u/818639/blog/180310