Python实现八皇后问题示例代码


Posted in Python onDecember 09, 2018

八皇后问题描述

问题: 国际象棋棋盘是8 * 8的方格,每个方格里放一个棋子。皇后这种棋子可以攻击同一行或者同一列或者斜线(左上左下右上右下四个方向)上的棋子。在一个棋盘上如果要放八个皇后,使得她们互相之间不能攻击(即任意两两之间都不同行不同列不同斜线),求出一种(进一步的,所有)布局方式。

首先,我们想到递归和非递归两类算法来解决这个问题。首先说说递归地算法。

很自然的,我们可以基于行来做判断标准。八个皇后都不同行这是肯定的,也就说每行有且仅有一个皇后,问题就在于皇后要放在哪个列。当然八个列下标也都不能有相同,除此之外还要保证斜线上不能有重叠的皇后。

第一个需要解决的小问题就是,如何用数学的语言来表述斜线上重叠的皇后。其实我们可以看到,对于位于(i,j)位置的皇后而言,其四个方向斜线上的格子下标分别是 (i-n,j+n), (i-n,j-n), (i+n,j-n), (i+n,j+n)。当然i和j的±n都要在[0,7]的范围内,保持不越界。暂时抛开越界限制不管,这个关系其实就是: 目标格子(a,b)和本格子(i,j)在同一条斜线上 等价于 |a - i| == |b - j| 。

然后,从递归的思想来看,我们在从第一行开始给每一行的皇后确定一个位置。每来到新的一行时,对本行的所有可能位置(皇后放在这个位置和前面所有已放置的皇后无冲突)分别进行递归地深入;若某一行可能的位置数为0,则表明这是一条死路,返回上一层递归寻找其他办法;若来到的这一行是第九行(不存在第九行,只不过是说明前八行都已经正确配置,已经得到一个解决方案),这说明得到解决方案。

可以看到,寻找一行内皇后应该摆放的位置这是个递归过程,并且在进入递归时,应该要告诉这个过程的东西包括两个: 1. 之前皇后放置的状态, 2. 现在是第几行。

所以,递归主体函数可以设计为 EightQueen(board, row),其中board表示的是当前棋盘的状态(比如一个二维数组,0表示未放置,1表示放有皇后的状态)。另外还可以有一个check(board,pos),pos可以是一个(x,y)元组,check函数用来返回以当前的board棋盘状态,如果在pos再放置一个皇后是否会有冲突。

基于上面的想法,初步实现如下:

def check(board,pos):
 # check函数暂时先不实现
 pass

def EightQueen(board,row):
 blen = len(board)
 if row == blen: # 来到不存在的第九行了
  print board
  return True # 一定要return一个True,理由在下面
 for possibleY in range(blen):
  if check(board,(row,possibleY)):
   board[row][possibleY] = 1 # 放置一个Queen
   if not EightQueen(board,row+1): # 这里其实是本行下面所有行放置皇后的递归入口。但是如果最终这条路没有找到一个解,那么
    # 此时应该将刚才放置的皇后收回,再去寻找下一个可能的解
    board[row][possibleY] = 0
   else:
    return True
 return False

最开始,可能在回归返回条件那里面不会想到要return True,而只是return。对应的,下面主循环中放置完Queen之后也只是简单地递归调用EightQueen,不会做逻辑判断。但是很快可以发现这样做存在一个问题,即当某一层递归中for possibleY这个循环走完却没有找到一个合适的解(即本行无合适位置),此时返回上一行,上一行的possibleY右移一格,此时之前放在这一行的Queen的位置仍然是1。这样之后本行的所有check肯定都是通不过的。所以我们需要设计一个机制,使得第一个possibleY没有找到合理的最终解决方案(这里就加上了一个判断条件),要右移一格到下一个possibleY时将本格的Queen收回。

这个判断条件就是如果某层递归for possibleY循环整个走完未找到结果返回False(EightQueen整个函数最后的返回),上一层根据这个False反馈把前一个Queen拿掉;如果找到了某个结果那么就可以一路return True回来,结束函数的运行。

