八皇后问题一向用来作为回溯的样例,这里就稍微抛下自己当初的解法
问题:在一个8*8的棋盘上摆放八个皇后,要求皇后不能相互攻击。
解法:这里以C语言为求解语言,先讲解非递归方法,之后讲解递归方法
由于皇后攻击方向为横向,竖向,和斜线攻击,问题也就简化为每排摆放一个皇后,
也即找到每排摆放皇后的位置。
一个简单的思路就是可以利用1个1维数组来求解,定义为全局变量,方便调用。
int arr[8]; // 用于每排皇后的位置,0-7表示第1列到第8列,初始化为-1,表示无皇后
所以一个遍历即可完成。
for( int row = 0; row < 8; ++row )
{
// 这里就是定位每排皇后的位置
}
那么问题就直接集中在如何确定每排皇后的位置
由于每排皇后有8个摆放的位置,所以只能一个一个试,即
for( int col = 0; col < 8; ++col )
{
if( 判断该排,该列是否可以摆放皇后 )
{
可以摆放,就直接确定皇后的位置,然后跳出循环
arr[row] = col;
break;
}
}
这里的if判断其实只用确定,第row排的皇后和之前的皇后是否冲突即可,
判断也很简单,只要不在同一列,而且斜线对角不成45度或135度即可,
所以可以抽离成一个函数单独实现
// 用于判断第row排第col列的皇后和前row排的皇后是否冲突
// 冲突返回true
bool judge(int row, int col)
{
for( int i = 0; i < row; ++i )
{
if( col == arr[i] || // 判断是否位于同一列
fabs( i - row ) == fabs( arr[i] - col ) ) // 判断是否位于斜线上
return true;
}
return false;
}
那么上面的循环就很好写了
for( int row = 0; row < 8; ++row )
{
for( int col = 0; col < 8; ++col )
{
if( !judge( row, col ) )
{
arr[row] = col;
break;
}
}
}
当然,做到这一步很简单,但却得不到正确的解,若是第row排每一列都不能摆放皇后,
则第row排产生不了结果
这里就需要回溯判断,
1、因为第row排都不能摆放,那么就必须修改row-1排的皇后位置,
2、然后再来摆放第row排的皇后,确定好位置,则继续row+1排皇后的位置
3、如果继续不能摆放,则重复1,若第row-1排的皇后所有的位置换过之后,
还是确定不了row排皇后的位置,那么就继续修改row-2排,直到可以,或者
已达到第0排,还是不行,则结束查找。
那么做出以下修改,加粗为增加部分
for( int row = 0; row < 8; ++row )
{
int col = 0; // col需要在for之外用到,因为受作用域的限制,这里需要提出来
// 这里需要有些处理,稍下分析
for( ; col < 8; ++col )
{
if( !judge( row, col ) )
{
arr[row] = col;
break;
}
}
// 若是col == 8表示第row排皇后,已经尝试过每一列,
// 但是均不能摆放,需要重新摆放第row-1排的皇后
if( col == 8 )
{
row -= 2; // 这里需要-2,因为for循环中有一个++row
}
}
这里只是单纯回到了row-1排,却没有任何处理,因为第row-1排之前已经放过皇后了(假设为第y列),
所以现在row-1排的皇后只需要从第y+1列开始摆放即可,所以可以添加的处理是
if( arr[row] != -1 ) // 如果这是回溯的一排,那么必然已经摆放过皇后了,那就单独处理
{
col = arr[row] + 1; // 既然是回溯的,就令该排皇后从摆放在y+1列处
arr[row] = -1; // 既然是回溯的,就表示这排重新摆放,所以就直接令该排为-1,表示没摆皇后
}
好了,处理到了,这一步,如果不考虑边界问题,基本上已经可以实现输出一种情况
(1,1) (2,5) (3,8) (4,6) (5,3) (6,7) (7,2) (8,4) #1
这种结果恰巧不涉及到回溯2排的情况,所以,正好可以输出,现在添加边界处理
if( arr[row] != -1 ) // 如果这是回溯的一排,那么必然已经摆放过皇后了,那就单独处理
{
col = arr[row] + 1; // 既然是回溯的,就令该排皇后从摆放在y+1列处
arr[row] = -1; // 既然是回溯的,就表示这排重新摆放,所以就直接令该排为-1,表示没摆皇后
// 添加边界处理
if( arr[row] == 7 ) // 都到了最后一列也不行,需要继续回溯
{
row -= 2;
continue;
}
}
到目前为止,所以的处理都已经结束,处理了边界,现在看下全貌
这里去掉注释和judge函数
void slove()
{
for( int row = 0; row < 8; ++row )
{
int col = 0;
if( arr[row] != -1 )
{
col = arr[row] + 1;
arr[row] = -1;
if( arr[row] == 8 )
{
row -= 2;
continue;
}
}
for( ; col < 8; ++col )
{
if( !judge( row, col ) )
{
arr[row] = col;
break;
}
}
if( col == 8 )
{
row -= 2;
}
}
}
好了,再接再厉,实现全部结果输出
要实现全部输出,则需要在完成一次结果输出之后,继续回溯上一排,以祈求一次新的输出
这样重复直到回溯到第1排第8列这最后一个结束为止,即可退出查找新的摆放方法
那么修改如下
void slove()
{
// 如果回溯到了第1排第8列,就退出
if( arr[row] == 7 && row == 0 )
{
return ;
}
// 和原先内容一样
// 若是最后一排也摆放好皇后,则输出该种摆法结果,然后继续循环
if( arr[row] != -1 && row = 7 )
{
disp(); // 一个输出函数
// 需要回溯到上一排,以祈求新的摆法
}
}
这里由于使用非递归,要实现这种跳转,就只能采用goto,尽管我们学习中,都被告诫限制使用goto,但是
在不破坏程序结构的同时,采用一下也是可以的
最终结果如下
void slove()
{
for( int row = 0; row < 8; ++row )
{
if( arr[row] == 7 && row == 0 )
{
return ;
}
label: // 跳到这里,处理回溯的那一行
int col = 0;
if( arr[row] != -1 )
{
col = arr[row] + 1;
arr[row] = -1;
if( arr[row] == 8 )
{
row -= 2;
continue;
}
}
for( ; col < 8; ++col )
{
if( !judge( row, col ) )
{
arr[row] = col;
break;
}
}
if( col == 8 )
{
row -= 2;
}
if( arr[row] != -1 && row == 7 )
{
disp();
goto label;
}
}
}
至此,关于非递归的解法就全部结束了
这里将此封装下,给下可以运行的全套代码
/*
* eighetPuzzle.cpp
*
* Created on : 2012-11-26
* Author : zwsatan
*/
#include <iostream>
#include <cmath>
using namespace std;
class PuzzleQueen
{
int arr[8];
bool judge(int row, int col)
{
for( int i = 0; i < row; ++i )
{
if( col == arr[i] || fabs( i - row ) == fabs( arr[i] - col ) )
return true;
}
return false;
}
public:
PuzzleQueen()
{
for( int i = 0; i < 8; ++i )
arr[i] = -1;
}
void slove()
{
for( int row = 0; row < 8; ++row )
{
if( arr[row] == 7 && row == 0 )
{
return ;
}
label:
int col = 0;
if( arr[row] != -1 )
{
col = arr[row] + 1;
arr[row] = -1;
if( arr[row] == 8 )
{
row -= 2;
continue;
}
}
for( ; col < 8; ++col )
{
if( !judge( row, col ) )
{
arr[row] = col;
break;
}
}
if( col == 8 )
{
row -= 2;
}
if( arr[row] != -1 && row == 7 )
{
disp();
goto label;
}
}
}
void disp()
{
static int count = 0;
for( int i = 0; i < 8; ++i )
{
cout << "(" << i+1 << "," << arr[i] + 1 << ") ";
}
cout << "#" << ++count;
cout << endl;
}
};
int main(int argc, char *argv[])
{
PuzzleQueen pq;
pq.slove();
return 0;
}
疲乏了吗?
还是继续接下来看递归的解法,`(*∩_∩*)′
当然,我们绝大多数时间,对待回溯采用的都是递归方法,因此,我们在上述的方法上,稍作修改,
以使它成为递归形式
回到我们最初,即使使用递归,要做到的也是每排摆放一个皇后
so
void slove(int row) // 这里改为处理第row行皇后
{
// 这里用来处理,第row行皇后分别放在col列上的结果
for( int col = 0; col < 8; ++col )
{
// 这里进行递归处理
}
}
递归处理里也是很简单
// 这里处理第row行第col列的皇后,是否可以摆放
void recursion(int row, int col)
{
// 判断是否与之前的皇后冲突
if( !judge( row, col ) )
{
// 不发生冲突,将此位置记录
arr[row] = col;
// 若达到第七行,表示一次摆放方法结束
if ( row == 7 ) {
disp(); // 输出摆放结果
return;
}
// 继续遍历下一个皇后位置
slove( row + 1 );
}
// 发生冲突,不处理,退出此函数,会在slove函数中继续处理后一列的摆放位置
}
最终slove方法稍加封装可以这样
void slove()
{
slove_( 0 ); // 处理第1行
}
void slove_(int row)
{
for( int col = 0; col < 8; ++col )
{
recursion( row, col );
}
}
void recursion(int row, int col)
{
if( !judge( row, col ) )
{
arr[row] = col;
if ( row == 7 ) {
disp();
return;
}
slove_( row + 1 );
}
}
当然,这个,也可以稍加封装成类,就不再浪费大家时间了
最后,我关于回溯的理解,就是
循环+递归
好了,最后,拖一个以前写的八皇后程序
这是基于Qt写出来的demo程序,放出下载链,感兴趣的可以看看