动态规划-斐波那契数列求算

1. 基本解法

几乎是小白闭着眼都能写出的代码:
为了不产生指数级别的时间复杂度,只需要将中间过程中计算的斐波那契数列数值都记录下就可,改进代码如下:

/**
 * 斐波那契数列的实现类
 * @author Gastby
 *
 */
public class FormatTest {

	/**
	 * 哈希表用来存放中间生成的的斐波那契数值
	 */
	static HashMap<Integer, Long> map = new HashMap<Integer, Long>();
	public static void main(String[] args) {
		PrintStream pw = System.out;
		int n = 50;
		pw.println(fb1(n)); //直接打印第50个斐波那契数列
		pw.println(fb2(n));
		pw.println(fb3(n));
	}

	/**
	 * 递归调用,求算中间第n个斐波那契数值
	 * @param n
	 * @return
	 */
	private static Long fb1(int n) {
		if(n == 1) {
			if(map.containsKey(n)) //先判断是否已经存入了哈希表中
				return map.get(n);
			else
				map.put(n, (long)1);
			return map.get(n);
		} else if(n == 2) {
			if(map.containsKey(n))
				return map.get(n);
			else
				map.put(n, (long)1);
			return map.get(n);
		} else {
			long v1, v2;
			if(map.containsKey(n-1))
				v1 = map.get(n-1);
			else {
				v1 = fb(n-1);
				map.put(n-1, v1);
			}
			if(map.containsKey(n-2))
				v2 = map.get(n-2);
			else {
				v2 = fb(n-2);
				map.put(n-2, v2);
			}
			return v1 + v2;
		}
	}

}

当然这样的做法太复杂了,其实根本用不着哈希表,只需要数组就可以解决问题。


2. 数组自下向上求解第n个数值;

通过将结果备忘在一个n维数组中,然后依次取出前两位求算得出后一位的结果并保存,时间复杂度O(n),空间复杂度是O(n);实现代码如下:

/**
	 * 数组求解第n个斐波那契额数列数值
	 * @param n 输入的第n个数
	 * @return 返回该数值
	 */
	private static int fb2(int n) {
    	if(n == 1)
    		return 1;
    	else if(n == 2)
    		return 1;
    	else {
    		int[] t = new int[n];
    		t[0] = 1;
    		t[1] = 1;
    		for(int i=2; i<n; i++) {
    			t[i] = t[i-1] + t[i-2];
//    			System.out.println(i + "--" + t[i]);
    		}
    		return t[n-1];
    	}
	}
  • 但其实在只需要求解第n个斐波那契额数值的时候,你中间缺保留计算了n-1个不需要存储数,一定程度上造成了空间的浪费,所以如果真的从节省空间的角度考虑的化,代码可以从以下角度进一步优化,只用两个变量和一个临时变量的空间大小就可计算得出最终第n个所需数值的代码如下,同样自下向上,代码实现如下:
	private static int fb3(int n) {
		int a1=1, a2=1;
		if(n == 1) return a1;
		if(n == 1) return a2;
		for(int k=2; k<n; k++) {
			if(k % 2 == 1) a1 = a1 + a2;
			if(k % 2 == 0) a2 = a1 + a2;
		}
		if(n % 2 == 0) return a1;
		return a2;
	}

3. 利用矩阵求算的解法;

斐波那契数列递推公式如下:
在这里插入图片描述
不妨通过以下矩阵形式观看递推公式:
在这里插入图片描述
而我们则只需要求出A矩阵的n-1次幂结果,就可以最后快速得出F(n)的结果了,而对于n次幂的简化计算,则可以通过判断奇偶的形式以对数级别的运算量得出,这里后续补上代码如下;

/*
为了方便计算,形如下式:矩阵保存数值如下aij代表第i行第j列数值;
*/
struct matrix {
	int a00, a01, a10, a11;
};
void multiplyMatrix(matrix &m, int n) {
	int u, v, s, t;
	for(int i=0; i<n; i++) {
		u=m.a00+m.a01;
		v=m.a00;
		s=m.a10+m.a11;
		t=m.a10;
		m.a00=u;
		m.a01=v;
		m.a10=s;
		m.a11=t;
	}
}
int main() {
	struct matrix m = {1,1,1,0};
	multiplyMatrix(m, 5);
	printf("%d ", m.a00);
	printf("%d\n", m.a01);
	printf("%d ", m.a10);
	printf("%d ", m.a11);
	return 0;
}
  • 实际上,我们还可以以这样的视角进行计算,由于
    在这里插入图片描述
    通过假设下式成立:
    在这里插入图片描述
    而通过数学归纳法可最终得证上式成立(证明过程省略);
    然后也同上,直接通过计算A矩阵的n次幂结果最终得出F(n)项的数值;实现在代码上,如在c语言里可以用一个结构体存储矩阵信息,java或c++则可以使用自定义的class来完成矩阵的定义和幂求算,具体代码实现如下:
void multiplyMatrix(matrix &m, int n) {
	if(n<2) return;
	multiplyMatrix(m, n/2);

	int u, v, s, t;
	u=m.a00*m.a00+m.a01*m.a10;
	v=m.a00*m.a01+m.a01*m.a11;
	s=m.a10*m.a00+m.a11*m.a10;
	t=m.a10*m.a01+m.a11*m.a11;
	m.a00=u;
	m.a01=v;
	m.a10=s;
	m.a11=t;

	if(n&1) {
		u=m.a00+m.a01;
		v=m.a00;
		s=m.a10+m.a11;
		t=m.a10;
		m.a00=u;
		m.a01=v;
		m.a10=s;
		m.a11=t;
	}
}

但实际上,我们在上述计算过程中,多算了不少数值,比如我们只需要求算F(n),却把F(n+1)和F(n-1)都算了出来了,这无疑增加了计算量,《算法时空》里给出了另一个非常惊艳的计算方法,一定程度上更加优化了计算量;

目前见过时间复杂度最低的解法

和上述解法中与其计算
在这里插入图片描述
不如计算
在这里插入图片描述
那么如何计算呢?不妨借用前面已经得证的结论:
在这里插入图片描述
则当 n = 2m 时,下式成立:
在这里插入图片描述

  • 从而当 n=2m时
    在这里插入图片描述
    可通过计算
    在这里插入图片描述
    得出最后结果。

  • 当 n=2m+1时
    在这里插入图片描述
    同理可通过计算
    在这里插入图片描述
    得出最后结果。
    具体实现代码后续补上如下:

/*
为了方便计算,a1为上面的值,a2为下面的值;
*/
struct matrix {
	int a1,a2;
};
void multiplyMatrix(matrix &m, int n) {
	if(n<1) return;
	int u, v;
	multiplyMatrix(m, n/2);
	if(n&1) {
		u=m.a1*m.a1+2*m.a1*m.a2;
		v=m.a1*m.a1+m.a2*m.a2;
	} else {
		u=m.a1*m.a1+m.a2*m.a2;
		v=2*m.a1*m.a2-m.a2*m.a2;
	}
	m.a1=u;
	m.a2=v;
}
int main() {
	struct matrix m = {1,0};
	multiplyMatrix(m, 4);
	printf("%d ", m.a1);
	printf("%d\n", m.a2);

	return 0;
}

由此可见,常见数学知识的运用对于解决上述问题会多么的方便!
学好数学,很重要!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值