1.回溯法
回溯法,又被称为“试探法”。解决问题时,每进行一步,都是抱着试试
或者这么走下去肯定达不到目标,立刻做回退操作重新选择。这种走不通
就回退再走的方法就是回溯法。
问题: 列举集合 {1,2,3} 中所有子集的问题中
使用回溯法。从集合的开头元素开始,对每个元素都有两直到集合最后一个元素。
其中的每个操作都可以看作是一次尝试,每次尝试都可以得出一个结果。将得到的
结果综合起来,就是集合的所有子集。
回溯法从问题本身出发,寻找可能实现的所有情况。和穷举法的思想相近,
不同在于穷举而回溯法在列举过程如果发现当前情况根本不可能存在,就
停止后续的所有工作,返回上一步进行新的尝试。
递归是从问题的结果出发,例如求 n!,要想知道 n!的结果,就需要知道
n*(n-1)! 的结果,而要想知道 (n-1)! 结果,就需要提前知道 (n-1)*(n-2)!。
这样不断地向自己提问,不断地调用自己的思想就是递归。
回溯和递归唯一的联系就是,回溯法可以用递归思想实现。
回溯法的求解过程实质上是先序遍历“状态树”的过程。树中每一个叶子结点,都有可能是问题的答案。
2.八皇后问题
八皇后问题是以国际象棋为背景的问题:有八个皇后(可以当成八个棋子),如何在 8*8 的棋盘中放置八个皇后,使得任意两个皇后都不在同一条横线、纵线或者斜线上。
算法思路:
- 从棋盘的第一行开始,从第一个位置开始,依次判断当前位置是否能够放置皇后,
判断的依据为:同该行之前的所有行中皇后的所在位置进行比较,如果在同一列,
或者在同一条斜线上(斜线有两条,为正方形的两个对角线),都不符合要求,
继续检验后序的位置。 - 如果该行所有位置都不符合要求,则回溯到前一行,改变皇后的位置,继续试探。
- 如果试探到最后一行,所有皇后摆放完毕,则直接打印出 8*8 的棋盘。最后一定
要记得将棋盘恢复原样,避免影响下一次摆放。
实现代码:
def conflict(state, nextColumn):
"""
state: (7, 4, 6, 0, 2) 标记已经排好的每个皇后的位置,(0,7), (1, 4), (2,6),(3,0),(4,2)
nextColumn: 2 下一行八皇后准备要放置的列索引位置
利用回溯法判断是否可以在第6行的第3列放置皇后, 如果可以,返回True, 如果不可以,返回False;
"""
nextRow = rows = len(state) # 5
for row in range(rows): # row = 0 1 2 3 4
column = state[row]
if abs(column - nextColumn) in (0, nextRow - row):
"""
i=0:
state[0]=7, nextColumn=2
如果差值等于0,两个皇后在同一列, 则代表冲突, 返回True;
如果列的差值等与行的差, 两个皇后在对角线上, 则代表冲突, 返回True;
"""
return True
return False
# nums = 8 pos=()
def queens(num, state=()):
"""
采用生成器的方式来产生每一个皇后的位置,并用递归来实现下一个皇后的位置。
num: 皇后的数量
state: 标记已经排好的每个皇后的位置
"""
for pos in range(num): # 八皇后的数量N=0, 1, 2, 3, 4, 5, 6 , 7 你要在哪一列放置皇后
# 如果不冲突,则递归构造棋盘。
if not conflict(state, pos): # 回溯法的体现
# 如果棋盘状态state已经等于num-1,即到达倒数第二行,而这时最后一行皇后又没冲突,直接yield,打出其位置(pos, )
if len(state) == num - 1: # state=()
yield (pos,)
else: # (0, )
for result in queens(num, state + (pos,)):
"""
pos = 0
(0, ) (0, 2), (0, 2, 4), ()
1). pos=0,第一行放在第一列,这时不会冲突,但是不会进入if,因为还没到达倒数第二行, (0)
2). 进入else后,再调用queens(num, state+(pos,),这时进入第二行,再次递归展开则是queens(num,state+(pos, )+(pos, ) )
2). 进入else后,再调用queens(num, state+(pos,),这时进入第三行,再次递归展开则是queens(num,state+(pos, )+(pos, ) +(pos, ) )
3). 到达最后一行时返回(pos, ),再返回倒数第二行,再返回倒数第三行,最后到达最开始那层(pos, )+result, pos为第一行皇后所在列,
"""
yield (pos,) + result
def prettyprint(solution): # (6, 1, 5, 2, 0, 3, 7, 4)
"""
友好展示: 为了直观表现棋盘,用X表示每个皇后的位置
:param solution:
:return:
"""
def line(pos, length=len(solution)): # pos为6,即绘制在第7列 . . . . . . X .
return '. ' * (pos) + 'X ' + '. ' * (length - pos - 1)
for pos in solution:
print(line(pos))
if __name__ == '__main__':
solutions = queens(8)
for index, solution in enumerate(solutions):
print("第%d种解决方案:" % (index + 1), solution)
prettyprint(solution)
print('*' * 100)