我寻思着,不能只会暴力求解和动态规划吧,所以看了一下回溯法。
本文以找零问题为例,首先使用python进行实现,然后想讲讲个人对于这个算法的想法。
1 代码
def backtracking_exchange(amount, denominations):
"""
>>> backtracking_exchange(56, [20, 10, 5, 1])
[2, 1, 1, 1]
>>> backtracking_exchange(12, [9, 6, 5, 1])
[0, 2, 0, 0]
"""
def is_equal_amount(part):
# part表示值大小
return True if sum(part) == amount else False
def options(max_coin, part):
# 获取可选集合
res = []
for coin in denominations:
if coin <= max_coin and sum(part+[coin]) <= amount: # 减少遍历的条件,1是选择小于等于前一个硬币的大小,2是选择后总大小小于等于目标值
res += [coin]
return res
def solutions(max_coin, part=[]):
if is_equal_amount(part):
return [part] # 如果等于了就返回part
else:
res = [] # 结果集合
for o in options(max_coin, part):
res += solutions(o, part + [o])
return res # 如果不符合,返回的是[], 不会影响总体,可以理解为回溯。
res = sorted(solutions(denominations[0]), key=lambda x: len(x))[0] # 获取结果
# 以下简单加工成目标结果所需结构
buff_dic = {coin: 0 for coin in denominations}
for coin in res:
buff_dic[coin] += 1 # 计数
return [buff_dic[coin] for coin in denominations]
if __name__=='__main__':
import doctest # 测试模块
doctest.testmod(verbose=True)
2 个人想法
因为看到了八皇后问题(8-Queen Puzzle)而学习了回溯法,看了一些同学的讲解,说这个算法有dfs的思想,可以用迭代求解。大多是宏观上的讲解,讲真,看完一圈我没看懂具体应用是个啥玩意,不知道怎么从特殊到一般。所以想在这讲讲自己的看法,也算是对大家的补充。
首先,从解题核心思想上看,回溯法解法使用了dfs的思想,并且加入了剪枝技术(一开始以为好高大上,后来才明白就是提前结束的深度检索的过程)。通过迭代的方法可以很好的进行dfs遍历,一条路走到黑。走不下去的路,以例题为例,就是当前获取的金额大于目标金额amount,我就不走了,减少了遍历的路径。然后通过return []达到回溯的目的。
其次,从分解问题的角度上看,回溯法解题,总共可以分两不分。第一部分是迭代部分,包括选取迭代终止标志:is_equal_amount(part),以及迭代的最小子集:res += solutions(o, part + [o])。第二部分,下一步的所有选项集合,即候选集,通过函数solutions(max_coin, part=[])实现,这一步就好比八皇后问题中放了一个皇后之后,获取下一步有哪些可选步骤。
本题可以通过两种方式获取硬币情况,第一个就是上面写的方法,直接将面值记录,最后再计算个数,第二种我没有写,可以直接使用等长列表[0,0,0,0]来表示其各个硬币的选取情况。
回溯的精髓只有动手做了才清楚,也是清楚的那一瞬间让我很舒爽。奥利给,冲。