N皇后问题是一个经典的问题,在很多地方都有讨论过。回溯法是经典的解法,但是随着N的增大,其复杂度的增加呈指数增长,如果N=100使用回溯解法的话,回溯要运行的时间估计你可以去喝一壶茶了。
这段时间在看《人工智能》,里面也有对其的讨论,介绍了爬山法在N皇后问题中的应用。爬山法是一种向值增加的方向持续移动到简单循环过程,它将会在到达一个“峰顶”时终止,此时相邻状态中没有比该它更高的值。这个算法不维护搜索树。
最基本的爬上搜索算法(节选自《人工智能》第二版):
function HILL-CLIMBING(problem) return a state thate is a locak maximum
inputs: problem
local variables: current, a node
neighbor,a node
current = MakeNode(INITAL-STATE(problem));
loop do
neighbor = a highest-valued successor of current ;
if VALUE[neighbor] <= VALUE[current] then return STATE[current];
current = neighbor ;
爬山法属于一种局部的贪婪搜索方法,当它在搜索过程中遇到了局部最大值就很难继续向下搜索了。因此产生了爬山法的变种如随机爬山法及其变种如随机爬山法,随机重新开始的爬山法,模拟退火搜索能够非常有效的解决N皇后问题。
结合实际,我使用了随机爬山法的一个测试程序,不试不知道,测试发现速度果然是非常的快,对于100皇后都是瞬间秒杀。不过这个程序要实现百万皇后的问题秒杀,估计还是一项很艰巨的工作。不过随机爬山法这种方法是一种很有效的解决复杂搜索问题的方法之一。
程序如下,供以后参考:
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <iterator>
#include <vector>
typedef std::vector<int> CollisionList_t;
void print_row_mark(int N)
{
for (int i=0; i<N; ++i) {
std::cout << "+---";
}
std::cout << "+" << std::endl;
}
void print_row(int N, int fill)
{
for (int i=0; i<N; ++i) {
std::cout << "| " << ((i==fill) ? 'X' : ' ') << " ";
}
std::cout << "|" << std::endl;
print_row_mark(N);
}
// 皇后位置的表示方法:
// 使用数组chessman[N]来表示N个皇后的位置
// 第i个皇后chessman[i]的下标i表示其行所在的位置,
// chessman[i]表示其列的位置。
//
// 一个四皇后问题的表示方法如下所示:
// (0, 1) (1, 3) (2, 0) (3, 2)
// +---+---+---+---+
// | | X | | |
// +---+---+---+---+
// | | | | X |
// +---+---+---+---+
// | X | | | |
// +---+---+---+---+
// | | | X | |
// +---+---+---+---+
//
void print_chessboard(int* chessman, int N)
{
for (int i=0; i<N; ++i) {
std::cout << "(" << i << ", " << chessman[i] << ") ";
}
std::cout << std::endl;
print_row_mark(N);
for (int i=0; i<N; ++i) {
print_row(N, chessman[i]);
}
}
// 随机生成一个初始化状态,在每行每列上放置一个皇后
void generate_init_state(int* chessman, int N)
{
for (int i=0; i<N; ++i) {
chessman[i] = i;
}
for (int i=0; i<N; ++i) {
int r = rand();
r = r % N;
std::swap(chessman[r], chessman[N-r-1]);
}
}
// 返回冲突的皇后个数
int h(int* chessman, int N, CollisionList_t& collision_list)
{
collision_list.clear();
int collision = 0;
for (int i=0; i<N; ++i) {
for (int row=i+1; row<N; row++) {
if ((chessman[row] == chessman[i] + row - i)
|| (chessman[row] == chessman[i] - (row - i))) {
collision_list.push_back(row);
++collision;
}
}
}
return collision;
}
// 如果交换后冲突不比原来的大,就进行交换
// 只有交换成功后才改变cl为新的冲突列表
int try_exchange(int* chessman, int N, int row1, int row2, CollisionList_t& cl)
{
CollisionList_t new_cl;
// 交换两行的皇后的位置
std::swap(chessman[row1], chessman[row2]);
int new_collision = h(chessman, N, new_cl);
if (new_collision > cl.size()) {
// 取消之前的交换
std::swap(chessman[row1], chessman[row2]);
}
else {
cl = new_cl;
}
return new_cl.size();
}
int choose_next_state(int* chessman, int N, CollisionList_t& cl)
{
int old_collision = cl.size();
int new_collision;
int row1 = -1;
int row2 = -1;
// 优化最后只有一个冲突的解
if (cl.size() == 1) {
for (int i=0; i<N; ++i) {
if (i != cl[0] && (try_exchange(chessman, N, cl[0], i, cl) == 0)) {
return 0;
}
}
}
do {
// 最后的选择,随机的选择两个皇后调换其位置
row1 = rand() % N;
do {
row2 = rand() % N;
} while (row1 == row2);
new_collision = try_exchange(chessman, N, row1, row2, cl);
} while (new_collision > old_collision);
return new_collision;
}
// 使用随机爬山法寻找一个N皇后问题的解
int queue_solution(int N)
{
int* chessman = new int[N];
int max_tries = N*N;
int max_steps = N*N;
int tries = 0;
while (tries < max_tries) {
++tries;
int steps = 0;
int collision;
CollisionList_t collision_list;
srand(time(NULL) + tries * collision);
generate_init_state(chessman, N);
collision = h(chessman, N, collision_list);
while ((collision != 0) && (steps<max_steps)) {
collision = choose_next_state(chessman, N, collision_list);
++steps;
}
if (collision == 0) {
std::cout << "Found a solution. Tries: " << tries
<< " Steps: " << steps << std::endl;
print_chessboard(chessman, N);
return 1;
}
}
return 0;
}
// 接受一个命令行参数,要求为整数N,表示要寻找的解是N皇后问题。
int main(int argc, char* argv[])
{
int N = 8; // 缺省为寻找8皇后问题的一个解
if (argc == 2) {
N = atoi(argv[1]);
std::cout << "N: " << N << std::endl;
}
if (N <= 0) {
std::cout << "Input error: parameter must be a postive integer" << std::endl;
return 1;
}
srand(time(NULL));
int result = queue_solution(N);
if (result != 1) {
std::cout << "Failed, please try re-run to get a solution."
<< std::endl;
}
return 0;
}
TODO:在选择下一步的时候还有改进空间,比如优先从冲突的皇后中随机选择皇后进行下一次的位置调换等。