八皇后问题-回溯算法-c++

来源:互联网 发布:js prototype 继承 编辑:程序博客网 时间:2024/06/02 15:14
八皇后问题是大数学家高斯于1850年提出来的。该问题是在8×8的国际象棋棋盘上放置8个皇后,使得没有一个皇后能"吃掉"任何其他一个皇后,即没有任何两个皇后被放置在棋盘的同一行、同一列或同一斜线上。要求编一个程序求出该问题的所有解。
    l解题思路
使用回溯算法求解的问题一般有这样的特征,要求解问题必须分为若干步,每一步都有几种可能的选择,而且往往在某个选择不成功时需要回头再试另外一种选择,如果到达求解目标则每一步的选择构成了问题的解,如果回头到第一步且没有新的选择则问题求解失败。
初始化,做试探的准备
do {
选择当前步的可能求解路线
if (有可能的求解路线) 前进一步
else {
清除当前选择所遗留的痕迹
回溯一步
}
} while(没有到最后一步且没有回到初始状态)
if (到最后一步) 每一步的选择就是问题的解
else 问题没有解
回溯算法有两个关键的地方,一是要记住每一步的选择以便不重复选择以及最后能够输出整个解,二是在回溯到前一步的时候要消除当前步的影响。
根据回溯算法,显然要一个数据结构来记住每一步的选择,我们可通过使用一个数组solution记住每一行皇后放置的列位置来反映每一步的选择。为更好地选择下一步放置皇后的列位置,可使用一个二维数组board来模拟棋盘,并记住什么位置放了皇后。
在确定数据结构之后,我们对上述回溯算法作进一步的细化,需要确定几个问题:
1.怎样的状态是初始状态。
2.怎样选择当前步可能的路线
3.怎样表示向前推进一步
4.怎样回溯及清除当前步的痕迹
显然初始状态应该是solution数组中所有的值为一个特定的不可能的选择,例如为-1或n,其中n是棋盘的宽度,可能的选择是0到n-1。还有棋盘board的所有值应该表示没有放皇后。
选择当前步可能的求解路线应该是判断当前行可在哪个列位置放皇后。也就是说求解八皇后问题的每一步就是在每一行选择一个列位置放皇后。这里的关键是要考虑到当前行的状态有两种:
1.当前行在这一次的求解中还没试探过,这时考虑放皇后的列位置应该从0开始。
2.当前行在这一次的求解中已经放过皇后,这时再处在当前行是上一步回溯而来,那么这时再试探当前行放皇后的可能列位置应该从上一次在当前行试过的列位置的下一列开始考虑。
向前推进一步应该是记住当前行放皇后的列位置,并且在棋盘上做标记,然后将当前行加1。这时要记住如果当前行曾经放过皇后,那么要先消除上一次放皇后的标记。
回溯一步应该是置当前行没有放过皇后,然后将当前行减1.这时要记住如果当前行曾经放过皇后,那么要先消除上一次放皇后的标记。

经过这种细化之后,应该很容易写出求出八皇后问题一个解的算法,如果要求出所有的解则可以通过在最后一步再回溯而得到。再回溯的意思是说,在最后一步再从上一个解的列位置的下一位置再考虑放皇后,如果没有下一列位置,那么回溯到上一行。
下面的程序使用一个类QUEEN来封装数组solution、board以及当前行(current_row)和当前列(current_col)。reset()来设置初始状态,select_place()来选择当前行放皇后的位置,forward()向前推进一步,backward()回溯一步。get_first_solution()用来给出八皇后问题的第一个解,get_next_solution()用来给出下一个解,如果没有解返回FALSE,主函数可用一个循环来求出所有解,display_solution()显示一个解。为节省篇幅起见,我们将类的界面、实现及演示放在了同一文件。



