用递归树求解递归算法时间复杂度
文章内容、图片均来自极客时间。
递归代码复杂度分析起来比较麻烦。一般来说有两种分析方法:递推公式和递归树。
1 递推公式法
归并排序的递推公式是:
merge_sort(p…r) = merge(merge_sort(p…q), merge_sort(q+1…r))
终止条件:
p >= r 不用再继续分解
我们假设对n个元素排序的时间是T(n),那分解成两个子数组排序的时间是T(n2)T(\dfrac{n}{2})T(2n)。merge函数合并两个子数组的时间复杂度是O(n)。所以归并排序时间复杂度计算公式就是:
T(n)=2∗T(n2)+n,n>2T(n)=2*T(\dfrac{n}{2})+n,n>2T(n)=2∗T(2n)+n,n>2
T(1)=c;
继续计算T(n)
T(n)=2*T(n/2)+n=2*(2*T(n/4)+n/2)+n=4*T(n/4)+2n=4*(2*T(n/8)+n/4)+2n=8*T(n/8)+3n=8*(2*T(n/16)+n/8)+3n=16*T(n/16)+4n...=2^k*T(n/2^k)+k*n
当n/2k=1n/2^k=1n/2k=1的时候,k=log2nk=log_2nk=log2n,代入上面的式子:T(n)=c∗n+nlog2nT(n)=c*n+nlog_2nT(n)=c∗n+nlog2n,用大O表示法,T(n)=O(nlogn)。
2 递归树法
递归的思想就是将大问题分解为小问题来求解。然后再将小问题分解成小小问题。这样一层层分解直到问题不能再分解。如果我们把这一层层的分解过程画成图,其实就是一棵树。我们把它叫做递归树。
参看下图,括号中的数字表示问题的规模。
归并排序比较耗时的操作是合并,也就是将两个小数组合并成一个大数组。其他操作的代价很低,可以记为常数L。
从图中看出每一层的耗时是相同的,都为n。现在我们只要知道这棵树的高度h,就可以得到总的时间复杂度O(h*n)。
从图中能看到这是一颗满二叉树。满二叉树的高度大约是log2nlog_2nlog2n。所以归并排序的时间复杂度就是O(nlogn)。
3 递归树分析法实战
3.1 快排时间复杂度
快排的递推公式:quick_sort(p…q) = quick_sort(p,i-1)+ quick_sort(i+1,q)
退出条件:p>=q
快排最好的情况是每次都能将数组一分为二。这时候用递推公式T(n)=2T(n/2)+n,就能得到时间复杂度O(nlogn)。
但是不可能每次都是理想情况。我们假设平均情况下每次分区,两个分区的比例是1:k。当k=9的时候,公式变为这样:
T(n)=T(9n10)+T(n10)+nT(n)=T(\dfrac{9n}{10})+T(\dfrac{n}{10})+nT(n)=T(109n)+T(10n)+n
这个公式的值不太好求。用递归树是不是更简单呢?
我们看到每一层因为有选择交换操作,且最多n次。所以每一层的操作代价是相同的:n。
递归树的路径长度却是不一样的。快排结束的条件是待排序的区间大小为1,也就是说叶子节点的数据规模是1。从节点n到叶子节点1,递归树中的最长路径是每次乘以110\dfrac{1}{10}101,最短路径是每次乘以910\dfrac{9}{10}109。通过计算,我们可以得到,从根节点到叶子节点的最短路径是 log10nlog_{10}nlog10n,最长路径是log(109)nlog_(\dfrac{10}{9})nlog(910)n。所以总操作代价在n∗log10nn*log_{10}nn∗log10n和n∗log(109)nn*log_(\dfrac{10}{9})nn∗log(910)n之间。大O表示法不关注对数的底数,所以平均时间复杂度O(n*logn)。
将k=99,999…替换之后结论相同。
3.2 斐波那契数列
上面的两个例子每层的操作数相同,都是n。这次需要具体分析每层的操作数。
菲波那切数列的实现代码:
int f(int n) {if (n == 1) return 1;if (n == 2) return 2;return f(n-1) + f(n-2);
}
把递归代码画成递归树如下。
f(n)分解为f(n-1)和f(n-2),每次-1,或者-2。叶子节点的数据规模是1或者2。那最长路径大概就是n,最短路径大概是n2\dfrac{n}{2}2n。
每次分解之后的合并操作只是一次加法,算一个时间1。第一层是f(n-1)+f(n-2),1次运算;第二层是f(n-2)+f(n-3),f(n-3)+f(n-4)是两次运算…依次类推,第k层的总耗时是2k−12^{k-1}2k−1。算法总耗时是每层耗时相加。
如果路径长度都为n,那么总耗时是:2n−12^n-12n−1
1+2+22+...+2n−1=2n−11+2+2^2+...+2^{n-1}=2^n-11+2+22+...+2n−1=2n−1
如果路径长度都为n2\dfrac{n}{2}2n,那么总耗时是2n2−12^{\dfrac{n}{2}}-122n−1
1+2+22+...+2n2−1=2n2−11+2+2^2+...+2^{\dfrac{n}{2}-1}=2^{\dfrac{n}{2}}-11+2+22+...+22n−1=22n−1
所以算法的时间复杂度在2n2−12^{\dfrac{n}{2}}-122n−1和2n−12^n-12n−1之间。这样的计算不够精确,只是一个范围。但是我们知道了该算法的时间复杂度是指数级的。复杂度非常高。
心得:时间复杂度是可以估算的,不用非常准确。只要数量级保证正确即可。
3.3 全排列的时间复杂度
1 递归公式是
假设数组里的内容是1,2,3.....n
f(n)={最后一位是1,f(n-1)}+{最后一位是2,f(n-1)}+...+{最后一位是n,f(n-1)}
接着画出递归树。
2 对于问题f(n),在得到子问题f(n-1)的结果之后需要把结果相加,有n次相加,所以第一层的操作数是n。
对于问题f(n-1),首先会有n个f(n-1)的问题需要求解。每个f(n-1),在得到子问题f(n-2)的结果之后需要把结果相加,有n-1次相加操作,所以第二层的操作数是n*(n-1)。
以此类推,到最后一层的子问题是f(1),会有n*(n-1)(n-2)…2个f(1)。f(1)可以直接返回,操作数是1。所以最后一层的操作数是:n∗(n−1)∗(n−2)∗...∗2∗1=n!n*(n-1)*(n-2)*...*2*1=n!n∗(n−1)∗(n−2)∗...∗2∗1=n!。
3 因为叶子问题为f(1),问题每次-1分解,所以有n层。
4 前面每一层的操作数都小于n!,所以时间复杂度在n∗n!n*n!n∗n!到n!n!n!之间。
5 算法时间复杂度大于O(n!),小于O(nn!)。复杂度非常高。
4 递归树法分析得步骤
1 找到递归公式画递归树
2 计算每一层在得到子问题的解以后,还要进行哪些操作才能得到本问题的答案,计算这些操作的操作数。
3 考虑树的最长路径和最短路径。
4 分别按照最长路径和最短路径计算所有层的操作数的和。
5 得出时间复杂度范围,推测时间复杂度。
用递归树求解递归算法时间复杂度相关推荐
- 递归树求解递归算法的时间复杂度
递归算法时间复杂度的计算方程式一个递归方程: 在引入递归树之前可以考虑一个例子: T(n) = 2T(n/2) + n2 迭代2次可以得: T(n) = n2 + 2(2T(n/4) + (n/2) ...
- 递归树求递归算法时间复杂度
开篇前言:为什么写这篇文章?笔者目前在学习各种各样的算法,在这个过程中,频繁地碰到到递归思想和分治思想,惊讶于这两种的思想的伟大与奇妙的同时,经常要面对的一个问题就是,对于一个给定的递归算法或者用分治 ...
- 基于主定理以及递推树求解递归算法的时间复杂度
非递归算法的时间复杂度可以通过找到执行次数最多的代码,计算其执行次数即可.但是递归算法的时间复杂度则无法通过这种方式求得.有一种最简单的求递归算法的方式,即利用递推方法求解时间复杂度.如下所示: 这种 ...
- 递归树——分析递归算法的时间复杂度
递归树 递归的思想就是,将大问题分解为小问题来求解,然后再将小问题分解为小小问题. 这样一层一层地分解,直到问题的数据规模被分解得足够小,不用继续递归分解为止. 如果我们把这个一层一层的分解过程画成图 ...
- [数据结构]递归树:借助树求解递归算法的时间复杂度
文章目录 递归树与时间复杂度分析 实战一:分析快速排序的时间复杂度 实战二:分析斐波那契数列的时间复杂度 实战三:待补充,先学其他的... 递归树与时间复杂度分析 我们前面讲过,递归的思想就是,将大问 ...
- 如何用递归树求快速排序时间复杂度
其实也就是点看算法导论的心得,感觉算法导论写的有点不详细的补充 快速排序 我的理解就是利用分治法 ,递归排序最后合并的排序,因为快速排序的最坏时间复杂度比较低所以快速被叫做快速排序如图 对于求快速排序 ...
- 斐波那契数列——递归与非递归算法时间复杂度分析
- 算法:递归(借助递归树来求解分析递归算法的时间复杂度)
递归代码的时间复杂度分析起来非常麻烦,今天我们尝试来借助递归树分析递归算法的时间复杂度. 1. 递归树与时间复杂度分析 递归的思想就是将大问题一层一层地分解为小问题来求解,如果我们把这个分解过程画成图 ...
- 27 | 递归树:如何借助树来求解递归算法的时间复杂度?
目的 借助递归树来分析递归算法的时间复杂度 递归树 递归的思想就是将大问题分解为小问题来求解,然后再将小问题分解为小小问题.这样一层一层地分解,直到问题的数据规模被分解得足够小,不用继续递归分解为止. ...
最新文章
- 2017年6月21号课堂笔记
- Quartus II12.0安装教程
- 3 了解MyBatis映射文件
- C语言检查一个字符串是否为另一个字符串的子字符串的算法(附完整源码)
- WeChall_Training: Register Globals
- linux安装定制添加输入,Arch Linux--定制自己的Linux操作系統(乙-國際化桌面安裝篇)...
- 用Mysql网页式管理工具安全地访问数据库的方法
- 美团大脑:知识图谱的建模方法及其应用
- ubuntu16.04安装gcc g++7.5.0及各个版本的切换
- 容器与devops_容器和DevOps如何改变杜克大学的IT部门
- 羽毛球:东南大学vs南京大学
- 苹果6显示连接id服务器出错,appleid:appleid连接失败该如何解决
- springboot Junit单元测试之坑--@SpringBootTest注解无法加载src/main/resources目录下资源文件
- Jframe任务栏图标隐藏
- Typora Beta版过期解决方法
- 怪诞行为学 读书笔记
- 【逆向基础】常用逆向工具介绍
- MITK中窗宽窗位相关代码
- CSS 学习笔记 - 盒模型
- move_base学习(一)之双激光差动式移动机器人导航仿真
热门文章
- 计算机游戏性能测评,总结:游戏性能属均衡_联想 IdeaCentre B325-劲速型_一体电脑评测-中关村在线...
- 、反向对冲就是让你成为庄家的模式来盈利
- MSDN SmartCast更改下载步骤
- 传输层 可靠传输 重传与确认 停止等待协议工作原理
- 【计量经济学】【高教版】第一次作业(7、8、10)
- chrome检查更新时出错:无法启动更新检查(错误代码为 3: 0x80040154)
- Linux Vim查找字符串
- Linux 网络配置文件
- 结合Free to Earn和Play to Earn,Monsterra在GameFi领域的尝试
- 海量数据下,如何使用多线程实现 Excel 导出?附源码!