【八皇后问题】

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

■  描述 & 实现

  之前的Python基础那本书上介绍递归和生成器的一张有解过这个问题。书本中对于此问题的解可能更偏重于对于Python语言的应用。然而果然我也是早就忘光了。下面再来从头看看这个问题。

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

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

  第一个需要解决的小问题就是,如何用数学的语言来表述斜线上重叠的皇后。其实我们可以看到,对于位于(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函数暂时先不实现passdef EightQueen(board,row):blen = len(board)if row == blen:    # 来到不存在的第九行了print boardreturn True    # 一定要return一个True,理由在下面for possibleY in range(blen):if check(board,(row,possibleY)):board[row][possibleY] = 1    # 放置一个Queenif not EightQueen(board,row+1):    # 这里其实是本行下面所有行放置皇后的递归入口。但是如果最终这条路没有找到一个解,那么# 此时应该将刚才放置的皇后收回,再去寻找下一个可能的解board[row][possibleY] = 0else:return Truereturn 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函数。其实仔细想一下,如果按照上面的设想来实现check函数还是有点困难的。比如令 x,y = pos,尽管此时我们只需要去检查那些行下标小于x的board中的行,但是对于每一行中我们还是要一个个去遍历,找到相关行中值是1的那个格子(突然发现这个是one-hot模式诶哈哈),然后将它再和x,y这个位置做冲突判断。所以但是这个check函数复杂度就可能会达到O(n^2),再套上外面的循环,复杂度蹭蹭往上涨。下面是check函数的一个可能的实现:

def check(board,pos):x,y = posblen = 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 Falsereturn True

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

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

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

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

  然后是主函数的修改:

def EightQueen(board,row):blen = len(board)if row == blen:    # 来到不存在的第九行了print boardreturn Truecol = 0while col < blen:if check(board,row,col):board[row] = colif EightQueen(board,row+1):return Truecol += 1return Falsedef printBoard(board):'''为了更友好地展示结果 方便观察'''import sysfor 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 boardreturn Truecol = 0while col < blen:if check(board,row,col):board[row] = colif EightQueen(board,row+1):# return True    去掉这里即可,或者直接删除掉整个判断,只留下单一个EightQueen(board,row+1)passcol += 1return 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    # 别忘了放Queenif i >= blen - 1:print board    # i到达最后一行表明已经有了结果breakelse: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;
}

转载于:https://www.cnblogs.com/franknihao/p/9416145.html

