27 | 递归树:如何借助树来求解递归算法的时间复杂度?

目的

借助递归树来分析递归算法的时间复杂度

递归树

递归的思想就是将大问题分解为小问题来求解,然后再将小问题分解为小小问题。这样一层一层地分解,直到问题的数据规模被分解得足够小,不用继续递归分解为止。

如果我们把这个一层一层的分解过程画成图就是一棵树——递归树。举例一棵斐波那契数列的递归树。节点里的数字表示数据的规模,一个节点的求解可以分解为左右子节点两个问题的求解。

如何用递归树来求时间复杂度

以归并排序为例,通过递归树来进行时间复杂度分析。归并排序的递归树示意图:

每一层排序时间消耗为n,如果知道了树的高度h,那么总的时间复杂度就是O(n∗h)。对于归并排序来说,归并排序递归树是一棵满二叉树。我们前两节中讲到,满二叉树的高度大约是 log2​n,所以,归并排序递归实现的时间复杂度就是 O(nlogn)

实战一:分析快排的时间复杂度

递归树:(假设快拍每次分区都很不平均,一个分区是另一个分区的 9 倍)

快速排序的过程中,每次分区都要遍历待分区区间的所有数据,所以,每一层分区操作所遍历的数据的个数之和就是 n。我们现在只要求出递归树的高度 h,这个快排过程遍历的数据个数就是 h∗n ,也就是说,时间复杂度就是 O(h∗n)。

  • 快排的结束条件是待排序的区间大小为1,也即叶子结点规模为1
  • 递归树中最短的一个路径每次都乘以 1/10​,最长的一个路径每次都乘以 9/10

遍历数据的个数总和就介于 nlog10​n 和 nlog910​​n 之间。根据复杂度的大 O 表示法,对数复杂度的底数不管是多少统一写成 logn,所以,当分区大小比例是 1:9 时,快速排序的时间复杂度仍然是 O(nlogn).

实战二:分析斐波那契数列的时间复杂度

跨台阶的递归树示意图:

  • f(n) 分解为 f(n−1) 和 f(n−2),每次数据规模都是 −1 或者 −2,叶子节点的数据规模是 1 或者 2。所以,从根节点走到叶子节点,每条路径是长短不一的。
  • 每次都是 −1,那最长路径大约就是 n;如果每次都是 −2,那最短路径大约就是 n/2​。每次分解之后的合并操作只需要一次加法运算,这次加法运算的时间消耗记作 1。
  • 从上往下,第一层的总时间消耗是 1,第二层的总时间消耗是 2,第三层的总时间消耗就是 2的平方。依次类推,第 k 层的时间消耗就是 2的k−1方,那整个算法的总的时间消耗就是每一层时间消耗之和。
  • 如果最长路径大约就是 n,那么总和就是2的n次方-1;如果最长路径长度是n/2,那么总时间消耗是2的n/2次方-1

分析全排列的时间复杂度

全排列的递推公式是:


假设数组中存储的是1,2, 3...n。f(1,2,...n) = {最后一位是1, f(n-1)} + {最后一位是2, f(n-1)} +...+{最后一位是n, f(n-1)}。

// 调用方式:
// int[]a = a={1, 2, 3, 4}; printPermutations(a, 4, 4);
// k表示要处理的子数组的数据个数
public void printPermutations(int[] data, int n, int k) {if (k == 1) {for (int i = 0; i < n; ++i) {System.out.print(data[i] + " ");}System.out.println();}for (int i = 0; i < k; ++i) {int tmp = data[i];data[i] = data[k-1];data[k-1] = tmp;printPermutations(data, n, k - 1);tmp = data[i];data[i] = data[k-1];data[k-1] = tmp;}
}

递归树示意图

第一层分解有 n 次交换操作,第二层有 n 个节点,每个节点分解需要 n−1 次交换,所以第二层总的交换次数是 n∗(n−1)。第三层有 n∗(n−1) 个节点,每个节点分解需要 n−2 次交换,所以第三层总的交换次数是 n∗(n−1)∗(n−2)。以此类推,第 k 层总的交换次数就是 n∗(n−1)∗(n−2)∗...∗(n−k+1)。最后一层的交换次数就是 n∗(n−1)∗(n−2)∗...∗2∗1。每一层的交换次数之和就是总的交换次数。
n + n*(n-1) + n*(n-1)*(n-2) +... + n*(n-1)*(n-2)*...*2*1

