做你想做的,错的算我的
这里是Want595,欢迎光临我的世界 ~
目录
前言
一些关于回溯法的笔记。
一、N皇后
问题描述
在N*N的方格棋盘放置了N个皇后,使得它们不相互攻击(即任意2个皇后不允许处在同一排,同一列,也不允许处在与棋盘边框成45角的斜线上)。 你的任务是,对于给定的N(N是正整数且6<=N<15),求出有多少种合法的放置方法。并输出前4个解。比如当N=6时,检查一个如下的6 x 6的棋盘,有六个皇后被放置在棋盘上,使得每行、每列有且只有一个,每条对角线(包括两条主对角线的所有平行线)上至多有一个皇后。
程序设计
#N皇后
def NQueens(n):
def checkBoard(rowIndex):
for i in range(rowIndex):
if cols[i]==cols[rowIndex]:
return False
if abs(cols[i]-cols[rowIndex])==rowIndex-i:
return False
return True
def helper(rowIndex):
if rowIndex==n:
board=[[0 for i in range(n)] for j in range(n)]
for i in range(n):
board[i][cols[i]]=1
res.append(board)
return
for i in range(n):
cols[rowIndex]=i
if checkBoard(rowIndex):
helper(rowIndex+1)
cols=[0 for i in range(n)]
res=[]
helper(0)
return res
lst=NQueens(4)
for i in lst:
for j in i:
print(j)
print()
具体分析
在NQueens()函数中定义两个子函数,checkBoard()用于判断当前放置的皇后位置是否合法,helper()是我们的递归实现的主要函数。
checkBoard()函数以及helper()函数的参数都是当前行,例如当rowIndex=0时代表当前处在第一行,我们需要在第一行选择一个位置放置一个皇后,源码中的cols存放了皇后的列坐标,cols[0]的值为第一行皇后的列坐标,cols[1]为第二行皇后的列坐标,依次类推,例如cols=[0,1,2,3]就表示当前皇后的坐标为第一行第一列,第二行第二列,第三行第三列以及第四行第四列。所以代码中的cols[rowIndex]=i表示当前第rowIndex行的列坐标为i,然后判断该位置是否合法,如果合法就让行数加一继续判断,如果不合法就让列数加一继续判断,当当前行数等于总行数时就可以结束判断,将当前的满足条件的列表放入结果列表res中,最后输出结果列表即可。
二、数独
问题描述
用回溯法填满一张数独表
解题思路
① 从第一个空格开始。依次尝试1到9的数字,如果数字与盘面冲突就换成下一个数字,如果不冲突就去往第二个空格
② 在第二个空格,同样依次尝试1到9的数字,如果与盘面冲突就换成下一个数字,如果不冲突就去往第三个空格
③ 如果当前空格1到9都填不了,就返回到上一个空格,再依次尝试没有试过的数字,如果与盘面冲突就换成下一个数字
④ 当最后一个空格被填上数字时,就可以将答案加入结果列表
程序设计
#数独
def sudoku(board):
def checkBoard(row,col,num):
for i in range(9):
if i!=col and board[row][i]==num:
return False
if i!=row and board[i][col]==num:
return False
for i in range(row-row%3,3+row-row%3):
for j in range(col-col%3,3+col-col%3):
if i!=row and j!=col and board[i][j]==num:
return False
return True
def helper(index):
if index==81:
solution=copy.deepcopy(board)
res.append(solution)
return
i=index//9
j=index%9
if board[i][j]==0:
for num in range(1,10):
board[i][j]=num
if checkBoard(i,j,num):
helper(index+1)
board[i][j]=0
else:
helper(index+1)
res=[]
helper(0)
return res
board=[[5,3,0,0,7,0,0,0,0],
[6,0,0,1,9,5,0,0,0],
[0,9,8,0,0,0,0,6,0],
[8,0,0,0,6,0,0,0,3],
[4,0,0,8,0,3,0,0,1],
[7,0,0,0,2,0,0,0,6],
[0,6,0,0,0,0,2,8,0],
[0,0,0,4,1,9,0,0,5],
[0,0,0,0,8,0,0,7,9]]
lst=sudoku(board)
for i in lst:
for j in i:
print(j)
print
具体分析
与N皇后问题相似,数独问题主要是对每个空格进行判断,碰壁即回溯。
这个程序是一个基于回溯算法的数独求解器。首先定义了一个checkBoard函数,用来检查当前格子中填入的数字是否合法。然后定义helper函数,用来递归搜索数独的解。在每个格子中填入数字,然后判断该数字是否合法,如果合法则递归搜索下一个格子,如果不合法则回溯到上一个格子重新填数字。最后将所有解存放在一个列表中返回。
该程序的时间复杂度为O(9^m),其中m为数独中空格的数量,因为在每个空格中都要尝试填入1到9的数字,所以时间复杂度为9的m次方。但是由于数独的整体复杂度相对较低,所以该程序的实际运行时间非常快。
三、排列组合
排列
输出列表的全排列
#排列
def permute(nums,solution=[]):
if not nums:
res.append(solution)
return
for i in range(len(nums)):
newSolution=solution+[nums[i]]
newList=nums[0:i]+nums[i+1:]
permute(newList,newSolution)
res=[]
n=int(input())
lst=[i+1 for i in range(n)]
permute(lst)
for i in res:
for j in i:
print(j,end=' ')
print()
定义一个permute()函数,每次传入两个参数,一个是当前列表,另一个是当前排列后列表,例如lst=[1,2,3,4]时,我们调用该函数,newSolution=[1]作为当前排列的列表,newList1=[2,3,4]作为当前列表,然后开始递归,newSolution变为了[1,2],newList变为[3,4],继续直到newSolution=[1,2,3,4]时此时nums=[],我们将solution也就是上一层的newSolution加入结果列表,然后返回上一层,并且此时newSolution=[1,2,4],newList=[3],再一直递归……
组合
输出列表的所有组合
#组合
def combination(nums,solution,n):
if n==0:
res.append(solution)
return
for i in range(len(nums)):
newList=nums[i+1:]
newSolution=solution+[nums[i]]
combination(newList,newSolution,n-1)
res=[]
n=int(input())
lst=[i+1 for i in range(n)]
combination(lst,[],2)
for i in res:
for j in i:
print(j,end=' ')
print()
与列表的排列类似,求组合的递归也比较好理解,唯一不同的是newList保存的当前剩余列表变化了,因为组合不能重复,所以我们只需要保存i之后的元素即可,其他基本不变,多了一个参数n用来取nums中的n个数进行组合。
四、子集和
问题一
给定一个无重复元素的数组nums和一个目标数sum,输出所有使数字和为目标数的组合
#问题一
def combinationSum(nums,sums):
def helper(curSum,solution,index):
if curSum>sums:
return
if curSum==sums:
res.append(solution)
return
for i in range(index,n):
newSum=curSum+nums[i]
newSolution=solution+[nums[i]]
helper(newSum,newSolution,i)
n=len(nums)
nums.sort()
res=[]
helper(0,[],0)
return res
nums=list(map(int,input().split(' ')))
sums=int(input())
lst=combinationSum(nums,sums)
for i in lst:
for j in i:
print(j,end=' ')
print()
这段程序实现了“组合总和”问题,即给定一个无重复元素的数组和一个目标数,找出所有可以使数字和等于目标数的组合。
程序的实现思路是采用回溯算法,首先将数组进行排序,然后从第一个元素开始递归地将当前元素加入组合中,并向下遍历数组,直到找到子集和等于目标数或者当前子集和已经大于目标数。如果子集和等于目标数,则将当前组合加入结果列表中,如果子集和大于目标数,则回溯到上一个元素并继续遍历。
程序的输入分为两部分,首先输入一个整数数组并用空格隔开,然后输入一个整数表示目标数。最后输出所有满足条件的组合。
需要注意的是,程序中的helper函数中的solution参数是一个列表,最终结果也是一个列表的列表。每个子列表表示一个符合要求的组合。
问题二
给定一个集合nums和一个目标数sum,输出所有使子集和为目标数的组合
#问题二
def combinationSum(nums,sums):
def helper(curSum,solution,index):
if curSum>sums:
return
if curSum==sums:
res.append(solution)
return
for i in range(index,len(nums)):
newSum=curSum+nums[i]
newSolution=solution+[nums[i]]
helper(newSum,newSolution,i+1)
nums.sort()
res=[]
helper(0,[],0)
return res
nums=list(map(int,input().split(' ')))
sums=int(input())
lst=combinationSum(nums,sums)
for i in lst:
for j in i:
print(j,end=' ')
print()
问题一和问题二的唯一区别就是是否可取重复元素,只要稍微改变一下递归条件即可。
总结
本章主要通过回溯法解决一些实际问题。