当前位置:   article > 正文

Python回溯算法练习_输出自然数1~n所有不重复的排列

输出自然数1~n所有不重复的排列

1、题目一:全排列问题

1.1 问题描述

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

1.2 输入格式

输入 n(1≤n≤9)

1.3 输出格式

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

1.4 样例输入

4

1.5 样例输出

  1. 1 2 3 4
  2. 1 2 4 3
  3. 1 3 2 4
  4. 1 3 4 2
  5. 1 4 2 3
  6. 1 4 3 2
  7. 2 1 3 4
  8. 2 1 4 3
  9. 2 3 1 4
  10. 2 3 4 1
  11. 2 4 1 3
  12. 2 1 4 3
  13. 3 1 2 4
  14. 3 1 4 2
  15. 3 2 1 4
  16. 3 2 4 1
  17. 3 4 1 2
  18. 3 4 2 1
  19. 4 1 2 3
  20. 4 1 3 2
  21. 4 2 1 3
  22. 4 2 3 1
  23. 4 3 1 2
  24. 4 3 2 1

1.6 解题思路

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

1.7 运行代码

  1. #方法一:内置函数
  2. from itertools import *
  3. n=int(input())
  4. li=[]
  5. for i in range(1,n+1):
  6. li.append(str(i))
  7. for i in permutations(li):
  8. a=' '.join(i)
  9. print(' ',a)
  10. #方法二:回溯
  11. n=int(input())#输入的数
  12. a=[0 for _ in range(n+2)]#用来存放的列表
  13. used=[False for _ in range(n+2)] #用与记录是否有被用过
  14. def dfs(step):
  15. global n,a,used #全局变量
  16. if step==n+1: #终止条件
  17. #输出结果
  18. for i in range(1,n+1):
  19. print("%5d"%a[i],end="")
  20. print()#换行
  21. for j in range(1,n+1):
  22. if not used[j]: #判断是否有使用
  23. a[step]=j #修改a列表的数
  24. used[j]=True #修改牌的状态为y已经使用
  25. dfs(step+1) #继续走到第step+1个盒子上
  26. used[j]=False #递归结束,返回上一个盒子,修改牌的状态为没有使用
  27. 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. 1 2 3
  2. 1 2 4
  3. 1 2 5
  4. 1 3 4
  5. 1 3 5
  6. 1 4 5
  7. 2 3 4
  8. 2 3 5
  9. 2 4 5
  10. 3 4 5

2.6 解题思路

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

2.7 运行代码

  1. n,m=map(int,input().split())
  2. a=[0 for _ in range(n+2)]
  3. used=[False for _ in range(n+2)]
  4. def dfs(step):
  5. global n,a,used
  6. if step==m+1:
  7. for i in range(1,m+1):
  8. print(a[i],end="")
  9. print()
  10. #枚举手中的n个牌,当前编号一定大于上一个盒子的编号(i>a[step-1])
  11. for i in range(a[step-1]+1,n+1):
  12. if not used[i]:
  13. a[step]=i
  14. used[i]=True
  15. dfs(step+1)
  16. used[i]=False
  17. 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. 【测试样例1
  2. 2 1
  3. 【测试样例2
  4. 2 2
  5. 【测试样例3
  6. 2 3

3.5 样例输出

  1. 【测试样例1
  2. 001
  3. 010
  4. 100
  5. 【测试样例2
  6. 0011
  7. 0101
  8. 0110
  9. 1001
  10. 1010
  11. 1100
  12. 【测试样例3
  13. 00111
  14. 01011
  15. 01101
  16. 01110
  17. 10011
  18. 10101
  19. 10110
  20. 11001
  21. 11010
  22. 11100

3.6 解题思路

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

3.7 运行代码

  1. m,n=map(int,input().split())#输入的数
  2. a=[0 for _ in range(n+m+2)]#用来存放的列表
  3. used=[m,n] #表示两种棋子的数量
  4. def dfs(step):
  5. global a,used,m,n #全局变量
  6. if step==m+n+1:
  7. for i in range(1,m+n+1):
  8. print(a[i],end="")
  9. print()
  10. #因为只有0和1两种棋子,所以只需要遍历棋子的种类
  11. for i in range(2):
  12. if used[i]>0:
  13. a[step]=i
  14. used[i]-=1 #使用过就减1
  15. dfs(step+1)
  16. used[i]+=1 #回溯加一
  17. 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 样例输入

  1. 4
  2. aacc

4.5 样例输出

  1. aacc
  2. acac
  3. acca
  4. caac
  5. caca
  6. ccaa
  7. 6

4.6 解题思路

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

4.7 运行代码

  1. n=int(input()) #输入字符长度
  2. m=input() #字符
  3. a=[0 for _ in range(n+2)]#用来存放的列表
  4. used=[0 for _ in range(27)] #表示26个字母的空列表
  5. for i in m: #记录i中的字母个数,从1开始计算
  6. used[ord(i)-ord('a')+1]+=1
  7. count=0 #计数
  8. def dfs(step):
  9. global a,used,n,count #全局变量
  10. if step==n+1:
  11. for i in range(1,n+1):
  12. print(chr(96+a[i]),end="") #转为字母
  13. print()
  14. count+=1
  15. for i in range(len(used)):
  16. if used[i]>0:
  17. a[step]=i
  18. used[i]-=1 #使用过就减1
  19. dfs(step+1)
  20. used[i]+=1 #回溯加一
  21. dfs(1)
  22. 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 样例输出

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

5.6 解题思路

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

