n皇后问题(回溯法-递归法和循环法,最小冲突法(较快解决10000级别问题))

文章目录

n皇后问题简单解释

对于一个n*n的棋盘来说,皇后如果是同行同列或者是在同一斜对角上,就是会相互击杀。

所以,我们需要找到一个安全的(使得所有皇后之间不相互击杀)安排方式。

递归版本

#include <iostream>
using namespace std;
#include <cmath>
int n;
bool place(int x[], int k) {
	for (int i = 1; i < k; ++i)
		if (x[i] == x[k] || (abs(x[i] - x[k]) == abs(i - k))) return false;
	return true;
}

void n_queens(int k, int x[]) {
	if (k > n) {
		for (int i = 1; i <= n; ++i) cout << x[i] << " ";
		cout << endl;
	}
	else {
		for (int i = 1; i <= n; ++i) {
			x[k] = i;
			if (place(x, k)) n_queens(k + 1, x);
		}
	}
}
int main() {
	cin >> n;
	int *x = new int[n+1];
	n_queens(1, x);
	delete[]x;
	system("pause");
}

输入一个n,就可以输出对应的n皇后的安排方式。

4
2 4 1 3
3 1 4 2

上面的数组含义,即,在第i行皇后在第x[i]列。

非递归版本-找到一个可行解版本

#include <iostream>
using namespace std;
#include <cmath>
int n;
bool place(int x[], int k) {
	for (int i = 1; i < k; ++i)
		if (x[i] == x[k] || (abs(x[i] - x[k]) == abs(i - k))) return false;
	return true;
}

void n_queens(int k, int x[]) {
	k = 1;
	x[1] = 0;
	while (k > 0) {
		x[k] ++;
		while ((x[k] <= n) && (!place(x, k))) x[k]++;
		if (x[k] <= n) {
			if (k == n) break; // 找到了一种解
			else {
				x[++k] = 0;
			}
		}
		else {
			x[k] = 0; k--; // 回溯 这条路走不通
		}
	}
	for (int i = 1; i <= n; ++i) cout << x[i] << " ";
	cout << endl;
}
int main() {
	cin >> n;
	int *x = new int[n+1];
	n_queens(1, x);
	delete[]x;
	system("pause");
}

运行情况为

4
2 4 1 3

非递归版本-找到所有可行解版本

#include <iostream>
using namespace std;
#include <cmath>
int n;
bool place(int k, int x[]) {
	for (int i = 1; i < k; ++i)
		if (x[i] == x[k] || (abs(x[i] - x[k]) == abs(i - k))) return false;
	return true;
}

void n_queens(int x[]) {
	for (int m = 1; m <= n; ++m) x[m] = 0;
	int i = 1, j;
	while (i <= n) {
		j = x[i] + 1;
		while (j <= n) {
			x[i] = j; //将第i行的皇后放在第j列上
			if (place(i, x)) break; // 跳出
			else j++; // 寻找下一个
		}
		if (j == n + 1) { // 在这一行,找了所有列都不对 
			if (i == 1) break; // 找遍了所有方法 都没有结果
			else i--; // 回溯到上一行
		} else if (i == n) { // 全部找到了
			for (int m = 1; m <= n; ++m) cout << x[m] << " ";
			cout << endl;
		}
		else { x[++i] = 0; }
	}
}
int main() {
	cin >> n;
	int *x = new int[n + 1];
	n_queens(x);
	delete[]x;
	system("pause");
}

运行的结果;

4
2 4 1 3
3 1 4 2

非递归版本二

#include <iostream>
using namespace std;
#include <cmath>
int n;
int *x;
bool place(int x[], int k) {
	for (int i = 1; i < k; ++i)
		if (x[i] == x[k] || (abs(x[i] - x[k]) == abs(i - k))) return false;
	return true;
}

void iterativeBacktrack() {
	int t = 1;
	int *f = new int[n + 1];
	for (int i = 1; i <= n; ++i) f[i] = 1;
	while (t > 0) {
		if (f[t] <= n) {
			while (f[t] <= n){
				x[t] = f[t];
				if (place(x, t)) { // 第k位放的没有问题
					f[t]++;
					if (t == n) { // output
						for (int m = 1; m <= n; ++m) cout << x[m] << " ";
						cout << endl;
					}
					else { 
						t++; // 考虑下一行的皇后位置
						break;
					}
				} else f[t]++; 
			}
		}
		else { 
			f[t] = 1; 
			t--;
		}
	}
	delete[] f;
}

