Hello,我是C风, 对于初学者或者编程小白来说,八皇后问题就是一个重要的坎,今天我们就详细来看看玩转八皇后的正确姿势,其中夹杂本人的一些理解,如有错误,欢迎批评指正~~
分析:八皇后问题首先就是要明确我们不是一次性把所有皇后摆在棋盘上,而是按行来摆放皇后,皇后的摆放就是按照一个层序进行,这就形成了一个递归,因为每个皇后的摆放具有相同的思路。
在调试的过程中发现这里的结束语句不只是那个return,return只是代表当前执行函数的结束,也就是只是代表了一层函数的结束,进入主调函数,就是回溯;还有就是非return情况:就是程序自动执行到函数结束的位置,就像main函数有时不写return也会结束运行,这也代表了函数的结束,也就是递归结束的条件之一。
分析八皇后问题,我们首先就是建立分析的思路,写出此问题的主要代码,之后再建立数学模型来表示棋盘,这里我们说过只能把皇后一个一个放置,如果都放置上去就会导致移动的时候所有的棋子都要移动,非常麻烦;这里这个规则就是每一行每一列对角线不能同时出现两个皇后,不然就会攻击,所以我们就要建立一个IsAttack函数来记录这个位置是否可以安全地放置皇后,递归结束的条件就是放置了第八个皇后,也就是行数等于8;还有就是下一行没有位置可以放置皇后;递归的语句就是放置下一个皇后,也就是该函数的自变量自增一;回溯的之后需要执行的就是将上一个皇后的位置清除,防止位置污染。这样递归三个重要组成:结束条件,递归语句,回溯执行语句就都有了。
int chess[10][10];
int cnt = 0;
这就是建立的一种数学模型,就是建立的二维数组模型。
建立八皇后问题的关键代码就是我之前所说的三要素就好了,我们在分析一个问题的时候如果问题可以像一棵树一样一层一层建立推出那么就要考虑递归的方法,这是分析问题的前提。
int EightQueen(int row)
{
if(row == 8)//递归结束条件
{
print();//打印本次的摆放次序
cnt ++;//排列次序加
return 0;//对于函数而言当然就是结束函数了,但这里只是结束的这一层函数,一层一层返回,就是返回主调用函数
}//这里让我想起了二叉树的建立就是一层一层返回
else{
for(int col = 0;col < 8;col++)
{
if(IsAttack(row,col))
{
chess[row][col] = 1;//安全就将皇后放置
EightQueen(row+1);//进入下一层
chess[row][col] = 0;//还是和二叉树建立一样只是那里是两个递归,这里的回溯之后的操作是不一样的
}//回溯之后就是下面的位置不满足所以就要把上一行的位置重新放置
}
}
}
从代码还可以看到一个特点就是当把一个问题考虑清楚之后关键代码是很少的;之后就是写我们的IsAttack函数以及print函数;这两个里面的IsAttack函数需要注意的是函数遇到return之后就会结束函数,所以之前我在每一个后面都写else就是错误的,这样执行到前面就会自己终止达不到效果,所以就应该在之后才加return 1;
int IsAttack(int row,int col)
{//这里开始出现的问题是我在每个if后面都加了else;这个问题容我再想想
int j,k;//问题就是函数遇到return就会结束执行,所以之前就会执行else之后跳出程序
for(j = 0;j < row;j ++)
{
if(chess[j][col] == 1)
{
return 0;
}
}
for(j = row,k = col;j >=0&&k>=0;j--,k--)
{
if(chess[j][k] == 1)
{
return 0;
}
}
for(j = row,k = col;j >= 0&&k <8;j--,k++)
{
if(chess[j][k] == 1)
{
return 0;
}
}
return 1;
}
对于print就是注意换行来保持输出界面的美观
int print()
{
for(int row = 0;row < 8;row++)
{
for(int col = 0;col < 8;col ++)
{
if(chess[row][col] == 1)
{
printf("& ");
}
else printf("# ");
}
printf("\n");
}
printf("--------------我是分界线--------------------\n");
}
这是用二维数组来表示棋盘的位置的,这样子虽然容易理解,但是问题是空间复杂度大;其实我们每一行只需要有一个位置存储皇后的位置,所以其实只需要使用一个一维数组;
变成一维数组就只是数学建模不同 了而已,主要思路是一模一样的,就只是在进行IsAttack时,判断要简单一些,调用数学函数库里面的绝对值函数,只要两个之差的绝对值相等就可以了。
#include<iostream>
using namespace std;
#include<stdio.h>
#include<math.h>
int ChessPos[10];
int cnt = 0;
int IsAttack(int row, int col)
{
int j;
for (j = 0; j < row; j++)
{
if (ChessPos[j] == col||abs(row - j) == abs(col - ChessPos[j]))
{
return 0;
}
}
return 1;
}
void print()
{
printf("第%d种结果是: ", cnt+1);
for (int i = 0; i < 8; i++)
{
printf("%d ", ChessPos[i]);
}
printf("\n");
}
int EightQueen(int row)
{
if (row == 8)
{
print();
cnt++;
return 0;
}
else {
for(int col = 1;col <= 8;col ++)
{
if (IsAttack(row,col))
{
ChessPos[row] = col;
EightQueen(row + 1);
ChessPos[row] = 0;//回溯之后进行的操作
}
}
}
}
int main()
{
EightQueen(0);
printf("%d", cnt);
}
所以递归的主要就是把递归函数写清楚,像八皇后问题,我们的关键就是写EightQueen函数,这里面就是由递归结束条件,之后就是不结束递归时我们怎么操作,这里就是使用的循环遍历位置,这里主要的就是如果我们的位置判断正确了之后的操作以及回溯之后我们要进行的操作,也就是我们的递归的重要组成部分。
还有就是关于代码的输入输出,这是受编译器限制,按照c++标准将printf改成cout就好了,夹杂了C与C++,下次发文就会注意改进这个问题~~