python中级篇1:n皇后问题(回溯算法)

hello!大家好,我是浪矢秀一。最近经历了许多事情,终于是恢复1次更新了。那么今天呢,我们来学习中级篇,需要学过不少python知识的人来学习。好了,废话不多说,我们进入今天的课程!

 

n皇后问题题目

在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小白变成编程大神!

 

你可能感兴趣的:(算法,python)