Python回溯算法练习

1、题目一:全排列问题

1.1 问题描述

输出自然数1到n所有不重复的排列,即n的全排列,要求所产生的任一数字序列中不允许出现重复的数字。

1.2 输入格式

输入 n(1≤n≤9)

1.3 输出格式

由1~n组成的所有不重复的数字序列,每行一个序列。每个数字占5列。

1.4 样例输入

4

1.5 样例输出

    1    2    3    4
    1    2    4    3
    1    3    2    4
    1    3    4    2
    1    4    2    3
    1    4    3    2
    2    1    3    4
    2    1    4    3
    2    3    1    4
    2    3    4    1
    2    4    1    3
    2    1    4    3 
    3    1    2    4
    3    1    4    2
    3    2    1    4
    3    2    4    1
    3    4    1    2
    3    4    2    1
    4    1    2    3
    4    1    3    2
    4    2    1    3
    4    2    3    1
    4    3    1    2
    4    3    2    1

1.6 解题思路

因为是全排列问题,首先最坏实现可以是暴力解,输入n,直接嵌套n个for即可,当然这样肯定是行不通的,遇到排列组合的题目,我们首先要考虑回溯算法,即(DFS),主要是使用递归来实现的,遇到递归题,肯定是要先判断它的终止条件的,因为本题是全排列问题,即退出条件就是当(step==n+1)时列表a肯定已经存放了n个元素即输出1~n的值,再通过回退来实现整个算法。回退是返回上一步递归,并将值清空,标记为未使用,这样就完成了整个全排列。

1.7 运行代码

#方法一:内置函数
from itertools import *
n=int(input())
li=[]
for i in range(1,n+1):
    li.append(str(i))
for i in permutations(li):
    a='    '.join(i)
    print('   ',a)

#方法二:回溯
n=int(input())#输入的数
a=[0 for _ in range(n+2)]#用来存放的列表
used=[False for _ in range(n+2)] #用与记录是否有被用过
def dfs(step):
    global n,a,used #全局变量
    if step==n+1: #终止条件
        #输出结果
        for i in range(1,n+1):
            print("%5d"%a[i],end="")
        print()#换行
    for j in range(1,n+1):
        if not used[j]: #判断是否有使用
            a[step]=j #修改a列表的数
            used[j]=True #修改牌的状态为y已经使用
            dfs(step+1) #继续走到第step+1个盒子上
            used[j]=False #递归结束,返回上一个盒子,修改牌的状态为没有使用
dfs(1)

1.8 运行结果

 2、题目二:组合的输出

2.1 问题描述

排列与组合是常用的数学方法,其中组合就是从n个元素中抽出r个元素(不分顺序且r<=n),我们可以简单地将n个元素理解为自然数1,2,…,n,从中任取r个数。

    现要求你用递归的方法输出所有组合。

    例如n=5,r=3,所有组合为:

    l 2 3   l 2 4   1 2 5   l 3 4   l 3 5   1 4 5   2 3 4   2 3 5   2 4 5   3 4 5

2.2 输入格式

一行两个自然数n、r(1<n<21,1<=r<=n)。

2.3 输出格式

所有的组合,每一个组合占一行且其中的元素按由小到大的顺序排列,每个元素占三个字符的位置,所有的组合也按字典顺序。

2.4 样例输入

5 3

2.5 样例输出

  1  2  3
  1  2  4
  1  2  5
  1  3  4
  1  3  5
  1  4  5
  2  3  4
  2  3  5
  2  4  5
  3  4  5

2.6 解题思路

这道题和上一道题类似,只输出不重复的无序数(组合问题),根据上面那题可以得出结论(1 5 3有不同中解法135 153 513 531),而在本题中,这种数都算是重复的,而最小值是135,所以我们只需要找有序列,即只需寻找当前手中的牌比上一个盒子中的牌要大即可。

2.7 运行代码

