问题:n皇后问题研究的是如何将n个皇后放在n*n的棋盘上,并且使皇后彼此之间不能相互攻击。
这就是棋盘的样子,那么我们想要放置进去皇后,那么我们就要让皇后的放置满足题意的要求,也就是皇后不能同行、同列、同斜线,只要能满足这三个条件,那么我们放置皇后的位置一定就是正确的。
解决思路:深度优先遍历、回溯、剪枝;
dfs算法分析:直接利用dfs对棋盘进行搜索,满足不放在同行、同列、同斜线上就可以防止皇后,直到搜完整个棋盘,这时便找到了一组解,将其输出。
C语言代码:
dfs模板
#include <iostream>
using namespace std;
const int N = 20;
// bool数组用来判断搜索的下一个位置是否可行
// col列,dg对角线,udg反对角线
// g[N][N]用来存路径
int n;
char g[N][N];
bool col[N], dg[N], udg[N];
void dfs(int u)
{
// u == n 表示已经搜了n行,故输出这条路径
if (u == n)
{
for (int i = 0; i < n; i ++ ) puts(g[i]); // 等价于cout << g[i] << endl;
puts(""); // 换行
return;
}
//对n个位置按行搜索
for (int i = 0; i < n; i ++ )
// 剪枝(对于不满足要求的点,不再继续往下搜索) udg[n - u + i],+n是为了保证大于0
if (!col[i] && !dg[u + i] && !udg[n - u + i])
{
g[u][i] = 'Q';
col[i] = dg[u + i] = udg[n - u + i] = true;
dfs(u + 1);
// 恢复现场 这步很关键
col[i] = dg[u + i] = udg[n - u + i] = false;
g[u][i] = '.';
}
}
int main()
{
cin >> n;
for (int i = 0; i < n; i ++ )
for (int j = 0; j < n; j ++ )
g[i][j] = '.';
dfs(0);
return 0;
}
另一种写法(回溯):
#include <iostream>
#include<algorithm>
using namespace std;
const int N=100;
int n;
int x[N];
bool ok(int t)
{
for(int i=1; i<t; i++)
{
if(x[t]==x[i]||(abs(t-i)==abs(x[t]-x[i])))
{
return false;
}
}
return true;
}
void bc(int t)
{
if(t>n)
{
for(int i=1; i<=n; i++)
{
cout<<x[i]<<endl;
}
printf("\n\n");
}
else
{
for(int i=1; i<=n; i++)
{
x[t]=i;
if(ok(t))
{
bc(t+1);
}
}
}
}
int main()
{
cin>>n;
for(int i=1; i<=n; i++)
{
x[i]=0;
}
bc(1);
return 0;
}
回溯:把它看做树的结构,树的每一层都有n个子树,每一个子树又有n个子树,只要我们递归的对子树进行搜索,满足条件的我们记录下来,这样我们首先想到了深搜,为了判断条件,我们便要剪枝(除去不满足条件的子树),
再次去递归遍历,也就是回溯。
一行一行地摆放,在确定一行中的那个皇后应该摆在哪一列时,需要当前列是否合法(也就是满足上述的三个条件),如果合法,则将皇后放置在当前位置,并进行递归,回溯。每行都摆满皇后时,则产生了一种解法,将所有解法收集并返回。
判断合法:当前将要摆放皇后的位置和其他已摆放皇后的位置不能在同一列,且不能在同一条45度斜线或135度斜线上。这里判断是否在同一条斜线上可通过当前将要摆放皇后的位置和其他已摆放皇后的位置横坐标之差和纵坐标之差的绝对值是否相等来判断。
关于判断条件:
假设x是问题的解 形式是n元组 (x1,x2,x3,x4…xn),其中xi表示代表第i个皇后放置在 第i行第xi列xi的值是 1,2,3,…n
#确定约束条件,第i个皇后和第j个皇后不同列 ,所以条件:(1)xi!=xj
第i个皇后和第j个皇后不同斜线,所以条件:(2)|i-j|!=|xi-xj|
python版本:
回溯
#1
def cmp(arr,row,col):
for i in range(row):
if col==arr[i] or abs(i-row)==abs(arr[i]-col):
return False
return True
def dfs(arr,row):
n=len(arr)
if row>=n:
for i, j in enumerate(arr):
print('.' * j + 'Q' + '.' * (len(arr) - 1 - j))
print("")
col=0
while col<n:
for col in range(n):
if cmp(arr,row,col):
arr[row]=col
dfs(arr,row+1)
col+=1
n=int(input("请输入皇后的数量:"))
arr=[0 for i in range(n)]
dfs(arr, 0)
另一种写法(也是回溯)
def Ok(k): #剪枝函数 k表示的是行数 判断是否同行同列或者在两条对角线上
for j in range(k): #因为按行枚举,所以一定不会同行,只需要判断同列或同对角线
if (x[k] == x[j] or (abs(k - j) == abs(x[k] - x[j]))): #如果同列或者同对角线就返回false
return False
return True #否则就返回true
def backtrack(x,t): #回溯函数
n=len(x) #n是x数组的长度
global sum #定义全局变量sum来表示放置方法的数量
if (t >=n): #如果t>=n那么说明走完了枚举的整个路径
sum+= 1 #走完整个路径sum+1
for i in range(n): #输出x数组
print(x[i],end=' ')
print()
else:
for i in range(n): #按行枚举 优化时间复杂度
x[t] = i+1 #记录下来
if (Ok(t)): #如果可以放置皇后,就往下在递归一层,直到走完整个路径
backtrack(x,t + 1)
if __name__ == "__main__":
n=int(input("请输入皇后的数量:"))
x = [0 for i in range(n)] #定义x数组
sum=0 #sum初始值为0
backtrack(x,0) #从第0层开始递归
print("共有:" + str(sum) + "种放置方案")
python dfs模板:
def dfs(arr,row): #dfs+回溯
n=len(arr)
if row==n:
for i in range(n):
print("".join(res[i]))
print()
for i in range(n):
if col[i] and dg[i+row] and udg[i-row+n]:
col[i],dg[i+row],udg[i-row+n]=False,False,False
res[row][i]='Q'
dfs(arr,row+1)
col[i],dg[i+row],udg[i-row+n]=True,True,True
res[row][i]='.'
n=int(input("请输入皇后数量:"))
col=[True]*n #列
dg=[True]*(2*n) #对角线
udg=[True]*(2*n) #反对角
res=[['.']*n for i in range(n)]
dfs(res,0)