int main() {
	cin >> n;
	x = new int[n + 1];
	iterativeBacktrack();
	delete[]x;
	system("pause");
}

最小冲突算法–5000级别较快

(只能找到一个解)

学习了下面这个链接的代码,做了很详细的笔记
https://www.cnblogs.com/fstang/archive/2013/05/12/3073598.html

  • 解决n=5000的数据 20s
  • 解决n=6000的数据 80s
#include <iostream>
using namespace std;
#include <ctime>
#define MAX 6000 //最多可能皇后数
#define swap(a,b) {int t = a; a = b; b = t;}
//row[i]表示当前摆放方式下第i行的皇后数,col[i]表示当前摆放方式下第i列的皇后数
int row[MAX];
int col[MAX];

int N; //放置N个皇后在N*N棋盘上

					//从左上到右下的对角线上row-col值是相同的,但是这个值有可能是负值,最小为-(N-1),
					//所以可以做个偏移,统一加上N-1,这样这个值就在[0,2*N-2]范围内,将这个值作为该对角线的编号
					//pdiag[i]表示当前摆放方式下编号为i的对角线上的皇后数
int pdiag[2 * MAX];//principal diagonal,主对角线,左上到右下(表示和主对角线平行的2N-1条对角线)

				   //从右上到左下的对角线row+col的值相同,取值范围为[0, 2 * MAX - 2],作为对角线编号
				   //cdiag[i]表示编号为i的对角线上的皇后数
int cdiag[2 * MAX];//counter diagonal,副对角线

				   //R[]用来存储皇后放置位置,R[row] = col表示(row,col)处,即“第row行第col列”有个皇后
int R[MAX];

//给定二维矩阵的一个点坐标,返回其对应的左上到右下的对角线编号
int getP(int row, int col) {
	return row - col + N - 1;
}

//给定二维矩阵的一个点坐标,返回其对应的右上到左下的对角线编号
int getC(int row, int col) {
	return row + col;
}

//返回begin, begin + 1, ... , end - 1 这end - begin个数中的随机的一个
int my_rand(int begin, int end) {//左闭右开[begin, end)
	return rand() % (end - begin) + begin;
}

//原地shuffle算法,算法导论中的randomize in place算法
void randomize(int a[], int begin, int end)// 左闭右开
{
	for (int i = begin; i <= end - 2; i++) {
		int x = my_rand(i, end);
		swap(a[i], a[x]);
	}
}

//初始化皇后的摆放,同时初始化row,col,pdiag,cdiag数组
void init()
{
	for (int i = 0; i < N; i++) {//N queens
		R[i] = i;
	}
	randomize(R, 0, N);//初始化N个皇后对应的R数组为0~N-1的一个排列,即没有任意皇后同列,也没有任何皇后同行
	for (int i = 0; i < N; i++) {
		row[i] = 1;//每行恰好一个皇后
		col[i] = 0;//下面再做初始化的
	}
	for (int i = 0; i < 2 * N - 1; i++) {//N queens
		pdiag[i] = 0;
		cdiag[i] = 0;
	}
	for (int i = 0; i < N; i++) {//N queens
		col[R[i]]++;
		pdiag[getP(i, R[i])]++;
		cdiag[getC(i, R[i])]++;
	}
}

bool adjust_row(int row); // 调整下第row行的皇后的位置
void print_result(); // 输出结果
bool qualify(); //验证是否正确

