数据结构与算法——递归函数的复杂度分析(C++)

文章目录

  • 0.复杂度度量
    • 0.1 时间复杂度
      • 0.1.1 大O记号
      • 0.1.2 大Ω ,大Θ记号
    • 0.2 空间复杂度
  • 1.递归函数复杂度分析--以fib()为例
    • 1.1递归跟踪
    • 1.2递归方程
  • 2.fib函数的改进
    • 2.1 线性递归法
    • 2.2 迭代法
  • 3.总结

0.复杂度度量

Mathematics is more in need of good notations than of new theorems.
–Alan Turing
与新定理相比,数学更需要好的符号。
–艾伦 ⋅ \cdot 图灵

算法的复杂度存在于时间与空间两个维度,分别体现在随输入规模增加,消耗的时间的增加与占用计算机物理内存空间的增加。

0.1 时间复杂度

执行时间随规模增长的变化趋势可以表示为输入规模的一个函数,称为该算法的时间复杂度(Time complexity),记位 T ( n ) T(n) T(n)
由于大规模的问题更能放大效率差异带来的效果,所以我们忽略解决小规模问题时的能力差异,着眼于处理更大规模问题的时候的处理时间。

这种着眼长远、更为注重时间复杂度的总体变化趋势和增长速度的策略与方法,即“渐进分析(asymptotic analysis)”。

0.1.1 大O记号

大O记号是时间复杂度 T ( n ) T(n) T(n)的渐进上界,一般用于描述时间复杂度。其定义为对于任何 n > > 2 n>>2 n>>2都有
T ( n ) ≤ c ⋅ f ( n ) T(n)\leq c\cdot f(n) T(n)cf(n)
可认为n足够大时, f ( n ) f(n) f(n)给出 T ( n ) T(n) T(n)的增长上界。大O记号可以记为
T ( n ) = O ( f ( n ) ) T(n)= O\big(f(n)\big) T(n)=O(f(n))
大O记号有以下性质
1.对于任一常数 c > 0 c>0 c>0,有 O ( f ( n ) ) = O ( c ⋅ f ( n ) ) O\big(f(n)\big)=O\big(c\cdot f(n)\big) O(f(n))=O(cf(n))
2.对任意常数 a > b > 0 a>b>0 a>b>0,有 O ( n a + n b ) = O ( n a ) O\big(n^a+n^b \big)=O\big(n^a\big) O(na+nb)=O(na)

0.1.2 大Ω ,大Θ记号

类似的,大Ω ,大Θ记号也可描述时间复杂度。大Ω 一般描述时间复杂度 T ( n ) T(n) T(n)的渐进下界,大Θ一般描述时间复杂度 T ( n ) T(n) T(n)的平均水平。
数据结构与算法——递归函数的复杂度分析(C++)_第1张图片 图 1 大 O , 大 Ω , 大 Θ 记 号 图 示 图1 \quad 大O,大Ω ,大Θ记号图示 1OΩΘ

0.2 空间复杂度

空间复杂度是指算法所需存储空间的多少,是衡量算法性能的另一个方面。也由大O,大Ω ,大Θ记号表示。但是,就渐进复杂度而言,在任一算法的任何一次运行过程中所消耗的存储空间,都不会多于其间所执行基本操作的时间。

1.递归函数复杂度分析–以fib()为例

斐波拉契数列的二分递归实现**fib_I(int n)**程序如下,这是一种典型的多递归基的二分递归。

/*版本I:__int64 fib_I(int n)*/
//递推
__int64 fib_I(int n)//O(2^n)
{
   return (2 > n) ? (__int64)n : fib_I(n - 1) + fib_I(n - 2);
}

1.1递归跟踪

递归跟踪分析法是通过跟踪每个递归实例的创建、执行和销毁,来确定整个算法的计算时间。 其中,递归实例的创建与销毁全都由操作系统负责完成,对应的时间成本近似为常数,不会超过递归实例实际计算中所需的时间成本,往往予忽略。
使用递归分析法对**fib_I(int n)**进行分析:

  • 1.对于形如fib(k)的递归实例,算法的执行过程中会先后重复出现fib(n-k+1)次,由数学归纳法可容易得出;
  • 2.该算法的每个递归实例自身消耗常数时间,故整体运行时间正比于递归实例的总数
    ∑ k = 0 n f i b ( n − k + 1 ) = ∑ k = 1 n + 1 f i b ( k ) = f i b ( n + 3 ) − 1 = O ( ϕ n ) = O ( 2 n ) \sum^{n}_{k= 0}fib(n-k+1)=\sum^{n+1}_{k= 1}fib(k)=fib(n+3)-1=O(\phi^n)=O( 2^n) k=0nfib(nk+1)=k=1n+1fib(k)=fib(n+3)1=O(ϕn)=O(2n)
    其中, ϕ = ( 1 + 5 ) / 2 = 1.618 \phi=(1+\sqrt 5)/2=1.618 ϕ=(1+5 )/2=1.618
    所以,以上算法使用递归跟踪得出的时间复杂度为 O ( 2 n ) O(2^n) O(2n)。由于该算法的递归深度不超过n,所以该算法需要空间不超过 O ( n ) O(n) O(n)