n,m=map(int,input().split())
a=[0 for _ in range(n+2)]
used=[False for _ in range(n+2)]
def dfs(step):
    global n,a,used
    if step==m+1:
        for i in range(1,m+1):
            print(a[i],end="")
        print()
    #枚举手中的n个牌,当前编号一定大于上一个盒子的编号(i>a[step-1])
    for i in range(a[step-1]+1,n+1):
        if not used[i]:
            a[step]=i
            used[i]=True
            dfs(step+1)
            used[i]=False
dfs(1)

2.8 运行结果

  3、题目三:排列棋子

3.1 问题描述

将M个白棋子与N个黑棋子排成一行,可以排成多种不同的图案。例如:2个白棋子和2个黑棋子,一共可以排成如下图所示的6种图案(根据组合数计算公式:)

请你编写一段程序,输出M个白棋子与N个黑棋子能够组成的所有图案。

为了避免程序输出结果过多导致严重超时,特别限制:1≤M,N≤6

3.2 输入格式

两个正整数M,N表示白棋子与黑棋子的数量,并且满足1≤M,N≤6 

3.3 输出格式

M个白棋子与N个黑棋子可以排列的所有图案。 
要求:每行输出一种图案,白棋子用0表示,黑棋子用1表示,按升序输出

3.4 样例输入

【测试样例1】
2 1
【测试样例2】
2 2
【测试样例3】
2 3

3.5 样例输出

【测试样例1】
001
010
100
【测试样例2】
0011
0101
0110
1001
1010
1100
【测试样例3】
00111
01011
01101
01110
10011
10101
10110
11001
11010
11100

3.6 解题思路

这道题我本来想着是创建一个列表,将m个0和n个1都存储起来,然后再根据上面的步骤,依次输出,但输出结果有许多重复值(001、001),程序里并没有因为两个相同的数而节省步骤,所以需要改变思想,删除重复的值,因为只有0和1两个值,所以只需要遍历这两种值即可,将used的值改为存放0和1的数量。使用了就减一,回退就加一,和上面的程序类似。

3.7 运行代码

m,n=map(int,input().split())#输入的数

a=[0 for _ in range(n+m+2)]#用来存放的列表
used=[m,n] #表示两种棋子的数量

def dfs(step):
    global a,used,m,n #全局变量
    if step==m+n+1: 
        for i in range(1,m+n+1):
            print(a[i],end="")
        print()
    #因为只有0和1两种棋子,所以只需要遍历棋子的种类
    for i in range(2):
        if used[i]>0:
            a[step]=i
            used[i]-=1 #使用过就减1
            dfs(step+1)
            used[i]+=1 #回溯加一
dfs(1)

3.8 运行结果

 4、题目四:有重复元素的排列问题

4.1 问题描述

设R={ r1, r2 , …, rn}是要进行排列的n个小写字母。其中小写字母r1, r2 , …, rn可能相同。试设计一个算法,列出R的所有不同排列。

       给定n 以及待排列的n 个小写字母。计算出这n 个小写字母的所有不同排列。

4.2 输入格式

输入数据的第1 行是小写字母个数n,1≤n≤500。接下来的1 行是待排列的n个元素。 

4.3 输出格式

计算出的n个小写字母的所有不同排列并按字典序输出。文件最后1行中的数是排列总数。

4.4 样例输入

4
aacc

4.5 样例输出

aacc
acac
acca
caac
caca
ccaa
6

4.6 解题思路

这道题和上一道题类似,只需要创建一个26个字母的列表,和上步一样的操作即可

4.7 运行代码

n=int(input()) #输入字符长度
m=input() #字符
a=[0 for _ in range(n+2)]#用来存放的列表
used=[0 for _ in range(27)] #表示26个字母的空列表
for i in m: #记录i中的字母个数,从1开始计算
    used[ord(i)-ord('a')+1]+=1 
count=0 #计数
def dfs(step):
    global a,used,n,count #全局变量
    if step==n+1: 
        for i in range(1,n+1):
            print(chr(96+a[i]),end="") #转为字母
        print() 
        count+=1
    for i in range(len(used)):
        if used[i]>0:
            a[step]=i
            used[i]-=1 #使用过就减1
            dfs(step+1)
            used[i]+=1 #回溯加一
