本期任务:介绍算法中关于回溯思想的几个经典问题
一、问题描述
在8X8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法,并将对应的摆法展示
二、算法思路
1. 策略选择
- 八皇后问题是典型的“多阶段决策最优解”问题:每个棋子决策一次,共决策8次;最优解是满足同一行、同一列或同一斜线上不冲突。
- 不满足无后效性:一个棋子摆放之后,可能导致不存在可行解,即后面的状态可能影响前面状态,故不能使用动态规划。
- 本问题也不满足贪心选择性,即无法通过局部最优的选择,能产生全局的最优选择,故不能使用贪心策略。
- 本题使用回溯算法暴力穷举所有可能的排列方式,并通过剪枝策略进行优化。
2. 回溯算法思路
- 暴力穷举,每一行都可能有8种可摆放位置,所有可能的摆放方式共有 8 8 8^8 88,穷举过程遵循深度优先搜索规则。
- 维护一个长度为8的数组res,用于保存每一行的棋子的列号。
- 剪枝策略:利用数组res,若当前棋子位置与前面棋子出现行、列或斜向冲突时跳过。
- 结算情形:8个棋子都放置好了
以4皇后为例,画递归树如下:
三、Python代码实现
class Solution:
def totalNQueens(self, n):
self.size = n # 问题规模(皇后数)
self.res = [-1] * self.size # 用来记录每一行中皇后在第几列
self.count = 0 # 记录符合要求的排列个数
self.helper() # 回溯求解
return self.count
def helper(self, row=0):
"""回溯的主逻辑"""
if row == self.size: # 棋子都放置好了,打印结果
self.printRes() # 打印结果
self.count += 1 # 符合要求的排列个数加一
return
for col in range(self.size): # 每一行都有8中放法
if self.isOk(row, col): # 有些放法不满足要求
self.res[row] = col # 第row行的棋子放到了column列
self.helper(row + 1) # 考察下一行
def isOk(self, row, col):
"""判断当前位置是否安全"""
for r in range(row):
if self.res[r] == col: # 同列不安全
return False
elif row + col == r + self.res[r]: # 主对角线不安全
return False
elif row - col == r - self.res[r]: # 副对角线不安全
return False
return True
def printRes(self):
"""打印结果"""
for i in range(self.size):
for j in range(self.size):
if self.res[i] == j:
print(' Q ', end='')
else:
print(' * ', end='')
print()
print()
def main():
client = Solution()
client.totalNQueens(n=4)
print(client.count)
if __name__ == '__main__':
main()
运行结果
* Q * *
* * * Q
Q * * *
* * Q *
* * Q *
Q * * *
* * * Q
* Q * *
2