算法分析 | 回溯法 | N皇后问题

一.问题分析

N×N的棋盘上有N个皇后,皇后的攻击范围是同一行,同一列和对角线.

N个皇后要如何安全排列?

在一个二维数组中,一行一行地看,观察第 i 行的第 i 个皇后能放在第几列.也就是说,默认每个皇后不可能出现在同一行.

↑显约束.

所以,在判断第t个皇后时,只需判断是否

a.列冲突:x[t]==x[i]                          i的范围是1~t-1 

b.对角线冲突:t-i==abs(x[t]-x[i])      i的范围是1~t-1 

↑隐约束

 

二.代码实现

分为三个部分

1.全局变量区

2.约束函数

3.递归函数

4.调用递归函数

 

1.全局变量

#include<cmath>

const int M = 20;
int n;//表示n个皇后
int x[M];//例如:x[2]=5表示第2行的皇后在第5列,数组稍宽些会少许多麻烦
int countn=0;//n皇后解的个数

 

2.约束函数

//形参t表示第t个皇后能否与前0~t-1个皇后不冲突

bool Place(int t)//第t个皇后能否放在第i个位置
{
	bool flag = true;
	for (int i = 1; i < t; i++)//观察是否与第1~t-1个皇后冲突
	{
		//在同一列 或 对角线 上
		if (x[t] == x[i] || t - i == fabs(x[t] - x[i]))
		{
			flag = false;
			break;
		}
	}

	return flag;
}

 

3.递归函数

一个形式上很简洁的递归.

尚未遍历到第n行时,遍历每一列,查找允许的安全位置

能够遍历到第n行,说明得出一个解,打印出来

"数组是从0开始的" 这一点有时候很棘手,不如在设置数组时多设置一单位长度.统一从1开始

void queen(int t)		//当前遍历的第t个皇后
{
	if (t > n2)
	{
		countn++;//表示找到一个解
		for (int i = 1; i <= n2; i++)
		{
			cout << x[i] << "\t";
		}
		cout << endl;
		cout << "------------" << endl;
	}
	else
	{
		for (int i = 0; i < n2; i++)//分别判断n个分支
		{
                        l[t] = i;//※(第一次的bug)若不添加该语句,x[]始终处于刚刚初始化的状态即memset(x,0,sixeof(x)),程序无法运行出结果
			if (Place(t))
			{
				queen(t + 1);
			}
		}
	}
}

4.main()

cout << "请输入1~20以内的数作为皇后个数:";
cin >> n2;//Queen.h定义的变量,也可以像函数那样被main()调用
if (n2 > 20) 
{
    cout << "输入值偏大,请重新输入:";
    cin >> n2;
    }
queen(1);
cout << "解的个数:	" << countn;

 

三.拓展优化

回溯法的优化,就是要减小解空间的大小,精简m叉树.具体来说,就是将隐约束 变成 显约束.

在以上代码中,0~n-1的每一个分支都要判断到并递归,这显然是极大的浪费.

通过隐约束可知,不可能是同一列,可以设置一个bool live[n]数组用于判断之前元素占的是哪一列.

在for()里嵌一个if()判断来减少循环次数.

最后效果如图:

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章