dfs(1)
print(count)

4.8 运行结果

  5、题目五:N皇后问题

5.1 问题描述

 在N*N的棋盘上放置N个皇后(n<=10)而彼此不受攻击(即在棋盘的任一行,任一列和任一对角线上不能放置2个皇后),编程求解所有的摆放方法。
 

5.2 输入格式

输入:n

5.3 输出格式

每行输出一种方案,每种方案顺序输出皇后所在的列号,每个数占5列。若无方案,则输出no solute!

5.4 样例输入

4

5.5 样例输出

    2    4    1    3
    3    1    4    2

5.6 解题思路

本题主要考虑皇后的放置问题,当放下皇后,行列和对角线都不能再放置,所以需要增加行列和对角线的列表,因为对角线上,数都是相同的,并且对角线分为两种,一种是上对角线(行等于列),还有一种是下对角线(行-列+所数入的数-1),得到这些就好做了,套用回溯模版即可。

5.7 运行代码

#方法一
n=int(input())

#创建一个列表,表示当前位置 pace[0]=1表示标记第一行第二列的值
pace=[0 for _ in range(n)]

#创建列
flat=[0 for _ in range(n)]

#创建对角线,分上下对角线
d1=[0 for _ in range(2*n-1)]
d2=[0 for _ in range(2*n-1)]
count=0 #计数
#输出
def printf(pace):
    for i in range(n):
        for j in range(n):
            if pace[i]==j:
                print("%5d"%(j+1),end="")
    print()

def dfs(row): #行
    global count,d1,d2,flat,pace
    for col in range(n): #遍历列
        #满足条件就放数,
        if flat[col]==0 and d1[row+col]==0 and d2[row-col+n-1]==0:
            pace[row]=col
            #将使用过的点都标记为1
            flat[col]=1
            d1[row+col]=1
            d2[row-col+n-1]=1
            #如果不是最后一行,递归
            if row<n-1:
                dfs(row+1)
            #最后一行
            else:
                count+=1
                printf(pace)
            #回溯,将点清0
            flat[col]=0
            d1[row+col]=0
            d2[row-col+n-1]=0
dfs(0)
if count==0:#如果没有值,输出
    print('no solute!')
#方法二:不同书写
n=int(input())

#创建一个列表,表示当前位置 pace[0]=1表示标记第一行第二列的值
pace=[0 for _ in range(n)]

#创建列
flat=[0 for _ in range(n)]

#创建对角线,分上下对角线
d1=[0 for _ in range(2*n-1)]
d2=[0 for _ in range(2*n-1)]
count=0 #计数

def dfs(row): #行
    global count,d1,d2,flat,pace,n
    if row==n:
        count+=1
        for i in range(n):
            print("%5d"%(pace[i]+1),end="")
        print()
        return 
    for col in range(n): #遍历列
        #满足条件就放数,
        if flat[col]==0 and d1[row+col]==0 and d2[row-col+n-1]==0:
            pace[row]=col
            #将使用过的点都标记为1
            flat[col]=1
            d1[row+col]=1
            d2[row-col+n-1]=1
            #递归
            dfs(row+1)
            #回溯,将点清0
            flat[col]=0
            d1[row+col]=0
            d2[row-col+n-1]=0
            
dfs(0)
if count==0:#如果没有值,输出
    print('no solute!')

5.8 运行结果

  6、题目六:4连通迷宫

6.1 问题描述

给定一个M*M(2≤M≤9)的迷宫,迷宫用0表示通路,1表示围墙。 

迷宫的入口和出口分别位于左上角和右上角,入口和出口显然都是0。 

在迷宫中移动可以沿着上、下、左、右四个方向进行,前进格子中数字为0时表示可以通过,为1时表示围墙不可通过,需要另外再找找路径。 

请统计入口到出口的所有路径(不重复),并输出路径总数。若从入口无法到达出口,请输出0。 