另外,如果只是获取一个解的话,可以考虑在if row == blen的时候,打印出board,然后直接sys.exit(0)。此时就只需要for possibleY循环完了之后return一个False就可以了。当然主循环中对于递归的返回的判断 if not EightQueen还是需要的。

上面没有实现check函数。其实仔细想一下,如果按照上面的设想来实现check函数还是有点困难的。比如令 x,y = pos,尽管此时我们只需要去检查那些行下标小于x的board中的行,但是对于每一行中我们还是要一个个去遍历,找到相关行中值是1的那个格子(突然发现这个是one-hot模式诶哈哈),然后将它再和x,y这个位置做冲突判断。所以但是这个check函数复杂度就可能会达到O(n^2),再套上外面的循环,复杂度蹭蹭往上涨。下面是check函数的一个可能的实现:

def check(board,pos):
 x,y = pos
 blen = len(board)
 for i in range(x):
  for j in range(blen):
   if board[i][j] == 1:
    if j == y or abs(j-y) == abs(i-x):
     return False
 return True

其实可以看到,我们花了一层循环在寻找某行中的one-hot,那些大量的0值元素是我们根本不关心的。换句话说,对于board这个二维数组,其实我们真正关心的是每行中one-hot值的下标值。自然我们就可以想到,能不能将board转化为一个一维数组,下标本身就代表了board中的某一行,然后值是指这一行中皇后放在第几列。

如果是这样的话,那么程序就需要改造,首先是check函数要根据新的board数据结构做一些调整:

def check(board,row,col):
 i = 0
 while i < row:
  if abs(col-board[i]) in (0,abs(row-i)):
   return False
  i += 1
 return True

可以看到,改变二维数组board变为一维数组之后,我们可以在O(1)的时间就确定row行之前每一行摆放的位置,并将其作为参考进行每一行的冲突判断。

然后是主函数的修改:

def EightQueen(board,row):
 blen = len(board)
 if row == blen: # 来到不存在的第九行了
  print board
  return True
 col = 0
 while col < blen:
  if check(board,row,col):
   board[row] = col
   if EightQueen(board,row+1):
    return True
  col += 1
 return False

def printBoard(board):
 '''为了更友好地展示结果 方便观察'''
 import sys
 for i,col in enumerate(board):
  sys.stdout.write('□ ' * col + '■ ' + '□ ' * (len(board) - 1 - col))
  print ''

总的结构,和没修改之前是类似的,只不过在主循环中,从上面的possibleY作为游标去设置 - 去除 一个位置的放置状态,这种方式改为了简单的col += 1。改成col+=1的好处就是当某轮递归以失败告终,返回上层递归之后,就不用再去特地收回之前放置好的Queen,而是可以直接让col += 1,。

printBoard函数可以将一维数组的board状态很直观地展现出来:

■ □ □ □ □ □ □ □
□ □ □ □ ■ □ □ □
□ □ □ □ □ □ □ ■
□ □ □ □ □ ■ □ □
□ □ ■ □ □ □ □ □
□ □ □ □ □ □ ■ □
□ ■ □ □ □ □ □ □
□ □ □ ■ □ □ □ □

所有结果

上面的程序多只是生成了一个结果,而实际上八皇后可以有很多种可能的布局。如何才能求得所有结果?其实只要小小地修改一下上面的程序就可以了。

以上面修改过后一维数组维护棋盘状态为例。程序在碰到一次row == blen的情况之后就返回了True,然后递归一层层地返回True直到最上层。所以找到一个解决方案之后,程序就会退出了。

反过来,如果获得一个解决方案之后,不判断EightQueen函数的返回,此时函数会继续执行col += 1,将状态搜寻继续下去,如此收集状态的任务在row == blen的判断中,(注意这里的return可不能删,这里需要一个return来提示递归的终结条件),而对于每条递归路径总是穷尽所有可能再回头,这样就可以获得到所有可能了:

def EightQueen(board,row):
 blen = len(board)
 if row == blen: # 来到不存在的第九行了
  print board
  return True
 col = 0
 while col < blen:
  if check(board,row,col):
   board[row] = col
   if EightQueen(board,row+1):
    # return True 去掉这里即可,或者直接删除掉整个判断,只留下单一个EightQueen(board,row+1)
    pass
  col += 1
 return False

示例结果:

[0, 4, 7, 5, 2, 6, 1, 3]
[0, 5, 7, 2, 6, 3, 1, 4]
[0, 6, 3, 5, 7, 1, 4, 2]
[0, 6, 4, 7, 1, 3, 5, 2]
[1, 3, 5, 7, 2, 0, 6, 4]
[1, 4, 6, 0, 2, 7, 5, 3]
[1, 4, 6, 3, 0, 7, 5, 2]
[1, 5, 0, 6, 3, 7, 2, 4]
[1, 5, 7, 2, 0, 3, 6, 4]
…… 总共有92种布局方案

非递归

非递归解这个问题,很显然是要去维护一个stack来保存一个路径了。简单来说,这个栈中维护的应该是“尚未尝试去探索的可能”,当我开始检查一个特定的位置,如果检查通过,那么应该做的是首先将本位置右边一格加入栈,然后再把下一行的第一个格子加入栈。注意前半个操作很容易被忽视,但是如果不将本位置右边一格入栈,那么如果基于本格有皇后的情况进行的递归最终没有返回一个结果的话,接下来就不知道往哪走了。如果使用了栈,那么用于扫描棋盘的游标就不用自己在循环里+=1了,循环中游标的移动全权交给栈去维护。

代码如下:

def EightQueen(board):
 blen = len(board)
 stack = Queue.LifoQueue()
 stack.put((0,0)) # 为了自洽的初始化
 while not stack.empty():
  i,j = stack.get()
  if check(board,i,j): # 当检查通过
   board[i] = j # 别忘了放Queen
   if i >= blen - 1:
    print board # i到达最后一行表明已经有了结果
    break
   else:
    if j < blen - 1: # 虽然说要把本位置右边一格入栈,但是如果本位置已经是行末尾,那就没必要了
     stack.put((i,j+1))
    stack.put((i+1,0)) # 下一行第一个位置入栈,准备开始下一行的扫描
  elif j < blen - 1:
   stack.put((i,j+1)) # 对于未通过检验的情况,自然右移一格即可

显然,把break去掉就是求所有解了

C语言版

#include <stdio.h>

static int board[8] = {};
int board_size = sizeof(board)/sizeof(int);

int check(int *board,int row){
 int i = 0;
 while(i < row){
  if(board[i] == board[row] || row - i == board[row] - board[i] || row - i == board[i] - board[row]){
   return 0;
  }
  i++;
 }
 // printf("board[%d]: %d\n",row,board[row]);
 return 1;
}

void print_board(int *board){
 int i;
 int size = board_size;
 for(i=0;i<size;i++){
  printf("%d,",board[i]);
 }
 printf("\n");
 i = 0;
 while (i < size){
  int j;
  for (j=0;j<size;j++){
   if(j == board[i]){
    printf("%s ","■ ");
   }
   else{
    printf("%s ","□ ");
   }
  }
  printf("\n");
  i++;
 }
}

int eight_queen(int *board,int row){
 if (row == 8){
  print_board(board);
  return 1;
 }
 board[row] = 0;
 while (1){
  if (check(board,row) && eight_queen(board,row+1)){
    return 1;
  }
  else{
   if(++board[row] >= 8){
    break;
   }
  }
 }

 return 0; 
}

int main(){
 eight_queen(board,0);
 // print_board(board);
 return 0;
}

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对三水点靠木的支持。

