回溯法

六月ゝ 毕业季﹏ 提交于 2021-02-12 11:54:16

回溯算法也叫试探算法,是一种系统的搜素问题解的方法。回溯算法的基本思想:在节点出选择一条 路向前走,能进则进,不能进则退回上一个节点,选另一条路走。为了能应用回溯算法,所要求的解必须能表示成n元组(x1,x2,x3,x4,...xn),其中xi是取自某个有穷集Si。通常,所求解的问题要取一个使某一规范函数P(x1,x2,x3,x4.....xn)取极小值(或者极大值或满足该规范函数的所有向量)。

回溯算法的一般步骤:

1 针对所给问题,定义问题的解空间,它至少包含问题的一个(最优)解。

2 确定易于搜索的解空间结构,使得能用回溯法方便地搜索整个解空间 。

3 以DFS或BFS搜索解空间,并且在搜索过程中用剪枝函数避免无效搜索。


  当生成问题状态空间树以后,就可以生成问题状态。然后找出问题状态中的那些是解状态,接着根据解状态确定那些解状态是答案状态。根据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;
}

参考资料:

计算机算法基础(第三版).余祥宣


易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!