int main(int argc, const char *argv[])
{
	srand((unsigned)time(NULL));// 设置好随机数种子
	cin >> N;
	init();
	if (!qualify()) { // 如果初始表不满足的话
		bool can_terminate = false;
		while (!can_terminate) {
			for (int i = 0; i < N; i++) {
				if (adjust_row(i)) {
					can_terminate = true;
					break;
				}
			}
		}
	}
	print_result();
	system("pause");
	return 0;
}
//用最小冲突算法调整第row行的皇后的位置(初始化时每行都有一个皇后,调整后仍然在第row行)
//调整过后check一下看看是否已经没有冲突,如果没有冲突(达到终止状态),返回true
bool adjust_row(int row) {
	int cur_col = R[row]; // 通过行得到列
	int optimal_col = cur_col;// 最佳列号,设置为当前列,然后更新
	int min_conflict = col[optimal_col]      // col不减一的原因是交换任然保证每行每列都有一个而已
		+ pdiag[getP(row, optimal_col)] - 1  // 现在还没移过去
		+ cdiag[getC(row, optimal_col)] - 1; // 对角线冲突数为当前对角线皇后数减一
	for (int i = 0; i < N; i++) {//逐个检查第row行的每个位置
		if (i == cur_col) continue; // 重复就跳过了
		int conflict = col[i] + pdiag[getP(row, i)] + cdiag[getC(row, i)];
		if (conflict < min_conflict) { // 因为之前这个点还没有移动,所以到这之后肯定是还会
			min_conflict = conflict;
			optimal_col = i;
		}
	}
	if (optimal_col != cur_col) {//要更新col,pdiag,cdiag
		col[cur_col]--;
		pdiag[getP(row, cur_col)]--;
		cdiag[getC(row, cur_col)]--;

		col[optimal_col]++;
		pdiag[getP(row, optimal_col)]++;
		cdiag[getC(row, optimal_col)]++;
		R[row] = optimal_col;// 注意,这里我们没有移动 在这个列上的之前的那个行上的皇后
		if (col[cur_col] == 1 && col[optimal_col] == 1
			&& pdiag[getP(row, optimal_col)] == 1 && cdiag[getC(row, optimal_col)] == 1) {
			return qualify();//qualify相对更耗时,所以只在满足上面基本条件后才检查
		}
	}
	//当前点就是最佳点,一切都保持不变
	return false;//如果都没变的话,肯定不满足终止条件,否则上一次就应该返回true并终止了
}
bool qualify() {
	for (int i = 0; i < N; i++) {//N queens
		if (col[R[i]] != 1 ||
			pdiag[getP(i, R[i])] != 1 ||
			cdiag[getC(i, R[i])] != 1) {
			return false;
		}
	}
	return true;
}
void print_result()
{
	cout << "the result is like this:\n";
	for (int i = 0; i < N; i++) {
		cout << R[i] << " ";
	}
	cout << endl;
	/*for (int j = 0; j < N; j++) { for (int k = 0; k < N; k++) { if (R[j] == k) cout << "*"; else cout << "-"; } cout << endl; }*/
}

最小冲突算法改进

根据观测,有两个函数经常调用,但是非常短。由于函数调用本身设计到压栈一直都

  • 解决1w的数据需要6秒
#include <iostream>
using namespace std;
#include <ctime>
#define MAX 1000005 //最多可能皇后数
#define swap(a,b) {int t = a; a = b; b = t;}
#define getP(row, col) (row - col + N - 1)
#define getC(row, col) (row + col)
//row[i]表示当前摆放方式下第i行的皇后数,col[i]表示当前摆放方式下第i列的皇后数
int row[MAX];
int col[MAX];

int N; //放置N个皇后在N*N棋盘上

					//从左上到右下的对角线上row-col值是相同的,但是这个值有可能是负值,最小为-(N-1),
					//所以可以做个偏移,统一加上N-1,这样这个值就在[0,2*N-2]范围内,将这个值作为该对角线的编号
					//pdiag[i]表示当前摆放方式下编号为i的对角线上的皇后数
int pdiag[2 * MAX];//principal diagonal,主对角线,左上到右下(表示和主对角线平行的2N-1条对角线)

				   //从右上到左下的对角线row+col的值相同,取值范围为[0, 2 * MAX - 2],作为对角线编号
				   //cdiag[i]表示编号为i的对角线上的皇后数
int cdiag[2 * MAX];//counter diagonal,副对角线

				   //R[]用来存储皇后放置位置,R[row] = col表示(row,col)处,即“第row行第col列”有个皇后
int R[MAX];

////给定二维矩阵的一个点坐标,返回其对应的左上到右下的对角线编号
//int getP(int row, int col) {
// return row - col + N - 1;
//}
//
////给定二维矩阵的一个点坐标,返回其对应的右上到左下的对角线编号
//int getC(int row, int col) {
// return row + col;
//}

//返回begin, begin + 1, ... , end - 1 这end - begin个数中的随机的一个
int my_rand(int begin, int end) {//左闭右开[begin, end)
	return rand() % (end - begin) + begin;
}

//原地shuffle算法,算法导论中的randomize in place算法
void randomize(int a[], int begin, int end)// 左闭右开
{
	for (int i = begin; i <= end - 2; i++) {
		int x = my_rand(i, end);
		swap(a[i], a[x]);
	}
}

