文章目录
分治的基本概念
在右边的数字三角形中寻找一条从顶部到底边的路径,使得路径上所经过的数字之和最大,路径上的每一步都只能往左下或右下走,只需求出最大的和,不需要给出具体路径。
对于n层的数字,一共有2n-1条不同的路径。
直接用循环来求出每条路径的和是非常困难的,这里我们用分治策略来解决这个问题。
最优化问题与最优解
数字三角形问题是个典型的最优化问题。
最优化问题指的是,问题可以有多个解,每个解有一个值,我们希望找到最优的那个值,称为一个最优解(最优解可能有多个)
数字三角形问题中,从上往下有很多不同的路径,这些不同的路径就是不同的解,其中路径上的数字和最大的解,就是我们要寻找的最优解。
分治=分解+解决+合并
分治策略的核心是:
- 将大的问题分解为形式相同的更小的子问题,子问题继续分解为更小的子问题… 一直分解下去,直到问题小到可以直接求解(自顶向下分解
- 直接解决最小的问题
- 最小问题求解后的结果依次进行合并,解决较大的问题,一直进行下去,直到解决原问题(自底向上合并)
(二分法也是一种分治,是一种尽量将问题分为规模相同的2个子问题的分治)
分解
以7为顶点(顶点为第0行第0列)的三角形问题,记为f(0,0)),可分解成2个较小的问题:
- 以3为顶点(顶点为第1行第0列)的问题,记为f(1,0)
- 以8为顶点(顶点为第1行第1列)的问题,记为f(1,1)
可知, f(0,0) = 7 + max(f(1,0), f(1,1) ),这是个分解动作,即:一个三角形问题=该问题顶点的数字+下面两个子问题的解的较大者
f(1,0)和f(1,1)如何求解?当然是继续分解
f(1,0) = 3+max( f(2,0), f(2,1) )
f(1,1) = 8+max( f(2,1), f(2,2) )
因此f(0,0) = 7+max( 3+max( f(2,0), f(2,1) ), 8+max( f(2,1), f(2,2) ) )
一直分解下去,直到f(4,x),x为列号;由于是最后一层,可知f(4,x)=A[4][x]
解决
层数大于等于2的问题都无法直接求出结果,需要继续分解,直到层数为1
考察原问题左下角的层数为2的问题的分解
f(3,0)=2+max(f(4,0), f(4,1))=2+max(A[4][0],A[4][1])= 2+5 = 7
合并
最小子问题求解之后,根据规则组合成上一层的子问题的解,一直进行下去,可以将原问题的解求出。
右上角的小数字表示以大数字为顶点的子问题的解,都是用大数字加上左下角的小数字和右下角小数字中的较大者,例如:
f(0,0)=7+max(f(1,0),f(1,1))=7+max(23,21)=30
用递归实现分治
什么是递归
有两种实现分治的方法:递归(自顶向下)、递推(自底向上)
什么是递归
函数调用自身的操作
def Factorial(n):
if n==1:return 1 #最小子问题,递归的出口
else: return n*Factorial(n-1) #递归调用
print(Factorial(10))
一定要为递归函数设计出口,否则会陷入死循环,最后会导致栈溢出
阶乘问题
用分治策略完成n的阶乘。
- 分解方法:n! = n * (n-1)!
- 统一表达式:f(i) 是i的阶乘
- 最小子问题:f(1) = 1
用递归函数表达分治策略:
f(n) = n*f(n-1)
f(1) = 1
分治是思想,递归是实现分治的手段之一
掌握正确的问题拆解方法: n! = n * (n-1)! --> f(n) = n*f(n-1)
掌握用函数统一表达子问题的方法:f(i) 是i的阶乘
为原问题定义最小子问题:f(1) = 1
用递归解决三角形问题
设计数据结构来存储三角形数据,最简单直观的方式,是使用嵌套的列表
A = [ [7], [3,8], [8,1,0], [2,7,4,4], [4,5,2,6,5] ]
对A中任一数字A[i][j],其左下角的数为A[i+1][j],右下角的数为A[i+1][j+1]
用正确的函数来统一描述原问题和子