hello!大家好,我是浪矢秀一。最近经历了许多事情,终于是恢复1次更新了。那么今天呢,我们来学习中级篇,需要学过不少python知识的人来学习。好了,废话不多说,我们进入今天的课程!
在1个n*n的国际象棋棋盘上,放置n个皇后,要求:同1行、同1列、同1斜线上只能有1个皇后。
既然是有很多行,分别满足不同条件,那么我们可以进行枚举每行,再枚举每列。但是,如果1行都不满足的话,就要返回上行,for循环是做不到的。那么,我们可以用回溯算法来做。
那么,什么是回溯算法呢?
很简单。举个例子吧,你去山洞探险,要找到1条通路。现在,你面前有3条路,每1条都很长。那么,你该怎么做呢?
我们只要先进去1号山洞。如果通了,就走出去了;如果没通,就返回来,尝试下个山洞。接着继续判断2号山洞,按同样的方法。最终,你一定会找到出口。
那么,这个"走不通就返回"的过程,就是类似于回溯算法。只要哪里不满足条件,就返回上一步去做。
那么,既然不能用for循环来做,我们该怎么做呢?
很简单,回溯算法最常见的做法就是递归算法了。
所谓递归算法,就是某个函数直接或间接地调用了自己,从而实现一系列算法。递归函数只要不达到条件,就一直调用自己,这是"递"。直到条件满足,再退出自己,这是"归"。
我们该怎么用递归来做这题呢?
好的,开始代码讲解。
首先,我们要定义变量n,作为"n皇后问题"中的n。
这里用"8皇后问题"举例。
n=8 #设置n
ans=0 #保存总共有多少种解法
#这里定义递归函数Queen(x),用来输出结果
Queen(1) #调用递归函数,进行计算并输出
print(f"{n}皇后问题有{ans}种解法") #输出共有多少解法
那么,我们只要定义好递归函数Queen(x)就可以了。
那么,怎么保存每次答案呢?
我们定义整型(int)列表a。那么,a[i]就表示第i行的皇后放在第a[i]列。
因为python的列表不像C++的数组,列表不能声明长度。那么,我们可以理解为,列表没有严格意义上的长度,可以越界写,但不能越界读。如果越界读,就会报错:
IndexError: list index out of range
为了防止数组越界的现象,我们可以定义变量(其实算是常量)maxN,用于表示最大的数字。列表定义了之后有些地方可以不用,但千万不能少定义空间而数组越界。
那么,maxN差不多就定义成:
maxN=n+5
这里的数字5只是例子,只要不越界就行。
列表a的定义:
a=[0 for i in range(1,maxN+1+1,1)]
range中的stop第1个"+1"是因为range的条件是小于某数,所以要加1;第2个"+1"是因为调用的是数组下标,a[0]不用,从a[1]开始,就得多定义1个。
好了,我们开始定义Queen(x)函数。Queen(x)中的x是函数参数,代表确定第几行的皇后在第几列。在此之前,1~(x-1)行的皇后位置必须确定好,要不然没法推算。
首先,我们要判断是否产生了新的结果。如果是,则输出。
def Queen(x):
global n,a,ans #因为n,a,ans是函数外的变量,要改变它们的值,得先global一下
if x>n: #如果递归到了第x行,且x行大于n,就说明产生了新的结果
ans+=1 #解法加1
print(f"第{ans}种解法:\n") #对第ans种解法进行输出提示
for i in range(1,n+1,1): #轮番遍历a,输出"第i行的皇后应该放在第a[i]列
print(f"第{i}行的皇后应该放在第{a[i]}列")
return #退出,继续计算其他可能
判断是否产生结果的方法也很简单,因为我们以后如果满足条件,就需要递归Queen(x+1)来计算下1行,所以如果参数x大于了边界n,就意味着前面不仅都满足条件,还放置完毕了,也就相当于产生了结果。
那么,如果没有产生新结果,那么,我们就需要轮番遍历第x行的每1列是否满足条件。
那么,怎么判断是否满足条件呢?
这个就不太简单了。我们需要同时满足3个条件,并且之后的也得满足。
既然这样,我们就可以定义3个布尔(bool)型列表来判断是否满足条件。
定义之前,我们得先看下它的规律。
以n=4为例,那么每个格子的位置用数对来表示就是:
(1,1) | (2,1) | (3,1) | (4,1) |
(1,2) | (2,2) | (3,2) | (4,2) |
(1,3) | (2,3) | (3,3) | (4,3) |
(1,4) | (2,4) | (3,4) | (4,4) |
首先判断"这1列是否有皇后"。这个很简单,定义布尔(bool)型列表c,c[i]表示第i列是否有皇后,True表示有,False表示没有。如果没有,就尝试放置,并把c[i]更新为True。
定义列表c:
c=[False for i in range(1,maxN+1+1,1)]
每列的问题解决了。我们来看斜线问题。
我们把从左上到右下的斜线称为主斜线,从右上到左下的斜线称为副斜线。
如何判断主对角线呢?
和列一样,我们定义列表d。那么,怎么取下标呢?
举个例子:
以:
(1,2) | |||
(2,3) | |||
(3,4) |
这条斜线为例。
你有什么发现?
没错。判断(x,j)是否与之前的冲突,就只需要判断d[x-j]就行了。但是,x-j的范围是(1-n)~(n-1),可能取负数,而下标不能为负数,所以我们要判断d[x-j+n]是否为True。如果是,就代表不能放置;如果否,就代表可以放置,再尝试放置,将d[x-j+n]更新为True。
定义列表d:
d=[False for i in range(1,2*maxN+1,1)]
主斜线的问题解决了。我们来看副斜线问题。
和列一样,我们定义列表e。那么,怎么取下标呢?
以:
(4,1) | |||
(3,2) | |||
(2,3) | |||
(1,4) |
这条斜线为例。
你发现了什么?
没错。判断(x,j)是否与以前冲突,就只需要判断e[x+j]就行了。我们要判断e[x+j]是否为True。如果是,就代表不能放置;如果否,就代表可以放置,再尝试放置,将e[x+j]更新为True。
定义列表e:
e=[False for i in range(1,maxN+1+1,1)]
好了,条件问题我们解决了。我们继续编写Queen(x)。
如果没有算出新结果,就枚举列数j作为放置位置。
for j in range(1,n+1,1):
if((not c[j]) and (not d[x-j+n]) and (not e[x+j])): #判断是否冲突
c[j]=d[x-j+n]=e[x+j]=True #更新冲突关系
a[x]=j #更新放置位置
Queen(x+1) #判断下1行
c[j]=d[x-j+n]=e[x+j]=False #如果返回,说明下1行无法放置,就将此行位置移动
a[x]=0 #把放置位置退出
现在,Queen(x)函数就编写好了。
Queen(x)总代码:
def Queen(x):
global n,a,c,d,e,ans
if x>n:
ans+=1
print(f"第{ans}种解法:")
for i in range(1,n+1,1):
print(f"第{i}行的皇后放在第{a[i]}列")
print("")
return
for j in range(1,n+1,1):
if((not c[j]) and (not d[x-j+n]) and (not e[x+j])):
c[j]=d[x-j+n]=e[x+j]=True
a[x]=j
Queen(x+1)
c[j]=d[x-j+n]=e[x+j]=False
a[x]=0
n=8 #定义n皇后问题中的n
maxN=n+5
a=[0 for i in range(1,maxN+1,1)]
c=[False for i in range(1,maxN+1,1)]
d=[False for i in range(1,2*maxN+1,1)]
e=[False for i in range(1,2*maxN+1+1,1)]
ans=0
def Queen(x):
global n,a,c,d,e,ans
if x>n: #是否得到新结果
ans+=1
print(f"第{ans}种解法:")
for i in range(1,n+1,1):
print(f"第{i}行的皇后放在第{a[i]}列") #输出
print("")
return
for j in range(1,n+1,1): #枚举列数
if((not c[j]) and (not d[x-j+n]) and (not e[x+j])): #判断与之前的皇后是否冲突
c[j]=d[x-j+n]=e[x+j]=True
a[x]=j
Queen(x+1) #递归,计算下1行
c[j]=d[x-j+n]=e[x+j]=False
a[x]=0
Queen(1) #从第1行开始计算
print(f"\n{n}皇后问题共有{ans}种解") #输出解法总和
好了,今天的课程就学到这里。我是浪矢秀一,关注我,带你从python小白变成编程大神!