回溯法——n皇后问题、2n皇后题解
1. 回溯算法详解
回溯算法模板
result = [] def backtrack(路径, 选择列表): if 满足结束条件: result.add(路径) return for 选择 in 选择列表: 做选择 backtrack(路径, 选择列表) 撤销选择
【注意】python中如果append列表要用深拷贝! Blist.append(Alist.copy())
2. n皇后问题
思路其实很简单,我们其实可以直接采用一个一维数组,来表示棋盘,因为棋盘上一行只能有一个皇后,所以的话,我们这样子表示的话,直接能表示出每一个皇后在第几行第几列,然后我们就一行行来找,第一行放在第几个,第二行放在第几个…,如此直到找到每一行都有一个皇后的解,然后tot++;然后返回,这一行继续往下找,看看是否还有满足的解,主要的是一个判断函数,就是在这一行之前看看是否有皇后与之同列或者对角线即可。
C++代码如下
#include<bits/stdc++.h> #define N 10 using namespace std; int n;//输入n皇后 int ans[N];//存放每一行皇后摆放的位置,行是隐式的,只用存放列就行,只需一维数组就可以表示!!,这边从ans[1]代表第一行摆放位置 int tot = 0;//用于统计解的个数 bool check(int ans[],int x,int y){//判断是否冲突 x行y列,有冲突返回0,没冲突返回10
bool flag = 0; int res; for(int i=1;i<=x-1 && !flag;i++){//当前x行,看前面1- - - x-1行, 同时flag主要是避免重复判读,只要有一行有冲突,就不用判断了,直接flag=1 res = ans[i];//res临时存放当前行皇后摆放的列索引 if(res == y || abs(x - i) == abs(y - res))//判断一下,我们当前要放的该行x该列y,与遍历的行i列res,是否同列 res==y? 是否在对角线上?注意对角线的判断条件写法! 举个特例即可理解 1,1 3,3 两个坐标是在对角线上,满足该公式 flag = 1; } return !flag; } void dfs(int m){//回溯+深度优先搜索 if(m == n+1){//一共n行,全部放完n行,记录一次结果 tot++; return ; } for(int j=1;j<=n;j++){//遍历n列 if(check(ans,m,j)){//如果不冲突 看当前防止位置m,j是否冲突
ans[m] = j;//放置并记录 dfs(m+1);//进入下一行放置皇后 } } return ; } int main(){ cin>>n; dfs(1);//从第一行开始 cout<<tot<<endl; return 0; }
Python代码如下
def check(board, row, col): for i in range(row): if abs(board[i]-col) == 0 or abs(board[i]-col) == abs(i-row):#不能放在同一列或者对角线上 return False return True def dfs(board, row):#board是棋盘选择列表,row是路径 if row == len(board): print(board) printBoard(board); return; for col in range(len(board)): if check(board, row, col): board[row] = col dfs(board, row+1)
board[row] = 0 def printBoard(board): for i,col in enumerate(board):#注意这里遍历枚举的方法,可以把一维的数组打印出二维 print('□ ' * col + '■ ' + '□ ' * (len(board) - 1 - col)) print("") board = [0 for i in range(4)] #模拟棋盘,这里用一维模拟,因为行是隐式的,只用存储列下标 dfs(board, 0) #从第0行开始放皇后
运行结果如下
3. 2n皇后 -蓝桥杯基础
https://www.dotcpp.com/oj/problem1460.html 连接
这题是八皇后问题的变形、八皇后是放一个皇后、本题2n皇后是放两个皇后。
解题思路:
我们可以先放好一个皇后后再放另一个皇后。在图里可以放皇后的格子为1,所以我们可以将不同皇后设置不同的数字来代表,比如2代表黑皇后,3代表白皇后。我们每放一个皇后时先检查他所在列,和两边的对角线有没有放皇后或者说是不能放皇后,判断条件是格子的数是否为一,不为一则是放了皇后或者是不能放皇后。放完最后一行后、我们在dfs函数里判断当前放的皇后是否是将所有的皇后放完了,我们可以用一个数字s代表当前放的棋子,判断条件是s是否等于最后要放的棋子,如果是则放完了计数器count加一,否则继续放棋子,从第一行开始,传下一个代表棋子的数字参数。看到这再看代码相信就明白了。
n = int(input()) mapL = [list(map(int,input().split())) for _ in range(n)] #模拟棋盘,接收二维数组 count = 0 #计数器 def dfs(row,n,s,mapL):#row行 n列 s代表当前放置的皇后是黑or白,maL代表棋盘 global count if row == n: #判断是否是放完了最后一行,注意我的行数是从0开始,0代表第一行,所以从0---n-1 行数 if s == 2: #2代表黑皇后,3代表白皇后 dfs(0,n,3,mapL) #2黑皇后放完,开始放白皇后,从0行重新开始 if s == 3: #全部放完,统计一次答案 count += 1 return for i in range(n):#从n列中做选择 if mapL[row][i] != 1: #不为1、说明放了皇后,或者不能皇后,选择下一列 continue if check(row,i,s,mapL): #当前放置的位置为row,i,要放置的皇后为s,检测是否冲突 mapL[row][i] = s #做选择,可以放,将格子的数字变为放置对应皇后的数字 dfs(row+1,n,s,mapL) #继续到下一行row+1放下一个同色皇后 mapL[row][i] = 1 #撤销选择 def check(row,j,s,mapL): for i in range(0,row-1): #检查对应列,列不动,遍历前面0-row-1所有行 if mapL[i][j] == s: return False r = row - 1 k = j - 1 while r>=0 and k>=0: #检查对应左上角r.k 是row-1,j-1左边的左上角的一个坐标 if mapL[r][k] == s: return False r -= 1 #继续找左上角一个 k -= 1 r = row -1 #重新设置初始值,为右上角一个坐标 k = j + 1 while r>=0 and k<n: #检查对应右上角,直到右角边界 if mapL[r][k] == s: return False r -= 1 k += 1 #继续找右上角一个 return True dfs(0,n,2,mapL) print(count)
注意这里判断函数的写法,与n皇后区分,n皇后只有一种棋子,棋子记1,可以用一个board来记录放置的列,从而写出判断条件;而2n皇后棋子记2 3 无法用一维记录,只能用二维记录,判断条件是一个个找右上角,注意写法!!!先定位到右上角,然后while直到边界,不断的前进。
4.拓展 leetcode n皇后问题解决
https://blog.csdn.net/qq_42589613/article/details/110801312
class Solution: def solveNQueens(self, n: int) -> List[List[str]]: board = [['.'] * n for _ in range(n)]#建立棋盘二维的 res = [] def check(board,row, col): #判断同一列是否冲突 for i in range(len(board)): if board[i][col] == 'Q': return False # 判断左上角是否冲突 i = row -1 j = col -1 while i>=0 and j>=0: if board[i][j] == 'Q': return False i -= 1 j -= 1 # 判断右上角是否冲突 i = row - 1 j = col + 1 while i>=0 and j < len(board): if board[i][j] == 'Q': return False i -= 1 j += 1 return True def backtrace(board, row, n):#row 路径 n 选择列表 # 如果走到最后一行,说明已经找到一个解 if row == n: temp_res = [] for temp in board:#遍历每一行,['.','Q','.']每行是列表,需要拼接成字符串再装配 temp_str = "".join(temp)#把列表每个元素拼接成字符串 temp_res.append(temp_str) res.append(temp_res) for col in range(n): if check(board, row, col): board[row][col] = 'Q' backtrace(board, row+1, n) board[row][col] = '.' backtrace(board, 0, n) return res
注意格式问题,还是套回溯的模板