皇后问题,8皇后、n皇后、2n皇后

8皇后问题

问题描述:

八皇后问题,是一个古老而著名的问题,是回溯算法的典型案例。
该问题是国际西洋棋棋手马克斯·贝瑟尔于1848年提出:
在8×8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。
高斯认为有76种方案。1854年在柏林的象棋杂志上不同的作者发表了40种不同的解,后来有人用图论的方法解出92种结果。
计算机发明后,有多种计算机语言可以解决此问题。

分析:

一、八重循环列举出所有的结果,在其中筛选满足条件的结果,不过显而易见,这样的话效率会很低,这里不再赘述
二、深度优先遍历搜索,以行为基准,在每一行找满足条件的点,若找到,则进入下一行继续找,若没有找到,则回溯到上一行切入点的后边继续找,直到在尾行找到满足条件的点为止,,则一组序列已经找到,继续回溯到上一行,找其他组的序列,若倒数第二行已经全部找完所有可能的序列,则继续回溯,继续dfs搜索…,直到找到所有组合为止
基于这种思想,可以用递归+回溯的思想或者迭代+回溯的思想进行解决

具体的算法细节及分析都在代码注释中表明!

代码:

//************递归+回溯

#include
using namespace std;
#include

int queen[8] = { 0 };  //用一维数组来维护皇后落子的点,例如,queen[1] = 5表示第2行第6列有一个落子点(从0开始)
int _count = 0;

// 检测冲突,若冲突返回真
//    行 --------- 列
// point_r ----- point_c   要检测的行和列(确定的点)
//   row ------- queen[row]  前n行已经找出来的点,看point是否与之前找出来的行列冲突
bool IsAttack(int point_r, int point_c)
{
	for (int row = 0; row < point_r; ++row)
	{
		//由于是在把前n-1行中确定的每个点 与 第n行中的point点进行比较,所以不需要进行同一行的判断,肯定不相等
		//只需检测 point点 是否与 前n-1行中的每个点 列号相等   或者  斜率相等,即在同一对角线上
		if (point_c == queen[row] || (abs(queen[row] - point_c) == point_r - row))
		{
			return true;
		}
	}
	return false;
}

//找点,递归,回溯
void find(int row)  //row为行号
{
	for (int col = 0; col < 8; ++col)  //遍历第row行中的每一列,确定当前的选中点
	{
		if (!IsAttack(row, col))  //不起冲突时
		{
			if (row == 7)  //已经进行到第7行,且第7行中的点已经确定时,代表一种组合已经确定,计数器++;回溯,返回第6层找其他的组合
			{
				_count++;
				return;
			}
			queen[row] = col;  
			find(row + 1);  //递归,没在尾行,且已经确定当前行的具体点,进入下一行的查找
		}
	}
	//最内层的循环确定,回溯到其他层,继续查找其他组合
}

int main()
{
	find(0);
	cout << _count << endl;

	return 0;
}


//************迭代+回溯

#include
using namespace std;
#include

int queen[8] = { -1 };
int _count = 0;


bool IsAttack(int point_r, int point_c)
{
	for (int row = 0; row < point_r; ++row)
	{
		if (point_c == queen[row] || abs(point_c - queen[row]) == point_r - row)
		{
			return true;
		}
	}
	return false;
}

void find()
{
	int row = 0, col = 0;
	while (row < 8)  //控制行
	{
		while (col < 8)  //控制列
		{
			if (!IsAttack(row, col))  //当前的点不起冲突时
			{
				queen[row] = col;  
				if (row == 7)  //检测当前点是否在尾行,即是否找到一组完整的解
				{
					_count++;  //计数器计数
					col++;  //循环自增到不满足条件,跳转到if(col==8)这一代码块中进行回溯处理,用时间换代码冗余量
//					queen[row] = -1;
				}
				else
				{
					col = 0;  //去下一行
					break;
				}
			}
			else
			{
				col++;  //当前点不满足,找当前行的下一个点
			}
		}
		//回溯
		if (col == 8)  //当前待检测的点越界
		{
			row--;  //回溯一行
			if (row < 0)  //若回溯出界,则程序运行结束
			{
				return;
			}
			else
			{
				col = queen[row];  
				col++;  //在上一行已确定的点之后继续找
//				queen[row] = -1;
				continue;
			}
		}
		row++;  
	}
}

int main()
{
	find();
	cout << _count << endl;

	return 0;
}

n皇后问题

其实要是完全理解了8皇后的思路,就会发现这两个问题其实是一样的处理办法,甚至代码都是一样的,只不过将8改为n就好,不过,为了充门面,还是把源码贴出来叭…

代码

//************递归 + 回溯
#include
using namespace std;
#include

int n = 0;  //n个皇后
int queen[10] = { -1 };
int _count = 0;

bool IsAttack(int point_r, int point_c)
{
	for (int row = 0; row < point_r; row++)
	{
		if (point_c == queen[row] || abs(point_c - queen[row]) == point_r - row)
		{
			return true;
		}
	}
	return false;
}

