数据结构(邓俊辉):递归和迭代、分而治之和减而治之(2.例子)

前文介绍了递归和迭代、减而治之和分而治之的概念。
数据结构(邓俊辉):递归和迭代、分而治之和减而治之(1.概念)
接下来举一些例子来解释:
数组求和为例:
采用减而治之(线性递归)的办法:

int sum(int A[n], int n){
	if(n > 1)
		return sum(A, n-1) + A[n-1];
	else //特殊情况 n<1 ,也就是数组为空的情况,这也是递归停止的条件
		return 0;
}

由上述代码可以看出,该问题采用的是减而治之(规模为n-1的子问题sum(A, n-1)和规模为1 的子问题A[n-1])的思路,同时必须考虑“递归基”用来停止递归。
很容易可以分析出来它的时间复杂度为:O(n),空间复杂度为:O(n)

下面采用分而治之(递归)的办法:

int sum(int A[], int lo, int hi){
	if(lo > hi)
		return false;
	else if(lo == hi)
		return A[lo]; //return A[hi]
	else{
		mid = (lo + hi)/2;//mid = (lo + hi) >> 1;
		return sum(A, lo, mid) + sum(A, mid+1, hi);
	}
	return ;
}

从以上代码可以看出,它将问题分成两个均等的子问题,调用自身去解决。
很容易可以分析出来它的时间复杂度为:O(n),空间复杂度为:O(log)

下面举个斐波那契数列的例子来说明递归和迭代的效率问题。
斐波那契数列的定义:

fib(0) = 0
fib(1) = 1
fib(n) = fib(n-1)+fib(n-2)  (n>=2)

(1)直接采用递归的办法:

int fib(int n){
	if(n == 0)
		return 0;
	else if(n == 1)
		return 1;
	else(n >= 2)
		return fib(n-1)+fib(n-2);
}

分析递归的时间复杂度:
采用递推公式来做:
递推公式其实粗糙的计算应该是把T(n)的递推公式求出来,形式上和fib(n)一样,那么最后的结果也是大致相同的。
但是这个O(2^n)的复杂度太高了。其本质原因是每一次都要重新计算,那么就会把前面算过的都算一遍,重复浪费了。

如何解决这个问题:借助一定量的辅助空间,在子问题解决后,将它们及时保存下来。
为了避免这样计算,要么每次计算前,查下辅助空间里面有没有保存它的解;要么从最底层出发,自下而上地递推出个问题的解。前者是“制表”,后者是“记忆”也即“动态规划”。

接下来用制表的方法(线性迭代)来优化下:

int fib(int n, int &prev){
	if ( n == 0){  //递归基
	prev = 1;
	return 0;
	}else{
	int prevPrev;
	prev = fib(n-1, prevPrev);
	return prev + prevPrev;
	}
}

不同之处在于:prev = fib(n-1, prevPrev);是在调用自身,可以一直调用下去O(n-1)次,但是直到递归基才计算,并且开始自下而上地记录每个算子。
所以它的时间复杂度是:O(n)。空间复杂度是O(n)(因为每次都要额外的辅助空间记录)。

(2)采用迭代的方法:

int fib(int n){
	int f = 0, g = 1;
	while(n--){
	g = f + g;
	f = g - f;
	}
	return g;
}

迭代示意图我们可以将斐波那契数列视为走楼梯,一次可以走一个楼梯或者一次走两个楼梯。那么如果走到了第5层楼梯,有多少种方法?其实就是走到第4层楼梯的方法+第3层楼梯的方法。既然采取迭代的方法,那么就要找到最小的问题的解,自底向上求解。

while(n--){
	g = f + g;
	f = g - f;
	}

这个迭代方法结合图来看,可以比喻为:当我处于第n层台阶的时候,也就是迭代了第n次(此时n=0),我一共有多少种方法,用g来表示
而我的方法是:每次走一个台阶(g=1)或者不走台阶(f=0),n代表(倒数的)迭代次数,什么时候停止迭代?当n==0的时候。
(1)当n=n的时候,也就是在第一层台阶,途径是:第0层台阶+1,或者第一层台阶+0;也就是,我要走上第一层台阶,要么我走了一层(g = 1),要么我本身就在这一级台阶上(f = 0)。所以第一层台阶的方法有:g = g + f;
(2)当n = n-1的时候,也就是在第二层台阶的时候,我的方法的数目是由第一层台阶的方法数目+第0层(假设有)台阶的方法数目,第一层台阶的方法数目已经在(1)中得到了,第0层怎么表示呢?第0层意味着一开始就没有走,那么就是下一层(-1层)上来的次数:我这一层的总的方法数目是g,又没有走(f = 0;),那么实际上要上来的方法数(必须走上来台阶)就是:f = g - f;

这样的解释实际上我自己也觉得有一点拗口。再重新解释就是:我处于第某层台阶,我能够上来的途径就是:每次走一个台阶(下一层台阶的总方法数)或者不走台阶(这层台阶到上一层台阶的f=0方法数),当我选择不走台阶的时候,我的方法应该是f的总和(这个时候f应该理解为不走台阶的总方法数目);当我选择走台阶的时候,那么我应该知道下一层台阶的总方法数目即g的总和;所以本台阶的总方法数为:g = g + f;下一层台阶的方法数为:f = g - f;

实际上用物理意义(走台阶)来解释迭代反而不太好理解,应该回归到迭代的本质,迭代就是已知最原始最小的问题的解,依次扩展到原问题,这是一个从已知到未知的过程,运用迭代器来计数即可。
所以采用迭代的方法还可以这样写:

int fib(int n){
	int f1 = 0, f2 = 1;
	while(n--){
	f2 = f1 + f2;
	f1 = f2 - f1;
	}
	return g;
}

表示如图:
迭代-代数
解释如下:
迭代本身就是代数的一种计算方法,化繁为简,逐步推进。
已知最开始的前一个数和后一个数,求迭代了n次的后一个数。将每次得到的新的数视为后一个数,在此之前的相邻的一个数视为前一个数。
接下来具体的操作是:每次先执行f2 = f1 + f2;得到新的后一个数,此时它已经更新了f2,而此时的f1是现在的新的f2的邻居的邻居,他们之间隔了一个数(这个数是原来的f2),所以要继续更新f1,也就是让它变成现在的新的f2的邻居,那么执行f1 = f2 - f1;就可以得到更新后的f1了。
具体如图表示:
在这里插入图片描述要记住:只有程序里只有两个变量:f1f2,所以要时刻更新这两个数来求解。

接下来分析迭代法的复杂度:

int fib(int n){
	int f1 = 0, f2 = 1;  //执行2次
	while(n--){    //执行n次
	f2 = f1 + f2;  //执行1次
	f1 = f2 - f1;  //执行1次
	}
	return g;    //执行1次
}
//总共时间:O(2+2*n+1)=O(n),空间为O(1)(仅为f1和f2)

在leetcode里面也有题目:
斐波那契数列
斐波那契数列

(不过每次写题时,一定要考虑特殊情况,也就是考虑算法的退化性,更主要的原因是,不这样考虑通不过。)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值