递归树-分析时间复杂度

递归的思想就是把一个大问题分解为小问题,再把小问题继续分解下去,一直到无法再分为止。所以递归的核心思路就是找到递归公式和终止条件。那么什么是递归树?简单来说,这颗树是由递归的子问题组成树,树的节点就是递归的子问题。

比如斐波那契数列的递归树就是这个样子:

一个节点的求解可以分解为两个左右子节点的求解过程,就这样递归下去形成了斐波那契数列的递归树

归并排序的时间复杂度分析

归并排序就是将一组数据不断一分为二,将大数组分为小数组,小数组再分为两个小小数组,每次都分为一半一半的数据。如图:

归并排序每次分解数据一分为二所消耗的时间可以考虑为常量1,重点比较耗时的是合并这个操作,合并操作将两个小数组合并为一个大数组,对于归并递归树中每一个层来说,总的归并带来时间复杂度都是一样的,因为每一层的节点数之和其实都是n,我们把每一层的时间消耗记做n,所以其实这颗树的时间复杂度就是树高度h乘以n,即O(h*n),那树的高度是多少?

对于归并排序来说,每次将数组一分为2,最终形成的就是一颗满二叉树,满二叉树的高度大约是logN,所以归并排序的时间复杂度:O(NlogN).

快速排序的时间复杂度分析

快速排序如果每次在分区操作后都可以将数组一分为2的话,其实跟归并排序一样,时间复杂度的递归公式为:

T(1)=C,T(n)=2*T(n/2)+n,容易推导出此时的时间复杂度O(NlogN),但是问题是:并不是每次分区后都可以恰好将数组一分为2!

比如说每次分区后,都将数组分为1:k的大小,假如当k=9时,得出时间复杂度的递归公式就是:

T(1)=C,T(n)=T(n/10)+T(9n/10)+n.这个公式的确可以推导出时间复杂度,但是会非常复杂,所以这里考虑使用递归树来推导。

当k=9,递归树如图:

快速排序中,每次分区操作其实都会遍历待区分区间的所有数据,所以在递归树中每一层遍历的数据个数之和就是n,假如树的高度是n,则这颗递归树的时间复杂度就是O(h*n).

快速排序的终止条件是待排序的区间大小为1,那么在上边的递归树中表现为最终叶子节点数据规模为1,比如n=1000时,f(n/10^3)就等于f(1),达到终止条件,但是f(9n/10^3)=f(9)则需要继续分区,所以这颗递归树中最短路径(最快到达叶子节点)的节点序列是:

n,n/10,n/10^2,n/10^3,...1

以上序列是从根节点到叶子节点的最短路径,即最小高度,等比数列求和结果为:log10(N),即lgN.

同理,最长路径即最大高度就是:

n,9n/10,9^2n/10^2,9^3n/10^3...1

等比数列求和:log10/9(N)

所以,遍历数据的总和就介于lgN到log10/9(N)之间,根据大O表法,统一记做logN.所以当k=9时的时间复杂度就是O(NlogN)

当k=99呢?其实一样是O(NlogN),所以只要k为一个事先确定的数,则快速排序的时间复杂度就是O(NlogN)

菲波那切数列的时间复杂度分析

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,则最长路径是n,每次都减去2则最短路径是n/2。然后再分析下每层的时间复杂度消耗,时间消耗主要就是每一层合并操作的加法运算,第一层我们记为常量1,那么第二层就是2,第三层2^2,第四层2^3....第k层就是2^k-1.所以总的时间消耗就等于所有层的消耗时间总和。

如果长度都为n则1+2+2^2+2^3+....+2^n-1=2^n-1

如果长度都为n/2,则1+2+2^2+2^3+....+2^(n/2-1)=2^(n/2)-1

所以菲波那切数列的时间复杂度是介于O(2^n)和O(2^n/2)之间指数级别的。

思考:如何把n个数的全排列找出来?

发布了222 篇原创文章 · 获赞 805 · 访问量 27万+

猜你喜欢

转载自blog.csdn.net/shengqianfeng/article/details/100511419
今日推荐