Python 不自己试试,还真猜不出递归函数的时间复杂度!

简介: Python 不自己试试,还真猜不出递归函数的时间复杂度!

如题,以斐波那契数列为例,写以下三种递归算法进行测试:

>>> def F1(n):
  if n<3: return 1
  return F1(n-1)+F1(n-2)
>>> def F2(n,n2=1,n1=1):
  if n<3: return 1
  if n==3: return n2+n1
  return F2(n-1,n1+n2,n2)
>>> def F3(n:int)->int:
  if n<3: return 1
  t=n//2
  if n%2: return F3(t)**2+F3(t+1)**2
  return F3(t)*(F3(t+1)+F3(t-1))
>>> for i in range(1,16):i,F1(i),F2(i),F3(i),F1(i)==F2(i)==F3(i)
(1, 1, 1, 1, True)
(2, 1, 1, 1, True)
(3, 2, 2, 2, True)
(4, 3, 3, 3, True)
(5, 5, 5, 5, True)
(6, 8, 8, 8, True)
(7, 13, 13, 13, True)
(8, 21, 21, 21, True)
(9, 34, 34, 34, True)
(10, 55, 55, 55, True)
(11, 89, 89, 89, True)
(12, 144, 144, 144, True)
(13, 233, 233, 233, True)
(14, 377, 377, 377, True)
(15, 610, 610, 610, True)
>>> 



F1 函数测试:

增加一个全局变量count用于计算调用次数。

>>> def F1(n):
  global count
  count += 1
  if n<3: return 1
  return F1(n-1)+F1(n-2)
>>> for i in range(1,16):
  count=0; F1(i),count
(1, 1)
(1, 1)
(2, 3)
(3, 5)
(5, 9)
(8, 15)
(13, 25)
(21, 41)
(34, 67)
(55, 109)
(89, 177)
(144, 287)
(233, 465)
(377, 753)
(610, 1219)
>>> 
>>> count=0; F1(20),count
(6765, 13529)
>>> count=0; F1(25),count
(75025, 150049)
>>> count=0; F1(30),count
(832040, 1664079)
>>> 6765*2,75025*2,832040*2
(13530, 150050, 1664080)
>>> 6765*2-1,75025*2-1,832040*2-1
(13529, 150049, 1664079)
>>> 


结果真的有点出乎意料,F(n)的运行次数是 2*F(n)-1。这么巨大的数字,怪不得此函数算 F(40) 时电脑基本上就动不了,要知道 2*F(40)-1 = 204668309,两亿次的调用计算量可想而知,不只是两亿次的整数运算。


所以这个函数的时间复杂度应在立方级O(n³)和指数级O(2ⁿ)之间吧:


20210911204255127.png



F2 函数测试:

>>> def F2(n,n2=1,n1=1):
  global count
  count += 1
  if n<3: return 1
  if n==3: return n2+n1
  return F2(n-1,n1+n2,n2)
>>> for i in range(1,16):
  count=0; F2(i),count
(1, 1)
(1, 1)
(2, 1)
(3, 2)
(5, 3)
(8, 4)
(13, 5)
(21, 6)
(34, 7)
(55, 8)
(89, 9)
(144, 10)
(233, 11)
(377, 12)
(610, 13)
>>> 
>>> count=0; F2(100),count
(354224848179261915075, 98)
>>> count=0; F2(1000),count
(4346655768693745643568852767504062580256466051737178040248172\
90895365554179490518904038798400792551692959225930803226347752\
09689623239873322471161642996440906533187938298969649928516003\
704476137795166849228875, 998)
>>> 

明显,经过尾递归优化后的时间复杂度为线性级O(n)。但是,它计算到 F(1027) 时就报错“超出了最大递归深度”,估计算法的空间复杂度已大到“内存占用”已到了堆栈限制上限了。

 



F3 函数测试:

>>> def F3(n:int)->int:
  global count
  count += 1
  if n<2: return n
  if n==2: return 1
  t=n//2
  if n%2: return F3(t)**2+F3(t+1)**2
  return F3(t)*(F3(t+1)+F3(t-1))
>>> count=0; F3(100),count
(354224848179261915075, 462)
>>> count=0; F3(101),count
(573147844013817084101, 344)
>>> count=0; F3(200),count
(280571172992510140037611932413038677189525, 1131)
>>> count=0; F3(201),count
(453973694165307953197296969697410619233826, 807)
>>> 


这个函数的时间复杂应该接近线性对数级 O(nlogn) 的,并且当n是奇数时比偶数时计算的快。



函数的时间测试