//初始化皇后的摆放,同时初始化row,col,pdiag,cdiag数组
void init()
{
	for (int i = 0; i < N; i++) {//N queens
		R[i] = i;
	}
	randomize(R, 0, N);//初始化N个皇后对应的R数组为0~N-1的一个排列,即没有任意皇后同列,也没有任何皇后同行
	for (int i = 0; i < N; i++) {
		row[i] = 1;//每行恰好一个皇后
		col[i] = 0;//下面再做初始化的
	}
	for (int i = 0; i < 2 * N - 1; i++) {//N queens
		pdiag[i] = 0;
		cdiag[i] = 0;
	}
	for (int i = 0; i < N; i++) {//N queens
		col[R[i]]++;
		pdiag[getP(i, R[i])]++;
		cdiag[getC(i, R[i])]++;
	}
}

bool adjust_row(int row); // 调整下第row行的皇后的位置
void print_result(); // 输出结果
bool qualify(); //验证是否正确

int main(int argc, const char *argv[])
{
	srand((unsigned)time(NULL));// 设置好随机数种子
	clock_t startTime, endTime;
	cin >> N;
	
	startTime = clock();
	init();
	if (!qualify()) { // 如果初始表不满足的话
		bool can_terminate = false;
		while (!can_terminate) {
			for (int i = 0; i < N; i++) {
				if (adjust_row(i)) {
					can_terminate = true;
					break;
				}
			}
		}
	}
	endTime = clock();
	print_result();
	cout << "用时:" << 1.0*(endTime - startTime) / CLOCKS_PER_SEC <<"秒"<< endl;
	system("pause");
	return 0;
}
//用最小冲突算法调整第row行的皇后的位置(初始化时每行都有一个皇后,调整后仍然在第row行)
//调整过后check一下看看是否已经没有冲突,如果没有冲突(达到终止状态),返回true
bool adjust_row(int row) {
	int cur_col = R[row]; // 通过行得到列
	int optimal_col = cur_col;// 最佳列号,设置为当前列,然后更新
	int min_conflict = col[optimal_col]      // col不减一的原因是交换任然保证每行每列都有一个而已
		+ pdiag[getP(row, optimal_col)] - 1  // 现在还没移过去
		+ cdiag[getC(row, optimal_col)] - 1; // 对角线冲突数为当前对角线皇后数减一
	for (int i = 0; i < N; i++) {//逐个检查第row行的每个位置
		if (i == cur_col) continue; // 重复就跳过了
		int conflict = col[i] + pdiag[getP(row, i)] + cdiag[getC(row, i)];
		if (conflict < min_conflict) { // 因为之前这个点还没有移动,所以到这之后肯定是还会
			min_conflict = conflict;
			optimal_col = i;
		}
	}
	if (optimal_col != cur_col) {//要更新col,pdiag,cdiag
		col[cur_col]--;
		pdiag[getP(row, cur_col)]--;
		cdiag[getC(row, cur_col)]--;

		col[optimal_col]++;
		pdiag[getP(row, optimal_col)]++;
		cdiag[getC(row, optimal_col)]++;
		R[row] = optimal_col;// 注意,这里我们没有移动 在这个列上的之前的那个行上的皇后
		if (col[cur_col] == 1 && col[optimal_col] == 1
			&& pdiag[getP(row, optimal_col)] == 1 && cdiag[getC(row, optimal_col)] == 1) {
			return qualify();//qualify相对更耗时,所以只在满足上面基本条件后才检查
		}
	}
	//当前点就是最佳点,一切都保持不变
	return false;//如果都没变的话,肯定不满足终止条件,否则上一次就应该返回true并终止了
}
bool qualify() {
	for (int i = 0; i < N; i++) {//N queens
		if (col[R[i]] != 1 ||
			pdiag[getP(i, R[i])] != 1 ||
			cdiag[getC(i, R[i])] != 1) {
			return false;
		}
	}
	return true;
}
void print_result()
{
	cout << "the result is like this:\n";
	for (int i = 0; i < N; i++) {
		cout << R[i] << " ";
	}
	cout << endl;
	/*for (int j = 0; j < N; j++) { for (int k = 0; k < N; k++) { if (R[j] == k) cout << "*"; else cout << "-"; } cout << endl; }*/
}

关于更高性能的算法

下面的这个算法,是可以在20s内接触10w的数据规模的。
https://blog.csdn.net/yongnuzhibu/article/details/7178112

    原文作者:回溯法
    原文地址: https://blog.csdn.net/a19990412/article/details/83304430
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