n皇后问题(回溯法的拓展)

//C++新手写的文章,代码能力不足,更多为学习用,望大家指正

八皇后问题是回溯法的入门程序,简易回溯法的思想主要和深度优先搜索(DFS)的思想差不多,我们先写一个基本的深搜程序

void DFS(GraphList *g,int v,int visited[]){    //GraphList是图类型
	cout<<v<<' ';    
	visited[v]=1;    
	int w=firstchild(g,v);  	//firstchild函数是获得第一个儿子
	while(w!=NULL)  {       	//也可以写成 while(w)       
		if(!visited[w]) 
		DFS(g,w,visited);        
		w=nextchild(g,v,w);     //nextchild函数是获得下一个儿子
	}
	return;
}

void DFSTraverse(GraphList *g){    
	int visited[visited_size];    		
	for(int i=0;i<visited_size;i++)        
		visited[i]=0;    
	for(int i=0;i<g->numVertex;i++)    
		if(!visited[i])  DFS(g,i,visited);
	return;
}

那回溯法和深搜相似在什么地方,深搜可以一直搜下去,当遇到已经搜索过的节点就自动去搜索父亲节点的下一个儿子,如果该父亲节点的儿子都已经被搜索过后就再回溯到上一级,这一点也是回溯法的核心。其实我的理解就是,回溯法就是一个自定义visited数组怎么被访问的深度优先搜索,在最普通的图的深度优先搜索中,我们采用挨个访问的方式来将visited数组设置也为已访问,但是当我们想要解决一些实际问题的时候,比如八皇后问题,我们不能让位于与该皇后同一行、同一列、以及同一对角线的节点都不能被访问,这时候我们习惯不再将深搜称为深搜,而是回溯法。所以回溯法本质上就是深度优先搜索

理解了回溯法的内容后,接下来我们可以进入n皇后问题的解决上,我们需要先分析该问题
八皇后问题的搜索条件
如图所示,是八皇后问题的搜索条件,每当我们进入到一个节点(也就是搜索到一个皇后)时,我们首先就确定了与该节点位于同一行,同一列,同一对角线的位置不能被访问,此时我们可以设置visited数组为1,来表示不可访问状态。

void access(int i,int j)
{
	for(int k=1;k<=queen_num;k++) {
  		vis[i][k] = 1;
  		vis[k][j] = 1;
 	}
 
 	for(int dr=-queen_num;dr<=queen_num;dr++) {
  		if(i+dr>0&&i+dr<=queen_num&&j+dr>0&&j+dr<=queen_num) {
   			vis[i+dr][j+dr] = 1;
  		}
  		if(i+dr>0&&i+dr<=queen_num&&j-dr>0&&j-dr<=queen_num) {
   			vis[i+dr][j-dr] = 1;
  		}
 	}
}

但是我们还要解决一个非常难缠的问题,也是回溯法的核心所在,怎么回溯?
我们学习深度优先搜索的时候,可能从来都没有考虑过这个问题,那是因为深度优先搜索采用的访问方式很简单,当我们需要回溯的时候,并不需要取消原来已经访问的节点的访问状态。
简单来说,深度优先搜索的任务是穷尽一个地图上的所有地点,我们走过了这个地点就不需要考虑再走了,而回溯法的任务不同,当有多条路都经过这个地点时,一条路走不通不代表其他的路也走不通,所以我们需要取消这个访问状态来确保再次经过这个地点时不会错过这个地点。

所以回溯就需要我们记录当前节点所确定不能被访问的节点,怎么记录这个状态,我们用到了一个四维数组,皇后问题的规模并不大,而且这可以很好的帮助我们记录状态
在这里插入图片描述
如图所示,红色表示在访问当前节点前已经被访问过的节点,绿色代表当前节点所新确定的不可访问的节点,该皇后所确定的不可访问的位置是图中红色和绿色加起来的部分,但是我们设置数组的时候不能将红色节点的信息也写进数组,因为这些节点在之前已经被访问过了,不是由当前节点所确定的不可访问节点,于是就得到了新的access函数

void access(int i,int j)
{
 	memset(d[i][j],0,sizeof(d[i][j]));
 	for(int k=1;k<=queen_num;k++) {
  		if(!vis[i][k]) d[i][j][i][k] = 1;
  		vis[i][k] = 1;
  		if(!vis[k][j]) d[i][j][k][j] = 1;
  		vis[k][j] = 1;
 	}
 
 	for(int dr=-queen_num;dr<=queen_num;dr++) {
  		if(i+dr>0&&i+dr<=queen_num&&j+dr>0&&j+dr<=queen_num) {
   			if(!vis[i+dr][j+dr]) d[i][j][i+dr][j+dr] = 1;
   			vis[i+dr][j+dr] = 1;
 		}
  		if(i+dr>0&&i+dr<=queen_num&&j-dr>0&&j-dr<=queen_num) {
   			if(!vis[i+dr][j-dr]) d[i][j][i+dr][j-dr] = 1;
   			vis[i+dr][j-dr] = 1;
  		}
 	}
}

这样我们就得到了一个确定每个会被访问的节点所确定的不可访问节点的四维数组(
读着真™拗口,),接下来就是回溯时所执行的去除不可访问状态的删除函数

有了四维数组的帮助,删除函数就简单多了,如下只有8行

void del(int i,int j)
{
	for(int k=1;k<=queen_num;k++) {
  		for(int s=1;s<=queen_num;s++) {
   			if(d[i][j][k][s]==1) vis[k][s] = 0;
  		}
 	}
}

当然如果不嫌麻烦也可以用更节省空间的数据结构——链表来替代四维数组的功能,这里我们就不实现了,大家可以尝试一下

最终附上n皇后问题的代码(内容有些冗杂没有删减)

#include<bits/stdc++.h>
using namespace std;

const int maxn = 10;
int queen_num,ans;
int vis[maxn][maxn],d[maxn][maxn][maxn][maxn];

void access(int i,int j)
{
  	memset(d[i][j],0,sizeof(d[i][j]));
  	for(int k=1;k<=queen_num;k++) {
    		if(!vis[i][k]) d[i][j][i][k] = 1;
    		vis[i][k] = 1;
    		if(!vis[k][j]) d[i][j][k][j] = 1;
    		vis[k][j] = 1;
  	}
 
  	for(int dr=-queen_num;dr<=queen_num;dr++) {
    		if(i+dr>0&&i+dr<=queen_num&&j+dr>0&&j+dr<=queen_num) {
      			if(!vis[i+dr][j+dr]) d[i][j][i+dr][j+dr] = 1;
      			vis[i+dr][j+dr] = 1;
   		}
    		if(i+dr>0&&i+dr<=queen_num&&j-dr>0&&j-dr<=queen_num) {
      			if(!vis[i+dr][j-dr]) d[i][j][i+dr][j-dr] = 1;
      			vis[i+dr][j-dr] = 1;
   		}
  	}
}

void del(int i,int j)
{
 	for(int k=1;k<=queen_num;k++) {
  		for(int s=1;s<=queen_num;s++) {
   			if(d[i][j][k][s]==1)  vis[k][s] = 0;
  		}
 	}
}

void dfs(int cur,int i) 
{
 	if(cur==queen_num) {
  		ans++;
  		return;
 	}
 	access(cur,i);
	for(int a=1;a<=queen_num;a++) {
  		if(!vis[cur+1][a]) {
   			dfs(cur+1,a);
  		}
 	}
 	del(cur,i);
 	return; 
}

int main()
{
 	scanf("%d",&queen_num);
 	for(int i=1;i<=queen_num;i++)
  		dfs(1,i);
 	printf("%d",ans); 
} 
发布了4 篇原创文章 · 获赞 5 · 访问量 80
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览