void find(int row)
{
	for (int col = 0; col < n; col++)
	{
		if (!IsAttack(row, col))
		{
			if (row == n - 1)
			{
				_count++;
				return;
			}
			queen[row] = col;
			find(row + 1);
		}
	}
}

int main()
{
	cin >> n;
	find(0);
	cout << _count << endl;

	return 0;
}
// *********************迭代+回溯
#include
using namespace std;
#include

int n = 0;  //n个皇后
int queen[10] = { -1 };
int _count = 0;

bool IsAttack(int point_r, int point_c)
{
	for (int row = 0; row < point_r; row++)
	{
		if (point_c == queen[row] || abs(point_c - queen[row]) == point_r - row)
		{
			return true;
		}
	}
	return false;
}

void find()
{
	int row = 0, col = 0;
	while (row < n)
	{
		while (col < n)
		{
			if (!IsAttack(row, col))
			{
				queen[row] = col;
				if (row == n - 1)
				{
					_count++;
					col++;
				}
				else
				{
					col = 0;
					break;
				}
			}
			else
			{
				col++;
			}
		}
		if (col == 8)
		{
			row--;
			if (row < 0)
			{
				return;
			}
			col = queen[row];
			col++;
			continue;
		}
		row++;
	}
}

int main()
{
	cin >> n;
	find();
	cout << _count << endl;

	return 0;
}

2n皇后问题

问题描述

问题描述
  给定一个n*n的棋盘,棋盘中有一些位置不能放皇后。现在要向棋盘中放入n个黑皇后和n个白皇后,使任意的两个黑皇后都不在同一行、同一列或同一条对角线上,任意的两个白皇后都不在同一行、同一列或同一条对角线上。问总共有多少种放法?n小于等于8。
输入格式
   输入的第一行为一个整数n,表示棋盘的大小。
   接下来n行,每行n个0或1的整数,如果一个整数为1,表示对应的位置可以放皇后,如果一个整数为0,表示对应的位置不可以放皇后。
输出格式
       输出一个整数,表示总共有多少种放法。
样例输入
4
1 1 1 1
1 1 1 1
1 1 1 1
1 1 1 1
样例输出
2
样例输入
4
1 0 1 1
1 1 1 1
1 1 1 1
1 1 1 1
样例输出
0

具体思路

其实吧,2n皇后就是n皇后问题的加强版,不过是需要在n皇后的基础上在加上一层n皇后。

首先,用一个数组将有限制的地址存储,等待后面的判断
其次,类似n皇后一样,分别用两个一维数组将黑白皇后的落子序列存储起来,数组下标为对应的行号,通过下标访问的值为列号
然后,类似n皇后,先深度优先遍历搜索将一个皇后的落子序列平铺出来,在此基础上,对另一个皇后的所有落子情况进行深度优先遍历搜索,当搜索到尾行且此时落子存在时,表示当前的一种解法可以将两个皇后的子全部落入棋盘中,此时计数器自增,然后再进行类似的回溯,找其他解法,直到全部找出来位置

代码

#include
using namespace std;
#include

int n = 0;  //2n皇后
int m[10][10];  //地图
int blackPos[10], whitePos[10];  //黑白皇后的选点集,下标表示行,通过下标访问的值为列
int _count = 0;  //方法计数器

bool IsAttack(int pos[], int point_r, int point_c)  //当前点是否冲突
{
	for (int row = 0; row < point_r; row++)
	{
		if (point_c == pos[row] || abs(point_c - pos[row]) == point_r - row)
		{
			return true;
		}
	}
	return false;
}

//黑皇后落子
void SetBlack(int row)
{
	if (row == n)
	{
		//黑皇后已落了n个子,计数器+1
		_count++;
		return;
	}
	//深度优先遍历搜索 黑皇后选定落子序列
	for (blackPos[row] = 0; blackPos[row] < n; blackPos[row]++)
	{
		if (!IsAttack(blackPos, row, blackPos[row]) && m[row][blackPos[row]] == 1 && blackPos[row] != whitePos[row])
		{
			SetBlack(row + 1);
		}
	}
}

//白皇后落子
void SetWhite(int row)
{
	if (row == n)
	{
		//白皇后已经落了n个子,即白皇后的一种序列已经确定,在此基础上对黑皇后的落子集合进行搜索
		SetBlack(0);
		return;
	}
	//深度优先遍历搜索,查找白皇后落子的序列
	for (whitePos[row] = 0; whitePos[row] < n; whitePos[row]++)
	{
		if (!IsAttack(whitePos, row, whitePos[row]) && m[row][whitePos[row]] == 1)
		{
			SetWhite(row + 1);
		}
	}
}

int main()
{
	cin >> n;
	for (int i = 0; i < n; ++i)
	{
		for (int j = 0; j < n; ++j)
		{
			cin >> m[i][j];
		}
	}
	SetWhite(0);
	cout << _count << endl;
	return 0;
}

你可能感兴趣的:(C++,算法)