问题描述:在一个N * N的棋盘中放置N个皇后,使得每个皇后都不在同一行、同一列和同一斜线上,总共有多少种放置方法。
思路:遍历棋盘的每一行,在该行中遍历列来寻找合适的位置放置皇后。如果找到合适的位置,放置皇后之后,跳到下一行。否则,回溯到上一行,找到该行下一个合适的列来放置皇后,再往下遍历。
在编程的具体实现中,可以用二维数组或一维数组两种方式来表示棋盘的行和列,回溯法也有递归和非递归(即迭代)两种方法来实现。
通过测试程序运行时间的比较,我们可以得出结论:使用迭代的程序比使用递归的程序快,使用一维数组的程序·比使用二维数组的程序快。
代码如下:
//----------------------------------【n皇后问题】------------------------------------
#include <iostream>
#include <vector>
#include <time.h>
#define UNPUT -1
using namespace std;
//-------------------------【方法1:回溯法递归 + 二维数组】--------------------------
class NQueens1
{
public:
NQueens1(int num = 8)
{
n = num;
count = 0;
for(int i = 0; i < n; ++i)
{
vector<bool> temp;
for(int j = 0; j < n; ++j)
{
temp.push_back(false);
}
board.push_back(temp);
}
}
// 利用回溯法递归
void put(int i = 0)
{
if(i == n)
return;
else
{
for(int j = 0; j < n; ++j)
{
board[i][j] = true;
if(isSafe(i, j))
{
put(i+1);
}
board[i][j] = false;
}
}
}
// 测试程序执行时间
void test()
{
clock_t start = clock();
put(0);
clock_t end = clock();
double totol_time = end - start;
printf("%f\n", totol_time);
}
private:
// 确定row行col列的皇后位置是否合法
bool isSafe(int row, int col) const
{
for(int i = 0; i < row; ++i)
{
for(int j = 0; j < n; ++j)
{
if(board[i][j] == true && (j == col
|| abs(row-i) == abs(col-j)))
{
return false;
}
}
}
return true;
}
// 打印二维数组中皇后的位置
void print()
{
printf("case%d\n", ++count);
for(int i = 0; i < n; ++i)
{
for(int j = 0; j < n; ++j)
{
board[i][j] ? printf("%c ", 2) : printf(". ");
}
cout << endl;
}
}
private:
int n; // 皇后个数,也是行数和列数
int count; // 解的个数
vector<vector<bool>> board; // 二维数组,表示某位置是否放置皇后
};
//-------------------------【方法2:回溯法递归 + 一维数组】--------------------------
class NQueens2
{
public:
NQueens2(int num = 8)
{
n = num;
count = 0;
for(int i = 0; i < n; ++i)
{
for(int j = 0; j < n; ++j)
{
colIndex.push_back(UNPUT);
}
}
}
// 利用回溯法递归
void put(int i = 0)
{
if(i == n)
return;
else
{
for(int j = 0; j < n; ++j)
{
colIndex[i] = j;
if(isSafe(i))
put(i+1);
colIndex[i] = UNPUT;
}
}
}
// 测试程序执行时间
void test()
{
clock_t start = clock();
put(0);
clock_t end = clock();
double totol_time = end - start;
printf("%f\n", totol_time);
}
private:
// 确定皇后位置是否合法
bool isSafe(int row) const
{
for(int i = 0; i < row; ++i)
{
if(colIndex[i] >= 0 && (colIndex[i] == colIndex[row]
|| abs(row-i) == abs(colIndex[row]-colIndex[i])))
{
return false;
}
}
return true;
}
// 打印所有皇后位置
void print()
{
printf("case%d\n", ++count);
for(int i = 0; i < n; ++i)
{
for(int j = 0; j < n; ++j)
{
if(j == colIndex[i])
printf("%c ", 2);
else
printf(". ");
}
cout << endl;
}
}
private:
int n; // 皇后个数
int count; // 解的个数
vector<int> colIndex; // 一维数组,表示在i行colIndex[i]列处放置皇后,
// colIndex[i] = -1 表示不放置皇后
};
//-------------------------【方法3:回溯法非递归 + 一维数组】--------------------------
class NQueens3
{
public:
// 初始化
NQueens3(int num = 8)
{
n = num;
count = 0;
for(int i = 0; i < n; ++i)
{
board.push_back(UNPUT);
}
}
// 放置皇后
void put()
{
int i = 0, j = 0; // i代表当前行,j代表当前列
while(i < n)
{
while(j < n)
{
if(isSafe(i, j))
{
board[i] = j; // 若当前位置合法,则放置皇后
j = 0; // 当前列重新设为0,即为棋盘第一列
break;
}
else
{
++j; // 若当前位置不合法,则寻找该行下一列位置
}
}
if(board[i] == UNPUT) // 若在该行没有合适位置
{
if(i == 0) // 若为第一行,则无需回溯
break;
else
{
i--; // 若不是第一行,则回到上一行
j = board[i] + 1; // 并找到该行下一列位置
board[i] = UNPUT; // 将该行原来放置的皇后去掉
continue;
}
}
if(i == n-1) // 若是最后一行
{
//print(); // 打印该解
j = board[i] + 1; // 找到该行下一列位置
board[i] = UNPUT; // 将该行原来位置的皇后去掉
continue;
}
++i;
}
}
// 测试程序
void test()
{
clock_t start = clock();
put();
clock_t end = clock();
double totol_time = end - start;
printf("%f\n", totol_time);
}
private:
// 判断该位置是否合法
bool isSafe(int row, int col) const
{
for(int i = 0; i < n; ++i)
{
if(board[i] >= 0 && (board[i] == col
|| abs(row-i) == abs(col-board[i])))
return false;
}
return true;
}
// 打印当前解
void print()
{
printf("case%d\n", ++count);
for(int i = 0; i < n; ++i)
{
for(int j = 0; j < n; ++j)
{
if(j == board[i])
printf("%c ", 2);
else
printf(". ");
}
cout << endl;
}
}
private:
int n; // 皇后个数
int count; // 解的个数
vector<int> board; // 一维数组代表棋盘
};
// 测试和比较三种方法
int main(int argc, char const *argv[])
{
NQueens1 q1;
NQueens2 q2;
NQueens3 q3;
q1.test();
q2.test();
q3.test();
return 0;
}