上课时给学生讲到递归实现的缺陷时,举过下面的例子:
使用递归方法来计算组合数:
从m个不同元素中,任取n(n≤m)个元素并成一组,叫做从m个不同元素中取出n个元素的一个组合;从m个不同元素中取出n(n≤m)个元素的所有组合的个数,叫做从m个不同元素中取出n个元素的组合数。
公 式: C(m,n)=n!/((m-n)!*n!)(n≤m)
性 质:C(m,n)= C(m,m-n) --可以推导出--》 C(m,0) = C(m,m) = 1
C(m,n)=C(m-1,n-1)+C(m-1,n)
请使用递归的方法来计算组合数。
使用递归的方法很简单,使用我们之前讲过的递归的原则:
// Divide and Conquer
int CombinationNumber(int m, int n)
{
// Part 1
if ( (n == 0) || (m == n) )
return 1;
// Part 2
return CombinationNumber(m-1, n) + CombinationNumber(m-1, n-1);
}
如上图,当我们来计算C(6,4)时,我们需要计算C(5,3)和C(5,4),而计算C(5,3)又要计算C(4,2)和C(4,3),依次 类推。
我们发现这里有大量的重复预算。这也就是为什么递归在时间和空间(递归要开辟栈空间)上有大量的资源消耗。
那么问题来了,我们需要怎么来优化?
根据上一章所讲的内容,我们可以尝试用循环来优化递归。这里我们使用另一种方法,DP(Dynamical Programming),也就是动态规划的方法来优化。
我们考虑:这里有很多中间结果被反复计算,从而引起了时间和空间上(递归需要分配栈空间)的浪费,
所以我们可以考虑将计算的中间结果记录在某处,然后当再次需要这个结果时,从已经计算的结果中来查找,从而得到结果,
这样在时间上可以节省大量的成本,当然,空间上则需要一定辅助空间。
按照这个思路,我们尝试用一张大的表来有序的保存中间结果。
如下图,我们让行对应m,让列对应n,这样我们根据C(m,0) = C(m,m) = 1,可以将下标中
的黑色区域填上初始值1;
然后根据性质:C(i,j)=C(i-1,j-1)+C(i-1,j)
我们找到了计算出C(i,j)的方法,所以在计算C(5,2)
时可以使用C(4,1)+C(4,2)=4+6=10
依次类推:
我们可以计算出表中的C(m,n);
于是,有了下面的算法2(Dynamical Programming)实现:
import math
import time
def combinationNum2(m,n):
#arr = [[0]*m]*n # row m, coloumn n
t1 = time.time()
myList = [([0] * (n+1)) for i in range(m+1)]
#print(myList)
for i in range(0, m-n+1):
myList[i][0] = 1
for i in range(0,n+1):
myList[i][i] = 1
#print(myList)
for j in range(1, n+1):
for i in range(j+1, m-n+j+1):
myList[i][j] = myList[i-1][j-1]+myList[i-1][j]
#print(myList)
print("combinationNum2 costed %f seconds"%(time.time()-t1))
return myList[m][n]
#return combinationNum(m-1,n-1)+ combinationNum(m-1,n)
这里我们就是一个使用DP的思想来优化递归的案例。
# 当然,我们可以从数学公式的角度来解决这个问题,代码如下:
def combinationNum3(m,n):
t1 = time.time()
result = math.factorial(m)/(math.factorial(m-n)*math.factorial(n))
print("combinationNum3 costed %f seconds"%(time.time()-t1))
return result
# 测试代码:
m = int(raw_input("Please input m:"))
n = int(raw_input("Please input n:"))
print(combinationNum2(m, n))
print(combinationNum3(m, n))
对Python算法,爬虫和数据分析感兴趣的朋友可以加入QQ群:748905525,一起学习讨论