【算法】八皇后问题 Python实现相关推荐

  1. python深度优先算法 八皇后_八皇后问题——DFS(深度优先搜索)

    八皇后问题,是在8×8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行.同一列或同一斜线上,问有多少种摆法? 算法思路: 八皇后问题实质为一种深度优先(DFS)搜索问题. ...

  2. 递归算法——八皇后问题 python

    研究了一下午的八皇后算法,可算是搞明白了,为了避免以后忘记,还是写个博客吧!可能会跟其他文章有相似之处,最终还是希望能好好学习算法,都是经过自己思考后亲自写的代码,虽然过程比较艰难,我写了很多注释. ...

  3. 回溯算法(八皇后问题)

    写在前面:博主是一位普普通通的19届双非软工在读生,平时最大的爱好就是听听歌,逛逛B站.博主很喜欢的一句话花开堪折直须折,莫待无花空折枝:博主的理解是头一次为人,就应该做自己想做的事,做自己不后悔的事 ...

  4. 数据结构与算法-- 八皇后问题(多种实现方案)

    八皇后问题解法一(排列筛选法) 本篇我们承接上一篇中的思想,想到了一个经典的算法题,八皇后问题: 题目:在8*8的国际象棋上摆放8个皇后,使得其互相不能攻击,即任意两个换后不能在同一行,同一列,或者同 ...

  5. 八皇后问题python实现

    八皇后问题是一个以国际象棋为背景的问题:如何能够在 8×8 的国际象棋棋盘上放置八个皇后,使得任何一个皇后都无法直接吃掉其他的皇后?为了达到此目的,任两个皇后都不能处于同一条横行.纵行或斜线上.八皇后 ...

  6. 八皇后问题python_八皇后问题Python实现

    八皇后问题描述 问题: 国际象棋棋盘是8 * 8的方格,每个方格里放一个棋子.皇后这种棋子可以攻击同一行或者同一列或者斜线(左上左下右上右下四个方向)上的棋子.在一个棋盘上如果要放八个皇后,使得她们互 ...

  7. 分治回溯算法----八皇后问题

    八皇后问题:在一个8×8的棋盘中,放入8个皇后棋子,要求同行同列同斜线不能有重复的皇后棋子,八皇后问题一共有92种解法.如图所示:即八皇后问题的一个解. //分治回溯算法解决八皇后问题 public ...

  8. 八皇后问题python实现_八皇后问题的python实现

    以前写的一个八皇后问题求解,思路就是每次循环列出所有的可能解,然后过滤出不符合要求的解.详细见代码: //检查两个点是否在攻击线上 def attack(p1,p2): return p1[0]==p ...

  9. 学习笔记-回溯算法(八皇后问题)暴力法

    八皇后问题暴力解决法(介绍代码有说明) 先展示结果: 我这里用的是一维数组来展示的结果 array={7,3,0,2,5,1,6,4} 7的下标为0, 在这里下标+1表示的是第几个皇后也是行的位置,a ...

  10. 八皇后问题python回溯_解决Python基于回溯法子集树模板实现8皇后问题

    这篇文章主要介绍了Python基于回溯法子集树模板实现8皇后问题,简单说明了8皇后问题的原理并结合实例形式分析了Python回溯法子集树模板解决8皇后问题的具体实现技巧,需要的朋友可以参考下 本文实例 ...

最新文章

  1. springmvc开启事务_java面试题 一 :SpringMvc的流程
  2. 南达科他州立大学计算机科学,关于举行南达科他州立大学Srinivas Janaswamy博士学术报告的通知...
  3. java spring 登录验证_详解使用Spring3 实现用户登录以及权限认证
  4. 反思读别人代码的思路
  5. 【渝粤教育】广东开放大学 会展项目管理 形成性考核 (59)
  6. Sqring核心概念
  7. vc++之剪贴板通信实例
  8. java远程方法调用(rmi)--好_java 远程方法调用(RMI)
  9. 大话西游服务器刚维护完几率,大话西游2玩家预约凌烟阁服务器瞬间成功,这算不算被几率...
  10. 基于PYTHON的艺术签名设置
  11. Opencv中的颜色检测
  12. “基于485总线的评分系统”
  13. 如何将计算机的名称改成英文翻译,电脑中的文档怎么进行中英文的翻译
  14. SQL Server的ltrim()和rtrim()函数
  15. 「常见面试题」Java基础之IO模型连环炮
  16. 计算机管理 没有适当的权限,电脑管家没有合适的权限打开是怎么回事?
  17. 建立PXC Percona 5.7 遇到的问题汇总
  18. 弘辽科技:电商创业故事分享
  19. eView触摸屏MT506L
  20. Imago BioSciences宣布获得Omega Funds领投的4千万美元B轮融资

热门文章

  1. 2021-06-29初识JQuery
  2. List常用方法总结
  3. 区块链 共识算法 分类
  4. C++ set 排序 修改元素之后不会改变原来的排序
  5. 值类型和引用类型 是什么 区别
  6. 【Django 2021年最新版教程11】数据库删除操作
  7. 新浪SAE sae_debug保存日志
  8. java定义用户类_用户定义的值类在Java中看起来像什么?
  9. 前端实现用户自定义建表_Excel、SQL、Python分别实现行列转换
  10. php数据库随机选择,php – 在MySQL数据库中选择两个随机行