6.2 输入格式

第一行输入1个正整数M(≤M≤9),表示迷宫是M行M列。 

第2行到第n+1行是一个M阶的0-1方阵。 

6.3 输出格式

统计入口到出口的所有路径(不重复),并输出路径总数。若从入口无法到达出口,请输出0。 

6.4 样例输入

3
0 0 0
1 0 1
0 0 1

6.5 样例输出

1

6.6 解题思路

走迷宫问题,首先就是将迷宫转换为二维数组的形式,因为有上下左右四个方向可以走,所以可以创建一个匿名函数,表示上下左右的移动,接着就是判断新走的点是否在迷宫中,并且为0,如果能走,就一直往下走,当前面没有路(1),则回退。最后要注意一点就是,要将起点标记为1,表示已经走过,不然会进行重复计算,就是因为这个错误就一直没有答对。

6.7 运行代码

n=int(input())
maze=[] #迷宫
#搭建迷宫
for i in range(n):
    li=list(map(int,input().split()))
    maze.append(li)
#print(maze)
#创建上下左右四个方向寻找
dirs=[
    lambda x,y:(x+1,y), #下
    lambda x,y:(x-1,y), #上
    lambda x,y:(x,y+1), #右
    lambda x,y:(x,y-1)  #左
    ]
x1,y1=0,0 #起点坐标
x2,y2=0,n-1 #终点坐标
count=0 #计数

def dfs(x1,y1,x2,y2): 
    global count
    #当起点和终点的值一样时,即找到一条路
    if x1==x2 and y1==y2:
        count+=1
    nextNode=[] #下一个点
    #寻找四个方向上的点
    for dir in dirs:
        nextNode=dir(x1,y1)
        #判断边界条件以及迷宫上的数
        if 0<=nextNode[0]<=n-1 and 0<=nextNode[1]<=n-1 \
            and maze[nextNode[0]][nextNode[1]]==0:
            maze[nextNode[0]][nextNode[1]]=1 #标记已经走过
            dfs(nextNode[0],nextNode[1],x2,y2) #下一点
            maze[nextNode[0]][nextNode[1]]=0 #回溯,清0
maze[0][0]=1 #将起点标记为已经走过,不然会进行重复计算,报错
dfs(x1,y1,x2,y2)
print(count)

6.8 运行结果

   6、题目六:4连通迷宫

6.1 问题描述

任何一个大于1的自然数n(n <= 10),总可以拆分成若干个小于n的自然数之和。

当n=7共14种拆分方法:

7=1+1+1+1+1+1+1

7=1+1+1+1+1+2

7=1+1+1+1+3

7=1+1+1+2+2

7=1+1+1+4

7=1+1+2+3

7=1+1+5

7=1+2+2+2

7=1+2+4

7=1+3+3

7=1+6

7=2+2+3

7=2+5

7=3+4

6.2 输入格式

输入自然数n 

6.3 输出格式

输出拆分的方案

6.4 样例输入

7

6.5 样例输出

1+1+1+1+1+1+1
1+1+1+1+1+2
1+1+1+1+3
1+1+1+2+2
1+1+1+4
1+1+2+3
1+1+5
1+2+2+2
1+2+4
1+3+3
1+6
2+2+3
2+5
3+4

6.6 解题思路

类似第三题组合题,当前数一定大于等于上一个数(不重复),并且拆数块一定大于2。

6.7 运行代码

n=int(input())
a=[0 for _ in range(n+2)]
def dfs(n,s):#需要分解的数,分解块数
    global a
    if n==0:
        if s<=2:
            return
        #取到倒数第二个数,因为后面有个+
        for i in range(1,s-1):
            print('%d+'%a[i],end="")
        #输出最后一位
        print(a[s-1])
        #print()
    for i in range(1,n+1):
        if i>=a[s-1]: #类似排序,所以只要找比上一个盒子大的数
            a[s]=i
            dfs(n-i,s+1)
dfs(n,1)

6.8 运行结果

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值