>>> def Fib(n):
    if n<3: return 1
    if n%2: return Fib(n//2)**2+Fib(n//2+1)**2
    return Fib(n//2)*(Fib(n//2+1)+Fib(n//2-1))
>>> from time import time
>>> t=time();n=Fib(2000000);print(time()-t)
111.7971076965332



斐波那契数列的递归改进


综合以上几个函数的经验作以下改进,改进后计算第500万项只需80秒,比不作改进的计算200万项还要快半分钟。

>>> def Fib(n:int,n1=34,n2=55):
    if n<11: return [0,1,1,2,3,5,8,13,21,34,55][n]
    if n==11: return n1+n2
    if n<1001:return Fib(n-1,n2,n1+n2)
    t=n//2
    if n%2: return Fib(t)**2+Fib(t+1)**2
    return Fib(t)*(Fib(t+1)+Fib(t-1))
>>> from time import time
>>> t=time();n=Fib(1000000);print(time()-t)
7.492823362350464
>>> t=time();n=Fib(2000000);print(time()-t)
18.6656014919281
>>> t=time();n=Fib(3000000);print(time()-t)
35.3787145614624
>>> t=time();n=Fib(4000000);print(time()-t)
46.64482545852661
>>> t=time();n=Fib(5000000);print(time()-t)
80.72290563583374
>>> 


--All done!

目录
相关文章
|
机器学习/深度学习 缓存 算法
Python算法设计中的时间复杂度与空间复杂度,你真的理解对了吗?
【10月更文挑战第4天】在Python编程中,算法的设计与优化至关重要,尤其在数据处理、科学计算及机器学习领域。本文探讨了评估算法性能的核心指标——时间复杂度和空间复杂度。通过详细解释两者的概念,并提供快速排序和字符串反转的示例代码,帮助读者深入理解这些概念。同时,文章还讨论了如何在实际应用中平衡时间和空间复杂度,以实现最优性能。
292 6
|
机器学习/深度学习 存储 算法
颠覆认知!Python算法设计中的时间复杂度与空间复杂度,你真的理解对了吗?
【7月更文挑战第22天】在Python算法设计中,时间与空间复杂度是评估算法效能的核心。时间复杂度不仅限于大O表示法,还涵盖平均与最坏情况分析。空间复杂度虽关注额外存储,但也反映内存效率。平衡二者需视场景而定,如利用原地算法减少内存消耗,或牺牲空间加速执行。算法优化技巧,如分治与动态规划,助你在资源与速度间找寻最优解,从而高效应对大数据挑战。
231 3
|
缓存 算法 数据处理
Python入门:9.递归函数和高阶函数
在 Python 编程中,函数是核心组成部分之一。递归函数和高阶函数是 Python 中两个非常重要的特性。递归函数帮助我们以更直观的方式处理重复性问题,而高阶函数通过函数作为参数或返回值,为代码增添了极大的灵活性和优雅性。无论是实现复杂的算法还是处理数据流,这些工具都在开发者的工具箱中扮演着重要角色。本文将从概念入手,逐步带你掌握递归函数、匿名函数(lambda)以及高阶函数的核心要领和应用技巧。
Python入门:9.递归函数和高阶函数
|
算法 Python
Python 数据结构和算法:解释什么是 Big O 表示法?举例说明几种常见的时间复杂度。
Python 数据结构和算法:解释什么是 Big O 表示法?举例说明几种常见的时间复杂度。
333 0
|
算法 Python
震惊!Python 算法设计背后,时间复杂度与空间复杂度的惊天秘密大起底!
在 Python 算法设计中,理解并巧妙运用时间复杂度和空间复杂度的知识,是实现高效、优雅代码的必经之路。通过不断地实践和优化,我们能够在这两个因素之间找到最佳的平衡点,创造出性能卓越的程序。
198 5
|
算法 Python 容器
Python常见操作的时间复杂度
本文整理了Python中常见数据结构操作的时间复杂度,旨在帮助大家了解Python操作的性能,协助运行更快的代码。
792 0
Python常见操作的时间复杂度
|
存储 算法 搜索推荐
深度剖析 Python 算法:时间复杂度与空间复杂度的爱恨情仇,你站哪边?
【7月更文挑战第23天】在Python算法设计中,时间复杂度与空间复杂度如影随形,反映算法效率与资源消耗。时间复杂度揭示算法随输入规模增长的计算趋势,空间复杂度关注额外存储需求。找最大值示例中,两种实现均具O(n)时间与O(1)空间复杂度,但在排序等复杂场景下,如冒泡排序与快速排序,或哈希表与二叉树查找,权衡变得关键。实时系统偏好低时间复杂度算法,存储受限环境则需关注空间效率。最佳选择依应用场景而定,掌握二者平衡,方能编写高效代码。
193 10
|
算法 Python
震惊!Python 算法设计背后,时间复杂度与空间复杂度的惊天秘密大起底!
【7月更文挑战第23天】在Python算法设计中,时间与空间复杂度是幕后操控程序效率的双雄。时间复杂度反映算法执行时间随输入规模增长的速度,空间复杂度则计量算法运行时额外内存的使用。如顺序查找的时间复杂度O(n)与固定空间O(1),对比冒泡排序的O(n^2)时间和快速排序的O(n log n)时间优势,后者虽递归消耗空间,但在多数情况下提供更佳性能。根据需求,可权衡选择,如利用哈希表在充足内存下实现O(1)查找,或在空间受限时,偏好空间效率更高的算法,实现Python代码的高性能与优雅。
162 6
|
存储 算法 搜索推荐
揭秘!Python算法设计的隐形杀手:忽视时间复杂度与空间复杂度的后果有多严重?
【7月更文挑战第24天】在 Python 编程中, 算法设计是性能与效率的基石。忽视时间复杂度 (如使用 O(2^n) 的斐波那契数列递归算法而非 O(n) 的动态规划版本) 和空间复杂度 (如在插入排序中每次迭代都复制整个已排序数组, 导致 O(n^2) 的空间复杂度) 可能严重拖累程序。性能优化至关重要, 合理的算法设计保证程序高效稳定, 是攀登技术高峰的坚实阶梯。
206 3
|
算法 搜索推荐 数据处理
震惊!Python算法设计背后,时间复杂度与空间复杂度的惊天秘密大起底!
【7月更文挑战第24天】在编程世界里, Python以简洁强大备受欢迎, 但算法设计与复杂度分析对程序性能至关重要。算法是程序的灵魂, 其效率直接影响数据处理能力。时间复杂度衡量算法执行速度, 如冒泡排序O(n²)与快速排序O(n log n)的显著差异; 空间复杂度关注内存占用, 递归算法需警惕栈溢出风险。优秀算法需平衡时间和空间效率, 深入理解问题本质, 迭代优化实现高效可靠。
204 2