人工智能导论:回溯算法与八皇后问题(Python实现)
速通回溯算法与八皇后问题
一、问题引入:
八皇后问题:如何能够在8×8的国际象棋棋盘上放置八个皇后,使得任何⼀个皇后都无法直接吃掉其他的皇后(即任两个皇后都不能处于同⼀条横⾏、纵行或斜线上)?请求出八皇后问题的所有解。
二、分析问题:
我们人类如何手动摆放八个皇后呢。思考后得出人类的摆放方式如下:
①在第一行的第一个位置摆放一个皇后
②在第二行的第一个位置摆放一个皇后,不满足规则,移除
③在第二行的第二个位置摆放一个皇后,不满足规则,移除
④在第二行的第三个位置摆放一个皇后
⑤在第三行的第一个位置摆放皇后,不满足规则,移除
……
⑥在第三行的第五个位置摆放皇后
……
总之我们每摆放一个皇后,都会判断它是否会被棋盘上已存在的皇后吃掉。这是一个很聪明的算法,比暴力枚举快多了。
还有一个问题:万一第八个皇后摆哪里都会被吃掉,那该怎么办?那一定是第七个皇后摆放的列有问题。我们需要把第七个皇后移动到下一个允许的列,再重新摆放第八个皇后。
又出现一个问题:第七个皇后已经到了最后一个允许的列了,怎么办?很简单,把第六个皇后移动到下一个允许的列,再重新摆放第七个皇后。
以此类推,当第二个皇后无处可摆时,我们就会把第一个皇后从第一列移动到第二列,然后重复上面引用块中的操作。
这就是回溯算法的大致思路,下面将用递归的方法实现。
三、回溯算法
1.解决问题的过程:
我们定义一个函数solve_n_queens(),函数中传入参数n表示棋盘的大小;用一个一维列表代表棋盘(想想看为什么没必要用二维列表?),索引代表行,元素的值代表皇后所在的列,一开始棋盘是空的,所以就把所有元素都设置为-1;用result列表储存所有可能的解(result的元素类型是什么?):
import copy
def solve_n_queens(n):
# 棋盘
board = [-1]*n
# 存储所有解
result = []
上面引入了copy包进行深度拷贝。
写一个判断该棋子是否会被前几行的皇后吃掉的函数:
#检测冲突
def is_valid(row, col):
for i in range(row):
if abs(row-i) == abs(board[i]-col) or board[i] == col:
return False
return True
紧接着是代码的核心,通过递归的方法实现回溯:
# 回溯(递归)添加行
def create(row):
if row == n:
result.append(board.copy())
return
for i in range(n):
if is_valid(row, i):
board[row] = i
create(row+1)
board[row] = -1
分析一下上面的函数,循环中的i代表列数,即从这一行的第一列开始尝试摆放皇后,如果没有冲突,把此时的列添加进解后,就开始摆放下一行,第row行第i列的可能性尝试尽后,重置该行的解,并尝试在该行的下一个可能的列中摆放皇后。
如果row的大小刚好超过了列表的最大索引,就说明最后一行的皇后摆放成功了!我们将整个board添加进解集(即result列表)中,并直接返回。
最后再加上一个打印所有解的棋盘的函数:
# 打印棋盘
def printb():
for b in result:
for i in range(n):
for j in range(n):
if b[i] == j:
print("Q", end = '')
else:
print("□", end = '')
print()
print()
print("共有", len(result), "组解。")
在solve_n_queens()内依次调用create()函数(传入参数为0,即从第一行开始寻找解)和printb()函数。在solve_n_queens()外部调用solve_n_queens(),传入的参数(即棋盘大小)任意。
create(0)
printb()
solve_n_queens(9)
2.完整代码
import copy
def solve_n_queens(n):
# 棋盘
board = [-1]*n
# 存储所有解
result = []
#检测冲突
def is_valid(row, col):
for i in range(row):
if abs(row-i) == abs(board[i]-col) or board[i] == col:
return False
return True
# 回溯(递归)添加行
def create(row):
if row == n:
result.append(board.copy())
return
for i in range(n):
if is_valid(row, i):
board[row] = i
create(row+1)
board[row] = -1
# 打印棋盘
def printb():
for b in result:
for i in range(n):
for j in range(n):
if b[i] == j:
print("Q", end = '')
else:
print("□", end = '')
print()
print()
print("共有", len(result), "组解。")
create(0)
printb()
solve_n_queens(8)
四、深度思考:
1.回溯算法的本质:
回溯算法的本质是对超大树的深度优先搜索,因为树是很大的,需要进行剪枝,即搜索到一个满足特定条件的节点后,返回上一个节点继续搜索,防止把时间浪费在遍历那个特殊节点下方的枝叶。
以下是一般化的回溯算法解决问题的流程:
回溯法步骤:
①定义解空间(描述解):用什么向量表示问题的解?向量中的每个变量如何取值?
②确定解空间的结构(画地图、做选择):子集树?排列树? m叉树?以及每个节点和边的含义。
③确定剪枝函数(约束):提前终止不符合条件的分支,以深度优先搜索解空间。
④撤销选择(回溯):每次递归后恢复上一步状态。
2.逻辑约束问题(CLP)
在文章的开头,我们用人的解决问题的逻辑引入了回溯算法。这是不是有一点点人工智能的味道了?下面我们介绍一种早期人工智能典型的编程范式。
逻辑编程是⼀种编程典范,它设置答案须匹配的规则来解决问题,而非设置步骤来解决问题。过程是“事实+规则=结果”。人工智能的发展与逻辑编程的发展是⼀个相辅相成的过程,早期的人工智能以规则和逻辑推理作为主要研究方向,这在逻辑编程的发展中发挥了重要的影响,另外更好更快的逻辑编程也推动了人工智能的发展,例如专家系统、知识图谱和自动定理证明。
逻辑约束问题是一类特殊的约束满足问题(CSP),它通过逻辑编程的方式声明变量之间的约束关系,由系统寻找满足所有约束的解。
核心要素:
变量:需要确定值的未知量
值域:每个变量可能的取值范围
约束:变量间必须满足的关系
适用场景:逻辑编程特别适合规则逻辑明确但解空间复杂的场景。例如:工作表编排、物流配送规划等
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐


所有评论(0)