本文内容为2022-2023学年第一学期运筹学课程动态规划部分的程序修改与讲解。
在课堂上展示的程序中,运行后可能会出现这样的结果:
memo = {}
def bestSum(targetSum, numbers):
shortest = None
## 终端条件
if targetSum == 0:
return []
if targetSum < 0:
return None
if memo.get(targetSum) is not None:
return memo.get(targetSum)
## 递归
## 状态转移
for number in numbers:
reminder = targetSum - number
reminder_r = bestSum(reminder,numbers)
if reminder_r is not None:
reminder_r.append(number)
if shortest is None or len(reminder_r) < len(shortest):
shortest = reminder_r.copy()
memo[targetSum] = shortest
return shortest
print(bestSum(100, [5, 4, 25]))
可以看出,结果出现了明显的错误,对于这一特定案例,可以通过将大的数放在前面的策略进行规避:
print(bestSum(100, [25, 5, 4]))
输出结果:
现在的结果看似正确,但是打印一下memo记录,发现还是存在问题的:
print(memo)
由于表格过长,这里就不一一列举,可以发现,除了最终target 100的memo结果正确之外,其他的记录都或多或少存在一些问题,因此原来的程序中一定存在一些没有发现的错误。
在讲解程序之前,先提一下关于python的对象与.copy()方法的有关知识:
# 首先定义一个list对象object1(这里任何python中的对象均可)
object1 = [1, 2, 3, 4]
# 使用id()方法查看对象的标识,可以理解为对象的地址
# id标识在对象的生命周期内是唯一且恒定的
print("id of object1", id(object1))
# 再定义一个对象object2,直接用object1为其赋值
object2 = object1
# 打印object2的标识,可以看到二者的标识是相同的,意味着object1
# 和object2这两个变量名指向的是用一个对象
print("id of object2", id(object2))
# 再使用.copy()方法对object3进行赋值
object3 = object1.copy()
# 打印object3的标识,可以看到object3与object1的标识并不相同,
# 这表示二者指向不同的对象
print("id of object3", id(object3))
上述程序的一组输出结果如下(大家实际运行时输出的id具体值可能会不同,但关系是相同的):
了解了这些前置知识后,就可以开始讲解程序中存在的问题了。
我们首先关注一下程序中使用的几个主要的对象(在本题中为列表list对象):
而在程序中,包含了以下几次对象赋值与修改(append)的操作:
可以发现,仅在给shortest对象赋值时,采用了.copy()方法,将reminder_r的值复制到了新的对象中,而其他几处都是直接采取了引用对象的赋值方法,因此在之后对reminder_r进行append操作时,实质上会出现直接对memo中记录的路径对象进行修改的情况:
因此,为了防止这样的操作对memo进行不正当修改,应在每一次赋值与返回值时,都采用.copy()方法。最终修改后的程序如下:
memo = {}
def bestSum(targetSum, numbers):
shortest = None
## 终端条件
if targetSum == 0:
return []
if targetSum < 0:
return None
if memo.get(targetSum) is not None:
return memo.get(targetSum).copy() # 值传递
## 递归
## 状态转移
for number in numbers:
reminder = targetSum - number
reminder_r = bestSum(reminder,numbers)
if reminder_r is not None:
reminder_r.append(number)
if shortest is None or len(reminder_r) < len(shortest):
shortest = reminder_r.copy() # 值传递
if shortest is not None: # 加上判断,消除memo中的无效记录None
memo[targetSum] = shortest.copy()
return shortest
print(bestSum(100, [25, 5, 4]))
输出结果:
memo记录(部分):