八皇后问题(递归回溯算法详解+C代码)

为了理解“递归回溯”的思想,我们不妨先将4位皇后打入冷宫,留下剩下的4位安排进4x4的格子中且不能互相打架,有多少种安排方法呢?现在我们把第一个皇后放在第一个格子,被涂黑的地方是不能放皇后的:在这里插入图片描述
第二行的皇后只能放在第三格或第四格,比如我们放在第三格:

在这里插入图片描述
此时,第三个皇后只有一个位置可选。当第三个皇后占据第三行蓝色空位时,第四行皇后无路可走,于是发生错误,则返回上层调整3号皇后,而3号皇后也别无可去,继续返回上层调整2号皇后,而2号皇后已然无路可去,则再继续返回上层调整1号皇后,于是1号皇后往后移一格位置如下,再继续往下安排:

在这里插入图片描述

核心代码如下:

void EightQueen( int row )
{
	int col;
	if( row>7 )                       //如果遍历完八行都找到放置皇后的位置则打印
	{
		Print();                       //打印八皇后的解 
		count++;
		return ;
		
	}

	for( col=0; col < 8; col++ )        //回溯,递归
	{
		if( notDanger( row, col ) )    // 判断是否危险
		{
			chess[row][col]=1;
			EightQueen(row+1);
			
			chess[row][col]=0;           //清零,以免回溯时出现脏数据
		}
	}
}

我们来重点看一下这段代码:
第一次进来,row=0,意思是要在第一行摆皇后,只要传进来的row参数不是8,表明还没出结果,就都不会走if里面的return,那么就进入到for循环里面,col从0开始,即第一列。此时第一行第一列肯定合乎要求(即notDanger方法肯定通过),能放下皇后,因为还没有任何其他皇后来干扰。

关键是notDanger方法通过了之后,在if里面又会调用一下自己(即递归),row加了1,表示摆第二行的皇后了。第二行的皇后在走for循环的时候,分两种情况,第一种情况:for循环没走到头时就有通过notDanger方法的了,那么这样就顺理成章地往下走再调用一下自己(即再往下递归),row再加1(即摆第三行的皇后了,以此类推)。第二种情况:for循环走到头了都没有通过notDanger方法的,说明第二行根本一个皇后都摆不了,也触发不了递归,下面的第三行等等后面的就更不用提了,此时控制第一行皇后位置的for循环col加1,即第一行的皇后往后移一格,即摆在第一行第二列的位置上,然后再往下走,重复上述逻辑。

注意,一定要添加清零的代码,它只有在皇后摆不下去的时候会执行清0的动作(避免脏数据干扰),如果皇后摆放很顺利的话从头到尾是不会走这个请0的动作的,因为已经提前走if里面的return方法结束了。

总之,这段核心代码很绕,原理一定要想通,想个十几二十遍差不多就能理解其中的原理了,递归回溯的思想也就不言而喻了。八皇后问题一共有92种情况

完整代码如下:

#include 

int count = 0;
int chess[8][8]={0};

int notDanger( int row, int col )
{
	int i,k;
	// 判断列方向
	for( i=0; i < 8; i++ )
	{
		if( chess[i][col]==1 )
		{
			return 0;
		}
	}
	// 判断左对角线 
	for( i=row, k=col; i>=0 && k>=0; i--, k-- )
	{
		if(chess[i][k]==1  )
		{
			return 0;
		}
	}
	// 判断右对角线 
	for( i=row, k=col; i>=0 && k<8; i--, k++ )
	{
		if(chess[i][k]==1  )
		{
			return 0;
		}
	}
	return 1;
}

void Print()          //打印结果 
{
	int row,col;
	printf("第 %d 种\n", count+1);
		for( row=0; row < 8; row++ )
		{
			for( col=0; col< 8; col++ )
			{
				if(chess[row][col]==1)        //皇后用‘0’表示
				{
					printf("0 ");
				}
				else
				{
					printf("# ");
				}
			}
			printf("\n");
		}
		printf("\n");
}