最后一个数,n∗(n−1)∗(n−2)∗...∗2∗1 等于 n!,而前面的 n−1 个数都小于最后一个数,所以,总和肯定小于 n∗n!,也就是说,全排列的递归算法的时间复杂度大于 O(n!),小于 O(n∗n!),虽然我们没法知道非常精确的时间复杂度,但是这样一个范围已经让我们知道,全排列的时间复杂度是非常高的。

总结:掌握分析的方法很重要,思路是重点,不要纠结于精确的时间复杂度到底是多少

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/419064.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

28 | 堆和堆排序:为什么说堆排序没有快速排序快?

如何理解“堆” 堆排序是一种原地的、时间复杂度为 O(nlogn) 的排序算法 堆的两个特点&#xff1a; 一颗完全二叉树堆中每个节点都必须大于等于&#xff08;或者小于等于&#xff09;其左右子节点的值&#xff1b; 对于每个节点的值都大于等于子树中每个节点值的堆&#xff…

29 | 堆的应用:如何快速获取到Top 10最热门的搜索关键词?

为什么评价算法性能是根据时间和空间复杂度&#xff0c;而不是别的参数&#xff1f;是因为计算机结构是冯诺依曼体系&#xff0c;除了输入输出设备和控制器&#xff0c;就剩下运算器和存储器了 问题引入 搜索引擎的热门搜索排行榜功能是如何实现的&#xff1f;搜索引擎每天会…

多线程——线程间的同步通信

1、概要 线程间的相互作用&#xff1a;线程之间需要一些协调通信&#xff0c;来共同完成一件任务。线程间的协调通信主要通过wait方法和notify方法来完成。因为wait和notify方法定义在Object类中&#xff0c;因此会被所有的类所继承。这些方法都是final的&#xff0c;即它们都是…

30 | 图的表示:如何存储微博、微信等社交网络中的好友关系?

列出功能需求->翻译成逻辑算法->抽象出数据结构->确定物理存储结构 后面的不会脱离前面的独立存在&#xff0c;只存在于工作流的运用中&#xff0c;所以不能把它们独立地看。 问题引入 在微博中&#xff0c;两个人可以互相关注&#xff1b;在微信中&#xff0c;两个…

部署OpenStack问题汇总(五)--openstack中删除虚拟主机,状态一直未deleting

【原创文章&#xff0c;转载请注明出处】 一、我重启了该机器&#xff0c;之后想删除没有创建成功的虚拟机(没有打开cpu的vt)&#xff0c;结果发现状态一直为deleting状态。在这个状态下创建虚拟机也失败。 二、分析&#xff1a;在/var/log/nova/nova-compute.log的log找到如下…

Java 微服务框架对比:Dubbo 和 Spring Cloud

微服务特点&#xff1a; 微服务&#xff08;Microservices&#xff09;是一种架构风格&#xff0c;一个大型复杂软件应用由一个或多个微服务组成。 独立部署松耦合单一职责&#xff0c;每个服务仅关注一件任务 微服务框架 相关概念: rpc 1、远端过程调用&#xff0c;其调用协…

并发队列ConcurrentLinkedQueue和阻塞队列LinkedBlockingQueue用法

reference : http://www.cnblogs.com/linjiqin/archive/2013/05/30/3108188.html 在Java多线程应用中&#xff0c;队列的使用率很高&#xff0c;多数生产消费模型的首选数据结构就是队列(先进先出)。Java提供的线程安全的Queue可以分为阻 塞队列和非阻塞队列&#xff0c;其中阻…

31 | 深度和广度优先搜索:如何找出社交网络中的三度好友关系?

问题导入 给你一个用户&#xff0c;如何找出这个用户的所有三度&#xff08;其中包含一度、二度和三度&#xff09;好友关系&#xff1f; 搜索算法 算法是作用于具体数据结构之上的&#xff0c;深度优先搜索算法和广度优先搜索算法都是基于“图”这种数据结构的。这是因为&a…