关于递归的理解及递归表达式复杂度分析(以求解最大公约数为例)

一,递归的四大基本法则:

①基准情形

基准情形是指那些不需要递归(不需要经过函数调用)之后就能退出的情况。它保证了递归的结束。

②不断推进

每一次递归之后,都要向着基准情形靠近,并且在靠近的过程中问题的规模越来越小。

③设计法则

书上说是:假设所有的递归调用都能运行-----“不是特别理解”

④合成效益法则

不要在不同的递归调用中做重复的工作。

 

二,实例

求解最大公约数--采用欧几里德算法

 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)

 

你可能感兴趣的:(关于递归的理解及递归表达式复杂度分析(以求解最大公约数为例))