C++回溯算法与八皇后问题

今天想写一篇有关回溯法的问题,但是不知道开头应该介绍些什么东西。所以简单粗暴一点,直接上问题。一种经典的回溯算法就是八皇后问题,何为八皇后问题呢?

八皇后问题是一个以国际象棋为背景的问题:如何能够在8×8的国际象棋棋盘上放置八个皇后,使得任何一个皇后都无法直接吃掉其他的皇后?为了达到此目的,任两个皇后都不能处于同一条横行、纵行或斜线上。 ——引自维基百科
为了更好的理解,上一张图:
在这里插入图片描述
图中画×的是满足条件的,画〇则不满足,因为有两个在同一条直线上。那么问题来了,在一个8 * 8的棋盘上符合条件的八皇后到底有多少种摆法呢?由此便衍生出了计算机中的八皇后问题。

首先,我们的基本思路就是先摆第一个,再摆第二个,依此类推,如果第二个不符合条件,则调整第一个的位置(回溯)。简而言之,我们将8 * 8的棋盘看成是一个二维数组,首先将第一个棋子摆在[0][0]的位置。再将第二个摆在[1][0]的位置(因为第二个位置一定不能再摆在0开头的位置,否则一定在同一行),如果不合适再放在[1][1]的位置,依次类推,如果摆到[1][7]的位置还有冲突,则需要调整第一个棋子的位置至[0][1],再依次尝试,这就是递归的基本原理,将其转换成代码如下:

for(int i = 0; i < QueenNum; i++){  //通过循环,放置每一个棋子
	column[currentQueen] = i;  //试探性的将当前的棋子放在第i列
	if(hasConflict()){  //判断和之前的是否有冲突,若有冲突直接continue,重新放置
		continue;
	}
	else  //没有冲突则直接进入迭代放置下一个棋子
		putQueen();
}
	

另外一个需要解决的问题就是冲突检测,如何检查是否有冲突呢?我们的基本思路就是,每排只放一个,这样保证了不会有任意2个棋子在同一行(即第i个棋子摆在弟i行)。其次,每摆一个棋子后,我们用一个数组将其列数记下来,如column[i]表示第i个棋子摆放的列数,因此当我们摆放第i+1个棋子时就需要判断这个棋子是否和column这个数组中的0~i个元素有冲突,若没有则将column[i + 1]置为当前棋子摆放的列数。基于这个思路我们可以写出以下代码:

bool hasConflict = false;
for(int i = 0; i < currentQueenNum; i++){  //currentQueenNum表示当前棋子的个数,遍历数组中已经存放的每个棋子所在的列数,i表示棋子的行数
	if(column[i] == currentQueenColumn || abs(column[i] - currentQueenClumn) == abs(i - currentQueenNum){ //前面一部分判断是否在同一列,后面一部分判断是否在同一斜线上
		hasConflict = true;  //如果有,则将有冲突标志置为true,并终止判断
		break;
	}
}

解决了这两块问题,接下来就是将他们整合以下,既然上面有迭代代码,那么就必须有一个终止迭代的条件,否则整个迭代会一直循环下去,永无停止。显而易见,迭代终止的条件就是,当currentQueenNum == QueenNum时终止迭代,即所有的皇后都放置完了。

完整代码如下:

#include <iostream>
#include<math.h>

#define MAXQUEEN 100

int checkerBord[MAXQUEEN];  //定义一个棋盘最大可容纳多少个皇后
int methods = 0;  //全局变量,记录一共有多少种方案
void putQueen(int queenNum, int currentQueenNum) {  //放置皇后,需要给出两个参数,一个是需要放置多少个皇后,一个是当前放置的是哪一个皇后
    if (queenNum == currentQueenNum)   //迭代终止条件,一旦迭代终止了,则表示放置方式符合条件方案加1
        methods++;
    else {
        for (int i = 0; i < queenNum; i++) {
            checkerBord[currentQueenNum] = i;  //逐行模拟放置
            bool hasConflict = false;
            for (int j = 0; j < currentQueenNum; j++) { //检测冲突
                if (checkerBord[j] == i || abs(currentQueenNum - j) == abs(checkerBord[j] - i)) {
                    hasConflict = true;
                    break;
                }
            }
            if (hasConflict)  //如果有冲突,则继续重新放置,否则放置下一个
                continue;
            else
                putQueen(queenNum, currentQueenNum + 1);
        }
    }
}

int main()
{
	int queenNum;
	std::cout << "请输入需要放置的皇后的个数(0~100):\t";
	std::cin >> queenNum;
    putQueen(queenNum, 0);  //main程序中,假设共有8个皇后,需要放置的皇后为0~7
    std::cout << "当皇后个数为" << queenNum << "时,共有" << methods << "种方案";
}

输出如下:
在这里插入图片描述
那么,我们能不能看到这么多的方案分别是怎么摆的呢?既然我敢提出来那答案当然是可以啦。从上面的介绍种我们能够知道,满足迭代终止条件时,肯定是符合条件的。那么在这种情况下我们只要按照存储在checkerboard中的列的摆放位置就可以了,我们约定空位置用〇表示,放置了皇后的位置用×表示,那么将上面的迭代终止的内容改一下就OK了,代码如下:

if (queenNum == currentQueenNum) {   
        methods++;
        std::cout << "方案" << methods << ": \n";
        for (int i = 0; i < queenNum; i++) {  //行数
            for (int j = 0; j < queenNum; j++) { //列数
                if (checkerBord[i] == j) //有放置棋子
                    std::cout << "× ";
                else  //无棋子
                    std::cout << "〇 ";
            }
            std::cout << std::endl;
        }
        std::cout << "\n\n";
    }

以上便是著名的八皇后问题的解法。

猜你喜欢

转载自blog.csdn.net/WJ_SHI/article/details/106587413
今日推荐