回溯法
回溯法(探索与回溯法)是一种选优搜索法,又称为试探法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法。
基本思想
在回溯法中,每次扩大当前部分解时,都面临一个可选的状态集合,新的部分解就通过在该集合中选择构造而成。这样的状态集合,其结构是一棵多叉树,每个树结点代表一个可能的部分解,它的儿子是在它的基础上生成的其他部分解。树根为初始状态,这样的状态集合称为状态空间树。
回溯法与穷举法有某些联系,它们都是基于试探的。穷举法要将一个解的各个部分全部生成后,才检查是否满足条件,若不满足,则直接放弃该完整解,然后再尝试另一个可能的完整解,它并没有沿着一个可能的完整解的各个部分逐步回退生成解的过程。而对于回溯法,一个解的各个部分是逐步生成的,当发现当前生成的某部分不满足约束条件时,就放弃该步所做的工作,退到上一步进行新的尝试,而不是放弃整个解重来。
回溯和递归唯一的联系就是,回溯法可以用递归思想实现。
例如问题:列举集合 {1,2,3} 中所有子集的问题
使用回溯法。从集合的开头元素开始,对每个元素都有两直到集合最后一个元素。 其中的每个操作都可以看作是一次尝试,每次尝试都可以得出一个结果。将得到的 结果综合起来,就是集合的所有子集。回溯法的求解过程实质上是先序遍历“状态树”的过程。树中每一个叶子结点,都有可能是问题的答案。如下图所示:
八皇后问题
八皇后问题,是一个古老而著名的问题,是回溯算法的典型案例。该问题是国际西洋棋棋手马克斯·贝瑟尔于1848年提出:在8×8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。
算法思路:
1. 从棋盘的第一行开始,从第一个位置开始,依次判断当前位置是否能够放置皇后, 判断的依据为:同该行之前的所有行中皇后的所在位置进行比较,如果在同一列, 或者在同一条斜线上(斜线有两条,为正方形的两个对角线),都不符合要求, 继续检验后序的位置。
2. 如果该行所有位置都不符合要求,则回溯到前一行,改变皇后的位置,继续试探。
3. 如果试探到最后一行,所有皇后摆放完毕,则直接打印出 8 * 8 的棋盘。最后一定要记得将棋盘恢复原样,避免影响下一次摆放。
下面我们用代码实现八皇后问题,首先,创建一个函数,用来判断下一行要放置的皇后是否与之前所放置的皇后冲突,代码如下:
def conflict(state, nextColumn):
nextRow = rows = len(state)
for row in range(rows):
column = state[row]
if abs(column - nextColumn) in (0, nextRow - row):
"""
如果差值等于0,两个皇后在同一列, 则代表冲突, 返回True;
如果列的差值等与行的差, 两个皇后在对角线上, 则代表冲突, 返回True;
"""
return True
return False
然后根据上面的思路用递归的方式写主函数,代码如下:
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,)):
yield (pos,) + result
最后再创建一个棋盘的绘制函数,皇后用x表示,代码如下:
def prettyprint(solution):
"""
友好展示: 为了直观表现棋盘,用X表示每个皇后的位置
"""
def line(pos, length=len(solution)):
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)
由于有92种结果,在这里只选部分截图,如下: