八皇后问题是一个以国际象棋为背景的问题:如何能够在8×8的国际象棋棋盘上放置八个皇后,使得任何一个皇后都无法直接吃掉其他的皇后,为了达到此目的,任两个皇后都不能处于同一条横行、纵行或斜线上。八皇后问题可以推广为更一般的n皇后摆放问题:这时棋盘的大小变为n×n,而皇后个数也变成n。当且仅当n = 1或n ≥ 4时问题有解。
八皇后问题最早是由西洋棋棋手马克斯·贝瑟尔(Max Bezzel)于1848年提出。第一个解在1850年由弗朗兹·诺克(Franz Nauck)给出。并且将其推广为更一般的n皇后摆放问题。诺克也是首先将问题推广到更一般的n皇后摆放问题的人之一。
在此之后,陆续有数学家对其进行研究,其中包括高斯和康托,1874年,S.冈德尔提出了一个通过行列式来求解的方法,这个方法后来又被J.W.L.格莱舍加以改进。
1972年,艾兹格·迪杰斯特拉用这个问题为例来说明他所谓结构化编程的能力。他对深度优先搜索回溯算法有着非常详尽的描述。
八个皇后在8×8棋盘上共有4,426,165,368 种摆放方法,但只有92个互不相同的解。如果将旋转和对称的解归为一种的话,实际上一共只有12个独立解,
以上信息摘自维基百科 https://zh.wikipedia.org/zh-hans/%E5%85%AB%E7%9A%87%E5%90%8E%E9%97%AE%E9%A2%98
闲来无事,写了一段八皇后解题方法,共输出 92 种解题方案。
两种算法输出:分别是 eight.cpp 调用的 GenerateLoop() 循环嵌套算法,以及后的递归算法 GenerateRecursion(),两种算法本质相同,都是逐层搜索、回退、再搜索。而递归算法因为不受循环嵌套的限制,通过修改 main.h 中的“纵向数量”和“横向数量”的数值,可以实现 n×n 的计算,例如 10×10 的计算将找到 724 种解,以此类推。
eight.cpp,入口程序,创建八皇后对象 (封装在 main.h 和 main.cpp),EightQueen 对象有两个主要的生成函数,循环嵌套搜索算法 GenerateLoop() 和递归算法 GenerateRecursion(),它们分别执行生成过程和打印结果。
// eight.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include "main.h"
using namespace std;
int main()
{
// 创建八皇后对象
EightQueen* eightQueen = new EightQueen();
// 生成题解, 逐层搜索搜索, 层级受限
eightQueen->InitCheckerboard();
eightQueen->GenerateLoop();
getchar();
// 生成题解, 递归搜索算法
eightQueen->InitCheckerboard();
eightQueen->GenerateRecursion();
getchar();
return 0;
}
main.h,对象定义,封装所需的函数和内部变量。
#pragma once
#ifndef EIGHT_MAIN
#define EIGHT_MAIN
// 纵向数量, 递归状态下可以随意扩大
#define CHECKER_BOARD_ROW 8
// 横向数量, 递归状态下可以随意扩大
#define CHECKER_BOARD_COLUMN 8
class EightQueen
{
private:
// 定义棋盘结构
int checkerboard[CHECKER_BOARD_ROW][CHECKER_BOARD_COLUMN];
// 初始化当前行
void ClearRow(int row);
// 检查向上冲突
bool CheckVerticalByUp(int row, int column);
// 检查左上角冲突
bool CheckUpperLeftCorner(int row, int column);
// 检查右上角冲突
bool CheckUpperRightCorner(int row, int column);
// 打印八皇后棋盘
void TablePrint();
// 计数
int intPrintCount = 0;
public:
// 初始化棋盘
void InitCheckerboard();
// 生成八皇后题解, 方法 1 嵌套循环
void GenerateLoop();
// 生成八皇后题解, 方法 2 递归
void GenerateRecursion(int row = 0);
EightQueen();
~EightQueen();
};
#endif
main.cpp,主要代码的对象封装。
/*
* 2018-8-13
*
* 创建一个 8x8 棋盘, 0 表示没有皇后,1表示存在皇后
*
* 循环嵌套算法实现
* EightQueen::GenerateLoop()
*
* 递归算法实现
* EightQueen::GenerateRecursion(int row)
*
* loop begin
* 逐层循环,第一层不做检查
* 每一层的每一列进行检查
* 取坐标,检查向上是否存在冲突,如果有则返回 false,否则返回 true
* 取坐标,检查左上角方向是否存在冲突,如果有则返回 false,否则返回 true
* 取坐标,检查右上角方向是否存在冲突,如果有则返回 false,否则返回 true
* 将皇后设置到某个指定的坐标
* loop end
*
*/
#include "stdafx.h"
#include <stdio.h>
#include "main.h"
// using namespace std;
EightQueen::EightQueen()
{
}
EightQueen::~EightQueen()
{
}
// 初始化棋盘
void EightQueen::InitCheckerboard()
{
for (int i = 0; i < CHECKER_BOARD_ROW; i++)
{
for (int j = 0; j < CHECKER_BOARD_COLUMN; j++)
{
checkerboard[i][j] = 0;
}
}
// 计数器
intPrintCount = 0;
}
// 打印八皇后解题表格
void EightQueen::TablePrint()
{
// 逐行输出棋盘信息
for (int row = 0; row < CHECKER_BOARD_ROW; row++)
{
// 设置表宽度
const int PrintLineLen = CHECKER_BOARD_COLUMN * 2;
// 定义输出表的行, +1 需要一个字符串结束符 0x0
char strPrintLine[PrintLineLen + 1];
// 初始化输出表的行
memset(strPrintLine, 0x0, PrintLineLen + 1);
// 绘制棋盘行, 用 " " 和 "." 标记
for (int i = 0; i < PrintLineLen; i++)
{
// 创建表格, 留一空格跳空,更易于识别
if (i % 2 == 0)
{
strPrintLine[i] = '.';
}
else
{
strPrintLine[i] = ' ';
}
}
// 遍历棋盘的行, 找到皇后, 则标记为 'x'
for (int i = 0; i < CHECKER_BOARD_COLUMN; i++)
{
// 找到一个皇后,标记为 x
if (checkerboard[row][i] == 1)
{
// 跳空置子
strPrintLine[i * 2] = 'x';
}
}
// 打印一行
printf("%s\n", strPrintLine);
}
}
// 检查垂直向上的冲突
bool EightQueen::CheckVerticalByUp(int row, int column)
{
// 避开当前的皇后位置
row--;
// 保持列,检查所有行是否存在皇后
while (row >= 0)
{
int val = checkerboard[row--][column];
if (val == 1)
{
return false;
}
}
return true;
}
// 检查左上对角线的冲突
bool EightQueen::CheckUpperLeftCorner(int row, int column)
{
// 避开当前的皇后
row--;
column--;
// 检查左上角所有的位置是否有皇后
while (row >= 0 && column >= 0)
{
int val = checkerboard[row--][column--];
if (val == 1)
{
return false;
}
}
return true;
}
// 检查右上对角线的冲突
bool EightQueen::CheckUpperRightCorner(int row, int column)
{
// 避开当前的皇后
row--;
column++;
// 检查左上角所有的位置是否有皇后
while (row >= 0 && column < CHECKER_BOARD_COLUMN)
{
int val = checkerboard[row--][column++];
if (val == 1)
{
return false;
}
}
return true;
}
// 初始化当前行
void EightQueen::ClearRow(int row)
{
for (int i = 0; i < CHECKER_BOARD_COLUMN; i++)
{
checkerboard[row][i] = 0;
}
}
// 逐个生成八皇后位置 递归算法
void EightQueen::GenerateRecursion(int row)
{
// 找到结果 显示结果并返回
if (row == CHECKER_BOARD_ROW)
{
// row == CHECKER_BOARD_ROW 已经完成了棋盘上全部 row 的布子
// 打印棋盘
TablePrint();
printf("No.%d\n", ++intPrintCount);
return;
}
// 在当前 row 内找到一个适合的位置放置皇后
for (int c = 0; c < CHECKER_BOARD_COLUMN; c++)
{
// 第一行不作检查
if (row == 0)
{
}
else
{
// 向上检查垂直冲突
if (CheckVerticalByUp(row, c) == false)
{
continue;
}
// 检查左上角冲突
if (CheckUpperLeftCorner(row, c) == false)
{
continue;
}
// 检查右上角冲突
if (CheckUpperRightCorner(row, c) == false)
{
continue;
}
}
// 当前 row 找到一个适合的位置
// 清空当前行
ClearRow(row);
// 置子
checkerboard[row][c] = 1;
// 递归进入下一行
GenerateRecursion(row + 1);
// 若循环内全部被 continue,将回退到上一 row 重新分配位置,直到全部棋盘可能性搜索完
}
}
// 逐行生成八皇后位置,循环嵌套算法
void EightQueen::GenerateLoop()
{
// 计数器
int count = 0;
// 第1行
for (int c0 = 0; c0 < CHECKER_BOARD_ROW; c0++)
{
int row = 0;
ClearRow(row);
checkerboard[row][c0] = 1;
// 第2行
for (int c1 = 0; c1 < CHECKER_BOARD_ROW; c1++)
{
int row = 1;
// 向上检查垂直冲突
if (CheckVerticalByUp(row, c1) == false)
{
continue;
}
// 检查左上角冲突
if (CheckUpperLeftCorner(row, c1) == false)
{
continue;
}
// 检查右上角冲突
if (CheckUpperRightCorner(row, c1) == false)
{
continue;
}
// 放置皇后
ClearRow(row);
checkerboard[row][c1] = 1;
for (int c2 = 0; c2 < CHECKER_BOARD_ROW; c2++)
{
int row = 2;
// 向上检查垂直冲突
if (CheckVerticalByUp(row, c2) == false)
{
continue;
}
// 检查左上角冲突
if (CheckUpperLeftCorner(row, c2) == false)
{
continue;
}
// 检查右上角冲突
if (CheckUpperRightCorner(row, c2) == false)
{
continue;
}
ClearRow(row);
checkerboard[row][c2] = 1;
for (int c3 = 0; c3 < CHECKER_BOARD_ROW; c3++)
{
int row = 3;
// 向上检查垂直冲突
if (CheckVerticalByUp(row, c3) == false)
{
continue;
}
// 检查左上角冲突
if (CheckUpperLeftCorner(row, c3) == false)
{
continue;
}
// 检查右上角冲突
if (CheckUpperRightCorner(row, c3) == false)
{
continue;
}
ClearRow(row);
checkerboard[row][c3] = 1;
for (int c4 = 0; c4 < CHECKER_BOARD_ROW; c4++)
{
int row = 4;
// 向上检查垂直冲突
if (CheckVerticalByUp(row, c4) == false)
{
continue;
}
// 检查左上角冲突
if (CheckUpperLeftCorner(row, c4) == false)
{
continue;
}
// 检查右上角冲突
if (CheckUpperRightCorner(row, c4) == false)
{
continue;
}
ClearRow(row);
checkerboard[row][c4] = 1;
for (int c5 = 0; c5 < CHECKER_BOARD_ROW; c5++)
{
int row = 5;
// 向上检查垂直冲突
if (CheckVerticalByUp(row, c5) == false)
{
continue;
}
// 检查左上角冲突
if (CheckUpperLeftCorner(row, c5) == false)
{
continue;
}
// 检查右上角冲突
if (CheckUpperRightCorner(row, c5) == false)
{
continue;
}
ClearRow(row);
checkerboard[row][c5] = 1;
for (int c6 = 0; c6 < CHECKER_BOARD_ROW; c6++)
{
int row = 6;
// 向上检查垂直冲突
if (CheckVerticalByUp(row, c6) == false)
{
continue;
}
// 检查左上角冲突
if (CheckUpperLeftCorner(row, c6) == false)
{
continue;
}
// 检查右上角冲突
if (CheckUpperRightCorner(row, c6) == false)
{
continue;
}
ClearRow(row);
checkerboard[row][c6] = 1;
for (int c7 = 0; c7 < CHECKER_BOARD_ROW; c7++)
{
int row = 7;
// 向上检查垂直冲突
if (CheckVerticalByUp(row, c7) == false)
{
continue;
}
// 检查左上角冲突
if (CheckUpperLeftCorner(row, c7) == false)
{
continue;
}
// 检查右上角冲突
if (CheckUpperRightCorner(row, c7) == false)
{
continue;
}
// 找到位置并置子
ClearRow(row);
checkerboard[row][c7] = 1;
// 打印表格
printf("No.%d: R0=%d, R1=%d, R2=%d, R3=%d, R4=%d, R5=%d, R6=%d, R7=%d\n", ++count, c0, c1, c2, c3, c4, c5, c6, c7);
TablePrint();
}
}
}
}
}
}
}
}
printf("Found %d, press enter to exist.", count);
}
八皇后问题的解输出,两种算法都各找到 92 种解。
Q群讨论 236201801
.