5.7 运行代码

  1. #方法一
  2. n=int(input())
  3. #创建一个列表,表示当前位置 pace[0]=1表示标记第一行第二列的值
  4. pace=[0 for _ in range(n)]
  5. #创建列
  6. flat=[0 for _ in range(n)]
  7. #创建对角线,分上下对角线
  8. d1=[0 for _ in range(2*n-1)]
  9. d2=[0 for _ in range(2*n-1)]
  10. count=0 #计数
  11. #输出
  12. def printf(pace):
  13. for i in range(n):
  14. for j in range(n):
  15. if pace[i]==j:
  16. print("%5d"%(j+1),end="")
  17. print()
  18. def dfs(row): #行
  19. global count,d1,d2,flat,pace
  20. for col in range(n): #遍历列
  21. #满足条件就放数,
  22. if flat[col]==0 and d1[row+col]==0 and d2[row-col+n-1]==0:
  23. pace[row]=col
  24. #将使用过的点都标记为1
  25. flat[col]=1
  26. d1[row+col]=1
  27. d2[row-col+n-1]=1
  28. #如果不是最后一行,递归
  29. if row<n-1:
  30. dfs(row+1)
  31. #最后一行
  32. else:
  33. count+=1
  34. printf(pace)
  35. #回溯,将点清0
  36. flat[col]=0
  37. d1[row+col]=0
  38. d2[row-col+n-1]=0
  39. dfs(0)
  40. if count==0:#如果没有值,输出
  41. print('no solute!')
'
运行
  1. #方法二:不同书写
  2. n=int(input())
  3. #创建一个列表,表示当前位置 pace[0]=1表示标记第一行第二列的值
  4. pace=[0 for _ in range(n)]
  5. #创建列
  6. flat=[0 for _ in range(n)]
  7. #创建对角线,分上下对角线
  8. d1=[0 for _ in range(2*n-1)]
  9. d2=[0 for _ in range(2*n-1)]
  10. count=0 #计数
  11. def dfs(row): #行
  12. global count,d1,d2,flat,pace,n
  13. if row==n:
  14. count+=1
  15. for i in range(n):
  16. print("%5d"%(pace[i]+1),end="")
  17. print()
  18. return
  19. for col in range(n): #遍历列
  20. #满足条件就放数,
  21. if flat[col]==0 and d1[row+col]==0 and d2[row-col+n-1]==0:
  22. pace[row]=col
  23. #将使用过的点都标记为1
  24. flat[col]=1
  25. d1[row+col]=1
  26. d2[row-col+n-1]=1
  27. #递归
  28. dfs(row+1)
  29. #回溯,将点清0
  30. flat[col]=0
  31. d1[row+col]=0
  32. d2[row-col+n-1]=0
  33. dfs(0)
  34. if count==0:#如果没有值,输出
  35. 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 样例输入

  1. 3
  2. 0 0 0
  3. 1 0 1
  4. 0 0 1

6.5 样例输出

1

6.6 解题思路

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

6.7 运行代码

  1. n=int(input())
  2. maze=[] #迷宫
  3. #搭建迷宫
  4. for i in range(n):
  5. li=list(map(int,input().split()))
  6. maze.append(li)
  7. #print(maze)
  8. #创建上下左右四个方向寻找
  9. dirs=[
  10. lambda x,y:(x+1,y), #下
  11. lambda x,y:(x-1,y), #上
  12. lambda x,y:(x,y+1), #右
  13. lambda x,y:(x,y-1) #左
  14. ]
  15. x1,y1=0,0 #起点坐标
  16. x2,y2=0,n-1 #终点坐标
  17. count=0 #计数
  18. def dfs(x1,y1,x2,y2):
  19. global count
  20. #当起点和终点的值一样时,即找到一条路
  21. if x1==x2 and y1==y2:
  22. count+=1
  23. nextNode=[] #下一个点
  24. #寻找四个方向上的点
  25. for dir in dirs:
  26. nextNode=dir(x1,y1)
  27. #判断边界条件以及迷宫上的数
  28. if 0<=nextNode[0]<=n-1 and 0<=nextNode[1]<=n-1 \
  29. and maze[nextNode[0]][nextNode[1]]==0:
  30. maze[nextNode[0]][nextNode[1]]=1 #标记已经走过
  31. dfs(nextNode[0],nextNode[1],x2,y2) #下一点
  32. maze[nextNode[0]][nextNode[1]]=0 #回溯,清0
  33. maze[0][0]=1 #将起点标记为已经走过,不然会进行重复计算,报错
  34. dfs(x1,y1,x2,y2)
  35. 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
  2. 1+1+1+1+1+2
  3. 1+1+1+1+3
  4. 1+1+1+2+2
  5. 1+1+1+4
  6. 1+1+2+3
  7. 1+1+5
  8. 1+2+2+2
  9. 1+2+4
  10. 1+3+3
  11. 1+6
  12. 2+2+3
  13. 2+5
  14. 3+4

6.6 解题思路

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

6.7 运行代码

  1. n=int(input())
  2. a=[0 for _ in range(n+2)]
  3. def dfs(n,s):#需要分解的数,分解块数
  4. global a
  5. if n==0:
  6. if s<=2:
  7. return
  8. #取到倒数第二个数,因为后面有个+
  9. for i in range(1,s-1):
  10. print('%d+'%a[i],end="")
  11. #输出最后一位
  12. print(a[s-1])
  13. #print()
  14. for i in range(1,n+1):
  15. if i>=a[s-1]: #类似排序,所以只要找比上一个盒子大的数
  16. a[s]=i
  17. dfs(n-i,s+1)
  18. dfs(n,1)
'
运行

6.8 运行结果

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/秋刀鱼在做梦/article/detail/825517
推荐阅读
相关标签
  

闽ICP备14008679号