求解八皇后问题的程序
//文件:QUEEN.CPP
// 功能:使用回溯算法来求八皇后问题的所有解
#include <iostream.h>
enum BOOLEAN {
FALSE = 0,
TRUE = 1
};
// 表示棋盘状态的枚举类型
enum STATUS {
EMPTY = 0,
HAVE = 1
};
const int max_length = 20;
class QUEEN {
public:
// FUNCTION: 设置棋盘的宽度,即皇后的个数,并设置初始状态。
QUEEN(int length = 8);
// FUNCTION: 设置初始状态
void reset();
// FUNCTION: 给出八皇后问题的第一个解
// RETURN: 如果有解返回TRUE,否则返回FALSE
BOOLEAN get_first_solution();
// FUNCTION: 给出八皇后问题的下一个解

// REQURE: 在第一次调用之前必需先调用get_first_solution()
// RETURN: 如果有下一个解返回TRUE,否则返回FALSE
BOOLEAN get_next_solution();
// FUNCTION: 用直观的方式显示八皇后问题的一个解
// REQURE: 必需先调用get_first_solution()或get_next_solution
void display_solution();
private:
// FUNCTION:选择当前行(current_row)放皇后的列位置,选择的结果在(current_col)
// RETURN: 如果有可以放皇后的位置饭后TRUE,否则返回FALSE
BOOLEAN select_place();
// FUNCTION: 从当前行(current_row)推进到下一行
void forward();
// FUNCTION: 从当前行(current_row)回溯到上一行
void backward();
// FUNCTION: 判断是否回溯到初始状态
// RETURN: 如果回到初始状态返回TRUE,否则返回FALSE
BOOLEAN back_to_start();
// FUNCTION: 判断是否推进到最后一步
// RETURN: 如果推进到最后一步返回TRUE,否则返回FALSE
BOOLEAN is_end();
// FUNCTION: 判断在第row行、第col列是否能够放皇后
// RETURN: 如果能够放皇后返回TRUE,否则返回FALSE
BOOLEAN check(int row, int col);
// 模拟棋盘及皇后放置的情况

STATUS board[max_length][max_length];
// 记住每行放皇后的列位置
int solution[max_length];
// 当前正在试探的行和列
int current_row;
int current_col;
// 皇后的个数及棋盘的宽度
int length;
};
QUEEN::QUEEN(int length)
{
if (length > max_length) length = max_length;
QUEEN::length = length;
reset();
}
void QUEEN::reset()
{
int i, j;
for (i = 0; i < length; i++)
for (j = 0; j < length; j++) board[i][j] = EMPTY;
for (i = 0; i < length; i++) solution[i] = length;
current_row = 0;

current_col = 0;
}
void QUEEN::display_solution()
{
int row, col;
for (col = 0; col < length; col++) cout << "+---"; cout<< "+\n";
for (row = 0; row < length; row++) {
for (col = 0; col < length; col++) {
if (col == solution[row]) cout << "| Q ";
else cout << "| ";
}
cout << "|\n";
for (col = 0; col < length; col++) cout << "+---"; cout<< "+\n";
}
}
BOOLEAN QUEEN::get_first_solution()
{
reset();
return get_next_solution();
}
BOOLEAN QUEEN::get_next_solution()
{
// 从上一解回溯,实际上这里的条件current_row >=length为永真
if (current_row >= length) current_row = length - 1;
// 使用回溯算法求问题的一个解
do {
if (select_place()) forward();
else backward();
} while (!back_to_start() && !is_end());
if (back_to_start()) return FALSE;
else return TRUE;
}
BOOLEAN QUEEN::select_place()
{
if (solution[current_row] >= length) current_col = 0; //当前行没有试探过,从第0列开始
else current_col = solution[current_row] + 1; //从上一次试探的下一列重新开始
// 选择在当前行可放皇后的列
while (!check(current_row, current_col) && current_col <length) {
current_col = current_col + 1;
}
if (current_col >= length) return FALSE; //当前行没有可放皇后的列位置
else return TRUE;
}

void QUEEN::forward()
{
if (solution[current_row] < length) {
// 当前行在上一次试探时放过皇后,要清除上一次放皇后的标记
board[current_row][solution][current_row]] = EMPTY;
}
solution[current_row] = current_col; // 记住当前行放皇后的列
board[current_row][current_col] = HAVE; //标记该位置放置了皇后
current_row = current_row + 1; // 推进到下一行
}
void QUEEN::backward()
{
if (solution[current_row] < length) {
// 当前行曾经放过皇后,清除放皇后的标记
board[current_row][solution][current_row]] = EMPTY;
// 标记该行没有试探过,或者说要从第0列再重新试探
solution[current_row] = length;
}
current_row = current_row - 1; // 回溯到上一行
}
BOOLEAN QUEEN::back_to_start()
{
if (current_row < 0) return TRUE;
else return FALSE;
}

BOOLEAN QUEEN::is_end()
{
if (current_row >= length) return TRUE;
else return FALSE;
}
BOOLEAN QUEEN::check(int row, int col)
{
int temp_row, temp_col;
// 判断同一列是否已经放置皇后
for (temp_row = row; temp_row >= 0; temp_row = temp_row -1)
if (board[temp_row][col] == HAVE) return FALSE;
// 判断左上斜线是否已经放置皇后
for (temp_row = row, temp_col = col;
temp_row >= 0 && temp_col >= 0;
temp_row = temp_row - 1, temp_col = temp_col - 1) {
if (board[temp_row][temp_col] == HAVE) return FALSE;
}
// 判断右上斜线是否已经放置皇后
for (temp_row = row, temp_col = col;
temp_row >= 0 && temp_col < length;
temp_row = temp_row - 1, temp_col = temp_col + 1) {
if (board[temp_row][temp_col] == HAVE) return FALSE;
}
return TRUE;

int main()
{
QUEEN queen(8);
int count = 0; // 记录解的数目
if (queen.get_first_solution()) { // 求第一解
queen.display_solution();
count++;
} else cout << "Can not find any solution!\n";
while (queen.get_next_solution()) { //通过不断地求下一个解来得到所有解
queen.display_solution();
count++;
}
cout << "Find " << count << "solutions!\n";
return 0;
}


0 0
原创粉丝点击