Python 相关文章推荐
Python实例分享:快速查找出被挂马的文件
Jun 08 Python
Python爬虫中urllib库的进阶学习
Jan 05 Python
python向已存在的excel中新增表,不覆盖原数据的实例
May 02 Python
mac下pycharm设置python版本的图文教程
Jun 13 Python
浅谈pyqt5中信号与槽的认识
Feb 17 Python
Python完成毫秒级抢淘宝大单功能
Jun 06 Python
python画双y轴图像的示例代码
Jul 07 Python
python多进程下实现日志记录按时间分割
Jul 22 Python
解决Python使用列表副本的问题
Dec 19 Python
PyTorch实现ResNet50、ResNet101和ResNet152示例
Jan 14 Python
tensorflow常用函数API介绍
Apr 19 Python
PyCharm上安装Package的实现(以pandas为例)
Sep 18 Python
python中的tcp示例详解
Dec 09 #Python
python数据处理 根据颜色对图片进行分类的方法
Dec 08 #Python
Python发送邮件测试报告操作实例详解
Dec 08 #Python
Python自动发送邮件的方法实例总结
Dec 08 #Python
Python数据集切分实例
Dec 08 #Python
python分批定量读取文件内容,输出到不同文件中的方法
Dec 08 #Python
对python遍历文件夹中的所有jpg文件的实例详解
Dec 08 #Python
You might like
神族 Protoss 历史背景
2020/03/14 星际争霸
有关php运算符的知识大全
2011/11/03 PHP
php获取textarea的值并处理回车换行的方法
2014/10/20 PHP
thinkPHP简单遍历数组方法分析
2016/05/16 PHP
PHP之十六个魔术方法详细介绍
2016/11/01 PHP
JQuery Highcharts 动态生成图表的方法
2013/11/15 Javascript
JS动态添加与删除select中的Option对象(示例代码)
2013/12/20 Javascript
详解JavaScript ES6中的模板字符串
2015/07/28 Javascript
JavaScript实现同一页面内两个表单互相传值的方法
2015/08/12 Javascript
jQuery判断checkbox选中状态
2016/05/12 Javascript
Angular2 (RC5) 路由与导航详解
2016/09/21 Javascript
JavaScript实现构造json数组的方法分析
2018/08/17 Javascript
angularjs性能优化的方法
2018/09/05 Javascript
JavaScript函数、闭包、原型、面向对象学习笔记
2018/09/06 Javascript
JavaScript实现无限级递归树的示例代码
2019/03/29 Javascript
产制造追溯系统之通过微信小程序实现移动端报表平台
2019/06/03 Javascript
[52:07]完美世界DOTA2联赛PWL S3 LBZS vs access 第二场 12.10
2020/12/13 DOTA
python检测服务器是否正常
2014/02/16 Python
CentOS 6.5中安装Python 3.6.2的方法步骤
2017/12/03 Python
EM算法的python实现的方法步骤
2018/01/02 Python
python ChainMap 合并字典的实现步骤
2019/06/11 Python
python随机生成大小写字母数字混合密码(仅20行代码)
2020/02/01 Python
python实现全排列代码(回溯、深度优先搜索)
2020/02/26 Python
Python根据指定文件生成XML的方法
2020/06/29 Python
Django多个app urls配置代码实例
2020/11/26 Python
selenium框架中driver.close()和driver.quit()关闭浏览器
2020/12/08 Python
HTML5混合开发二维码扫描以及调用本地摄像头
2017/12/27 HTML / CSS
HTML5语音识别标签写法附图
2013/11/18 HTML / CSS
澳大利亚在线购买儿童玩具:Toy Universe
2017/12/28 全球购物
巴西Mr. Cat在线商店:购买包包和鞋子
2019/09/08 全球购物
党员倡议书
2015/01/19 职场文书
农村结婚典礼主持词
2015/06/29 职场文书
企业团队精神心得体会
2016/01/19 职场文书
Python3 多线程(连接池)操作MySQL插入数据
2021/06/09 Python
MySQL七大JOIN的具体使用
2022/02/28 MySQL
一文了解Java动态代理的原理及实现
2022/07/07 Java/Android