N皇后问题(C++ tag路径回溯)

横纵斜不能有冲突

*暴力方法:全局搜索空间是N^2,即栈中的安全皇后放好后,下一个皇后的搜索路径仍然是原矩阵。

*剪枝后排除一部分不需要的遍历操作,将剩余遍历空间缩短至N!

tag数组负责记录当前的路径,和DFS中带路径的那个方法几乎一致,只不过回溯的结果是全部解(有多少个解的问题),之前写的那个单纯的dfs只需要找到一个有效解(即有没有解的问题)

下面给出N皇后(Leetcode51)的解法:

1.未优化版本(N*N超级暴力破解)

//自己最先想到并实现了的(按照全搜索空间全排列超级暴力搜,vs2015 + i7 3960X @3.7Ghz 默认频率(6皇后100ms,7皇后52s时间  8皇后需要36min
void dfsqueen3_1(int n, vector<vector<int>>&tag, vector<vector<string>>&ret) {
	//每一个子递归函数都采用N*N的搜索空间进行一个一个找吧,先弄出来一个版本再说(暴力)
	if (tag.size() == n)
	{
		int jb = 0;
		while (jb < n) {
			if (tag[jb][0] != jb++)
				return;
		}
		vector<string>vs;
		string c(n, '.');
		for (int i = 0; i < tag.size(); ++i) {
			c[tag[i][1]] = 'Q';
			vs.push_back(c);
			c[tag[i][1]] = '.';
		}
		ret.push_back(vs);
	}
	for (int q = 0; q < n; ++q)
	{
		for (int v = 0; v < n; ++v) //这一行的所有列递归完毕后
		{
			//判断是否满足条件
			int gk = 0;
			for (int i = 0; i < tag.size(); ++i) //判断此时的皇后是否互相攻击	
			{
				if ((q == tag[i][0] || v == tag[i][1] || q - tag[i][0] == v - tag[i][1] || q - tag[i][0] == tag[i][1] - v))
				{
					gk = 1;
					break;
				}
			}
			if (gk == 0)
			{
				tag.push_back({ q,v });//并未相互攻击,此时的坐标有效,加入路径	
				dfsqueen3_1(n, tag, ret);
				tag.pop_back();
			}

		}		

	}

}

以上方法可以说是非常暴力了,全局复杂度超级大,因为做出了很多重复工作,搞出来的解是四个方向均进行遍历的,也就是说四个方向数组的全排列过程,虽说对于该题而言方法不是很优秀,但是这种思想是有效的。

2.下面给出优化后的第一个版本

//get better2(9-QUEEN: 92s)
void dfsqueen3_2(int n, vector<vector<int>>&tag, vector<vector<string>>&ret,unordered_set<int>&rowset, unordered_set<int>&colset) {
	//
	if (tag.size() == n)
	{
		vector<string>vs;
		string c(n, '.');
		for (int i = 0; i < tag.size(); ++i) 
		{
			c[tag[i][1]] = 'Q';
			vs.push_back(c);
			c[tag[i][1]] = '.';
		}
		ret.push_back(vs);

	}
	for (int q = tag.size(); q < n; ++q)
	{		
		if (rowset.find(q) != rowset.end() && rowset.size() != 0) {
			continue;
		}
		for (int v = 0; v < n; ++v) //这一行的所有列递归完毕后
		{
			if ((colset.find(v) != colset.end()) && colset.size() != 0) {
				continue;
			}
			//判断是否满足条件
			int gk = 0;
			for (int i = 0; i < tag.size(); ++i) //判断此时的皇后是否互相攻击	
			{
				if (q - tag[i][0] == v - tag[i][1] || q - tag[i][0] == tag[i][1] - v)
				{
					gk = 1;
					break;
				}
			}
			//剪枝思想:如果第三行试过了所有方案都无法插入,那么第二行的选择肯定有问题,此时无需进入第四行了,直接返回第二行修改后再继续
			if (gk == 1 && v == n-1) { return; }//这一行剪一波
			if (gk == 0)
			{
				tag.push_back({ q,v });//并未相互攻击,此时的坐标有效,加入路径	
				if (tag[0][0] != 0)return;
				rowset.insert(q);
				colset.insert(v);
				dfsqueen3_2(n, tag, ret,rowset,colset);
				tag.pop_back();
				rowset.erase(q);
				colset.erase(v);
			}
		}
	}
}

 get better2方法在row搜索时将二重循环中的row搜索过程从七点0,变为了tag中的元素个数,也就是说如果tag中有3个元素,那么前三行是不用去搜索判断的,直接从第四行的所有列进行寻找即可,免去了row的一部分判断过程;第二,该方法在判断完一行的所有列之后进行了tag检查,也就是说如果一行的所有列都没有能满足的位置,那么这一行无效,下面的行统统不需要判断了,大大减少了计算量。

3.下面给出优化后的get better3版本

