关于递归的理解及递归表达式复杂度分析(以求解最大公约数为例)
一,递归的四大基本法则:
①基准情形
基准情形是指那些不需要递归(不需要经过函数调用)之后就能退出的情况。它保证了递归的结束。
②不断推进
每一次递归之后,都要向着基准情形靠近,并且在靠近的过程中问题的规模越来越小。
③设计法则
书上说是:假设所有的递归调用都能运行-----“不是特别理解”
④合成效益法则
不要在不同的递归调用中做重复的工作。
二,实例
求解最大公约数--采用欧几里德算法
1 public static int gcd_recursive(int m, int n){ 2 if(m < n) 3 { 4 int tmp = m; 5 m = n; 6 n = tmp; 7 } 8 9 if(n == 0) 10 return m;//基准条件 11 return gcd_recursive(n, m%n);//不断推进 12 }
分析:
第9-10行,是递归的基准条件。如果n=0,函数执行到10返回,不会执行到11行进行递归调用。
第11行,进行递归调用的地方。它是不断推进的,因为递归调用的参数朝着基准条件的方向变小了,如:
gcd_recursive(16,12)---->gcd_recursive(12,4)--->gcd_recursive(4,0)
每次递归调用,问题的规模越来越小了。
时间复杂度分析:
由公式: m%n<=m/2 可知:每次递归调用,问题的规模减小一半,类似于二分查找,这显然是一个非常好的算法。
由于第2-5行,花费的时间为常量时间,同样,在第9-10行的if语句判断也是花费的常量时间,在第11行进行递归调用,问题规模减少一半。
可得出,T(N) = T(N/2)+O(1) 推出:时间复杂度为O(logN)
-------------------------------------------------------------
递归逻辑的分析:
对于 gcd_recursive(16,12),第9行不成立,进入到第11行递归
对于 gcd_recursive(12,4), 第9行不成立,进入到第11行递归
对于gcd_recursive(4,0),直接执行到第9行返回,返回的值是4
返回之后,程序此时执行到gcd_recursive(12,4)中的第11行(即最后一行,不要被第9行干扰!第9行在gcd_recursive(12,4)中根本没有执行!!)
第11行代码是:gcd_recursive(4,0)
因为,gcd_recursive(4,0) 的结果是4,故 return gcd_recursive(4,0) 返回的结果也是 4。也即gcd_recursive(12,4)执行完成之后返回4。
由上面gcd_recursive(12,4)执行完成之后返回4,那么当gcd_recursive(16,12)的第11行代码 return gcd_recursive(12,4)
执行完毕时,
整个程序结束了,返回的结果最终是4。
这种形式的递归又称为尾递归。可以看出,尾递归形式的程序最终返回的值就是 最里层递归调用得到的值。
在这篇文章中:字符数组转换成数字
递归的时间复杂度分析如下:
return recurse(c, len - 1) * 10 + (c[len - 1] - '0');
使得每次递归时,问题规模减小1,而后面的 + (c[len - 1] - '0') 操作可视为常量时间,故复杂度:
T(N) = T(N-1)+O(1) 得到T(N)=O(N)
结论:
对于递归操作而言,如果每次递归使问题的规模减半,而其他操作都是常数时间
T(N)=T(N/2)+O(1), 则T(N)=O(logN)
若每次递归使用问题的规模减1,而其他操作是常数时间
T(N)=T(N-1)+O(1),则T(N)=O(N)
若每次递归使问题的规模减半,而其他操作是线性时间,T(N) = T(N/2)+O(N)
则T(N)=O(NlogN)