void EightQueen( int row )
{
	int col;
	if( row>7 )                       //如果遍历完八行都找到放置皇后的位置则打印
	{
		Print();                       //打印八皇后的解 
		count++;
		return ;
		
	}

	for( col=0; col < 8; col++ )        //回溯,递归
	{
		if( notDanger( row, col ) )    // 判断是否危险
		{
			chess[row][col]=1;
			EightQueen(row+1);
			
			chess[row][col]=0;           //清零,以免回溯时出现脏数据
		}
	}
}

int main()
{
	EightQueen(0);        
	printf("总共有 %d 种解决方法!\n\n", count);
	return 0;
}

#include
#include
#include
using namespace std;
 
#define Max 20    //表示最大的棋盘边长,可以自定义为其它数据
int pos[Max+1];   //为什么只需要定义一个一维数组就能描述二维的棋盘?
                  //pos[i]是这样定义的:即第i列的皇后放在第pos[i]行上,
                  // 也就是说,pos[i]的索引i代表皇后所在的列,它的值pos[i]代表皇后所在的行
int n;          //棋盘的边长和皇后的数量
int sum;         //可以成功摆放的数量,每次遍历成功就加1
 
bool checkNQ(int col){      //对第col之前的列进行逐列检查,pos[i]中i的值为列,pos[i]的值为行
	for(int i=1;i<col;i++)
		if(pos[i]==pos[col]||abs(i-col)==abs(pos[i]-pos[col]))
                 //如果行数相同,或者行数相减的绝对值等于列数相减的绝对值
                //此时都不能放皇后,因为对第col列之前的列进行逐列检查,
		//所以不需要再进行列是否相同的判断
			return false;
	return true;
}
 
void dfsNQ(int col,int n){
      if(col==n+1)   //成功遍历一次,sum加1,然后继续探索其他情况
      	sum++;
      for(int i=1;i<=n;i++){
      	pos[col]=i;//假设第col列的皇后放在第i行上,然后利用checkNQ()函数检查是否能放入
      	            //第一种情况,如果能放入,则继续假设下一列也放在第i行(实际上第i行此时已经不能放了,
      	             //所以cheakNQ()函数就会直接返回false,
      	            //然后上面的for循环中的i自动加1,即假设第col+1列放在第i+1行,然后又继续检查能否放入。
      	            //第二种情况,如果不能放入,for循环中的i就自动加1,即假设第col列的皇后放在第i+1行上,
      	            //又继续检查能否放入
      	            //如果当col<=n时(即列还没有遍历完)再也不能在任何一行放皇后,那么此时dfsNQ中的for循环的i已经
      	             //遍历完,dfsNQ就会返回到上一级的dfsNQ(col+1,n),此时col就会自动减1(因为每次递归都是加1),
      	             //然后,尝试第col列的皇后能否放在第i+1行上,如此进行回溯。
      	if(checkNQ(col))
      		dfsNQ(col+1,n);  //进行递归
      }
}
 int main()
 {
 	cout<<"请输入皇后的数量:";
 	cin>>n;
 	dfsNQ(1,n);  //传入第一列和n,从第一列开始放皇后。
 	cout<<endl<<"满足条件的所有摆放次数为:";
 	cout<<sum;
    return 0;    //说明:dfsNQ函数完全退出的条件是所有满足条件的情况都已经遍历过,再也没有满足条件的遍历
                 //根据递归的初始化分析得知,前面的遍历都会默认第一行第一列的位置会放皇后,
                 //然而实际情况是这个位置不放皇后也能满足条件的次数甚至更多,那么程序在运行到什么时候会把
                 //第一行第一列的位置放空呢?答案是当第一行第一列放皇后的满足条件的所有遍历都结束时,
                //就会把第一列的皇后放在第二行,而把第一行第一列的位置放空。
                 //如此进行到最后,最后面是把第一列的皇后放在最后一行,
                 //然后再全部遍历,结束时整个dfsNQ函数递归运行结束。主函数return 0.
 	

转载自:https://blog.csdn.net/qq_42552533/article/details/86684045

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