void dfsqueen3_3(int n, vector<vector<int>>&tag, vector<vector<string>>&ret, vector<int>&rowset, vector<int>&colset) {
	//
	if (tag.size() == n)
	{
		vector<string>vs;
		string c(n, '.');
		for (int i = 0; i < tag.size(); ++i)
		{
			c[tag[i][1]] = 'Q';
			vs.push_back(c);
			c[tag[i][1]] = '.';
		}
		ret.push_back(vs);
	}

	for (int q = tag.size(); q < n; ++q)
	{
		if (rowset[q] != 0) {
			continue;
		}
		for (int v = 0; v < n; ++v) //这一行的所有列递归完毕后
		{
			if (colset[v] != 0) {
				continue;
			}
			//判断是否满足条件
			int gk = 0;
			for (int i = 0; i < tag.size(); ++i) //判断此时的皇后是否互相攻击	
			{
				if (q - tag[i][0] == v - tag[i][1] || q - tag[i][0] == tag[i][1] - v)
				{
					gk = 1;
					break;
				}
			}
			//剪枝思想:如果第三行试过了所有方案都无法插入,那么第二行的选择肯定有问题,此时无需进入第四行了,直接返回第二行修改后再继续
			if (gk == 1 && v == n - 1) { return; }//这一行剪一波
			if (gk == 0)
			{
				//并未相互攻击,此时的坐标有效,加入路径	
				tag.push_back({ q,v });
				if (tag[0][0] != 0)return;
				rowset[q] = 1;
				colset[v] = 1;
				dfsqueen3_3(n, tag, ret, rowset, colset);
				tag.pop_back();
				rowset[q] = 0;
				colset[v] = 0;
			}

		}
	}

}

第三个版本把第二版中的哈希表unordered_set集合用vector来替代了,vector寻址的优势体现出来了。其他的没有做出改动

4.第四个版本(纯C数组优化常数时间)

//get better4(9-QUEEN: 18.139s  优化vector至普通数组,常数时间?)
void dfsqueen3_4(int n, vector<vector<int>>&tag, vector<vector<string>>&ret, int(&rowset)[10], int(&colset)[10]) {
	//
	if (tag.size() == n)
	{
		vector<string>vs;
		string c(n, '.');
		for (int i = 0; i < tag.size(); ++i)
		{
			c[tag[i][1]] = 'Q';
			vs.push_back(c);
			c[tag[i][1]] = '.';
		}
		ret.push_back(vs);
	}

	for (int q = tag.size(); q < n; ++q)
	{
		if (rowset[q] != 0) {
			continue;
		}
		for (int v = 0; v < n; ++v) //这一行的所有列递归完毕后
		{
			if (colset[v] != 0) {
				continue;
			}
			//判断是否满足条件
			int gk = 0;
			for (int i = 0; i < tag.size(); ++i) //判断此时的皇后是否互相攻击	
			{
				if (q - tag[i][0] == v - tag[i][1] || q - tag[i][0] == tag[i][1] - v)
				{
					gk = 1;
					break;
				}
			}
			//剪枝思想:如果第三行试过了所有方案都无法插入,那么第二行的选择肯定有问题,此时无需进入第四行了,直接返回第二行修改后再继续
			if (gk == 1 && v == n - 1) { return; }//这一行剪一波
			if (gk == 0)
			{
				//并未相互攻击,此时的坐标有效,加入路径	
				tag.push_back({ q,v });
				if (tag[0][0] != 0)return;
				rowset[q] = 1;
				colset[v] = 1;
				dfsqueen3_4(n, tag, ret, rowset, colset);
				tag.pop_back();
				rowset[q] = 0;
				colset[v] = 0;
			}

		}
	}

}

纯方括号数组的引用,看起来很不错,不过在小范围数字内,这种方法没有比vector方法取得足够令人满意的效果。这俩只相差了几ms,几乎可以看作是误差。

5.降维(根据一开始的思路, 不遍历row了,只遍历col,把row当成形参加入递归函数)

class Solution {
public:
void dfs(int n,int row,vector<vector<int>>&tag,vector<vector<string>>&ret,int(&colset)[10]) {
	if (tag.size() == n) {
		vector<string>vs;
		string c(n,'.');
		for (int i = 0; i < tag.size(); ++i) {
			c[tag[i][1]] = 'Q';
			vs.push_back(c);
			c[tag[i][1]] = '.';
		}
		ret.push_back(vs);
	}
	for (int v = 0; v < n; ++v) {
		if (colset[v] == 1)continue;
		int mute = 0;
		for (int i = 0; i < tag.size(); ++i) {
			if (row - tag[i][0] == v - tag[i][1] || row - tag[i][0] == tag[i][1] - v) {
				mute = 1;
				break;
			}
		}
		if (mute == 0) {
			tag.push_back({ row,v });
			colset[v] = 1;
			dfs(n,row+1,tag,ret,colset);
			tag.pop_back();
			colset[v] = 0;
		}
	}
}
vector<vector<string>> solveNQueens(int n) {
	vector<vector<string>>ret;
	vector<vector<int>>tag;
	int pc[10]{ 0,0,0,0,0,0,0,0,0,0 };
	dfs(n, 0,tag, ret, pc);
	return ret;
}
};

 显然,优化至目前的情况并非是最佳的,因为斜线上的皇后攻击判断仍然是每次递归后与所有tag中的元素判断,并不是O(1)复杂度的直接寻址,但是更优解法目前先不搞了。以后有机会再写。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值