动态规划通过保留一个预先算出的值的表而对已经解过的子问题不再进行递归调用,从而将递归算法写成非递归算法。动态规划通常应用于最优化问题。
斐波那契数
public class Fibonacci {
//Fibonacci数的递归解法,时间复杂度随n呈指数增长
public static int fib(int n){
//前两个数都是1
if(n <= 1)
return 1;
else
//为了计算fib(n),需要调用fib(n-1)和fib(n-2)
//而计算fib(n-1)时,又把fib(n-2)就算一遍
//这样的重复性计算是指数级的
return fib(n-1) + fib(n-2);
}
//Fibonacci数的非递归解法,时间复杂度为O(n)
public static int fibonacci(int n){
if(n <= 1)
return 1;
//为了得到当前值,只需要记录最近算出的两个值;
//动态规划增加的“表”只用来记录两个值;
//初始前两个值都为1;
int nMinusOne = 1;
int nMinusTwo = 1;
int answer = 1;
for(int i = 2; i <= n; i++){
//计算当前值
answer = nMinusOne + nMinusTwo;
//表里两个数据往后移
nMinusTwo = nMinusOne;
nMinusOne = answer;
}
return answer;
}
//测试
public static void main(String[] args) {
//50就够递归算法喝一壶了
int n = 30;
//计算运行时间
long time = System.currentTimeMillis();
//递归算法
System.out.println(fib(n));
//非递归算法
//System.out.println(fibonacci(n));
System.out.println(System.currentTimeMillis() - time);
}
}
矩阵乘法的顺序安排
多个矩阵相乘,相乘的结合方式对需要的纯量乘法次数是有很大影响的。而这个问题是贪心算法没法解决的。
将两个阶数为p*q和q*r的矩阵相乘,需要pqr次纯量乘法。
我们首先把这个问题变成一个递推问题。
定义T(N)为相乘顺序的个数,有
设矩阵为A1,A2,A3, ... , AN,且最后进行的乘法是(A1A2...Ai)(Ai+1Ai+2...AN).此时,有T(i)种方法计算(A1A2...Ai)且有T(N-i)种方法计算(Ai+1Ai+2...AN)。因此,对于每个可能的i,存在T(i)T(N-i)种方法计算(A1A2...Ai)(Ai+1Ai+2...AN)。
对于1<= i <= N,另c(i)是矩阵Ai的列数,可知Ai有c(i-1)行。同时定义c(0)是A1的行数。
设m(left,right)是进行矩阵乘法A(left)A(left+1)...A(Right-1)A(Right)所需要的乘法次数。定义m(left,left)=0。设最后的乘法是(A(left)...A(i))(A(i+1)...A(Right)),其中Left <= i <= Right。此时所需的乘法次数为
m(Left,i) + m(i+1,Right) + c(Left-1)c(i)c(Right)
这三项分别代表A(left)...A(i),A(i+1)...A(Right),以及它们的乘积所需的乘法。
如果定义M(Left,Right)为最优排列顺序下所需要的乘法次数,若left < Right,则
这个公式可以直接转换成递归程序。
但是,这样的程序是低效的!
由于只有M(Left,Right)的(N^2)/2个值需要计算,可以用一个表来存放这些值。
进一步,如果Right - Left = k,那么只有在M(Left,Right)的计算中所需要的那些值M(x,y)满足y-x < k。
如果除了最后答案M(1, N)外还需要显示实际乘法顺序,则无论何时改变M(Left,Right),都记录i的值。
//计算矩阵相乘的最优顺序
//c存放n个矩阵的列数,其中c[0]是第一个矩阵的行数,因此,c有n+1个元素
//最小相乘次数存在没m[1][n]中
//lastChange记录顺序
//m和lastChange下标从1开始
public static void optMatrix(int[] c, long[][] m, int[][]lastChange){
//实际矩阵个数
int n = c.length - 1;
for(int left = 1; left <= n; left++)
m[left][left] = 0;
for(int k = 1; k < n; k++)
for(int left = 1; left <= n - k; left++){
int right = left + k;
m[left][right] = (long) Double.POSITIVE_INFINITY;
for(int i = left; i < right; i++){
long thisCost = m[left][i] + m[i+1][right] + c[left-1]*c[i]*c[right];
if(thisCost < m[left][right]){
//记录最优值
m[left][right] = i;
//记录得到最优值时的i
lastChange[left][right] = i;
}
}
}
}