1.2递归方程

递归方程法通过对递归模式进行数学归纳,导出复杂度定界函数得到递推方程(组)及边界条件,将复杂度的分析转化为递推方程(组)的求解。
使用递归方程法对**fib_I(int n)**进行分析:

  • 1 T ( n ) = { n , n ≤ 1 T ( n ) = T ( n − 1 ) + T ( n − 2 ) + 1 , e l s e T(n)=\left\{ \begin{matrix} n,n\leq 1\\ T(n)= T(n-1)+T(n-2)+1 ,else \end{matrix} \right. T(n)={n,n1T(n)=T(n1)+T(n2)+1,else
    S ( n ) = [ T ( n ) + 1 ] / 2 S(n)=[T(n)+1]/2 S(n)=[T(n)+1]/2,可得
    S ( n ) = { n , n ≤ 1 S ( n ) = S ( n − 1 ) + S ( n − 2 ) , e l s e S(n)=\left\{ \begin{matrix} n,n\leq 1\\ S(n)= S(n-1)+S(n-2) ,else \end{matrix} \right. S(n)={n,n1S(n)=S(n1)+S(n2),else
  • 2. S ( n ) S(n) S(n) f i b ( n ) fib(n) fib(n)形式一模一样,只是起始项不同,S(n)相对于fib(n)提前了一个单元。 S ( n ) = f i b ( n + 1 ) S(n)=fib(n+1) S(n)=fib(n+1)
  • 3. T ( n ) = 2 ⋅ S ( n ) − 1 = 2 ∗ f i b ( n + 1 ) − 1 = O ( ϕ n ) = O ( 2 n ) T(n)=2\cdot S(n)-1=2*fib(n+1)-1=O(\phi^n) =O( 2^n) T(n)=2S(n)1=2fib(n+1)1=O(ϕn)=O(2n)
    所以,以上算法使用递归方程得出的时间复杂度为 O ( 2 n ) O(2^n) O(2n)。由于该算法的递归深度不超过n,所以该算法需要空间不超过 O ( n ) O(n) O(n)。时间复杂度与空间复杂度的计算结果与递归跟踪法得出的一样。

2.fib函数的改进

2.1 线性递归法

第一种关于 f i b ( ) fib() fib()的改进是使用线性递归,使用 “记忆” 的方式进行递归,避免如此多递归实例的创建。prev初值为 f i b ( − 1 ) = 1 fib(-1)=1 fib(1)=1,prevPrev初值为 f i b ( 0 ) = 0 fib(0)=0 fib(0)=0,由此可实现自上而下的记忆递归,不必多次调用相同的实例。
算法的时间复杂度正比于创建的递归实例数,即为 O ( n ) O(n) O(n);由于递归深度仍然为 n n n,空间复杂度为 O ( n ) O(n) O(n)


/*版本II:__int64 fib_II(int n)*/
//记忆递归
__int64 fib_II(int n,__int64& prev)//O(n)
{
   if(0==n)
   {
      prev = 1;
      return 0; //fib_II(-1,prev)=1;fib_II(0,prev)=0;
   }
   else
   {
      __int64 prevPrev;
      prev=fib_II( n-1,prevPrev);//fib_II(1,prev)->fib_II(0,prevPrev)->[prevPrev=1],fib_(0,prevPrev)返回值0->fib_II(1,prev)返回值0+1
      return prev + prevPrev;
   }
}

2.2 迭代法

第二种关于 f i b ( ) fib() fib()的改进为迭代的方法,套用动态规划的策略,对f和g进行初始化,之后逐个迭代。
该算法时间复杂度为 O ( n ) O(n) O(n)。由于除了g、f,不占用额外的空间,空间复杂度为 O ( 1 ) O(1) O(1)

/*版本III:__int64 fib_III(int n)*/
//迭代
__int64 fib_III(int n)//O(n)
{
   int f = 0;
   int g = 1;
   while(n>0)
   {
      g = g + f;
      f = g - f;
      n--;
   }
   return f;
}

3.总结

本文首先介绍了衡量算法性能的尺度——时间复杂度与空间复杂度,及其表示方式。
以计算斐波那契数列的函数fib()为例,分别使用递归跟踪与递归方程法计算其复杂度,时间复杂度为 O ( 2 n ) O(2^n) O(2n),空间复杂度为 O ( n ) O(n) O(n)
最后对fib()进行改进,使用线性递归的改进实例时间复杂度降为 O ( n ) O(n) O(n),空间复杂度为 O ( n ) O(n) O(n);使用迭代的改进实例空间复杂度为 O ( n ) O(n) O(n),空间复杂度降为 O ( 1 ) O(1) O(1)

你可能感兴趣的:(数据结构及实现)