递归与时间复杂度——7.1

递归篇

递归的定义:

在函数内部重复调用函数自身。

递归的理解

将原问题拆解为子问题:例如求1-100的和,我们可以把这个问题拆解为求解100与1-99的和,依次类推最后就会把问题转化为求1+2的和。所以将原问题拆解的本质是:将复杂问题拆分为相同或者相似的简单的子问题,这也就是分治的思想。
想到这里自然而然有一个疑问,递归与循环有什么区别?循环是我们常用的,好理解的,如果能将这两来对比学习,我相信能够加深对递归的理解。
首先例如求1-100的和,显然可以使用循环来解决这个问题,那么他与递归有什么区别?一开始我理解的是递归需要上一次的运算结果,而循环不用;但是很快我发现这个理解是错误的!因为循环同样需要一个sum来保存之前的计算结果。
在学习完归并排序,并将其应用在链表上后我发现,递归与循环的区别在于:递归大多是从顶到下的拆分问题,达到分治的效果;而循环大多是从低到上的拆分问题。
在链表的相关实现中,通过递归实现的代码通常简单,但是由于递归设计栈的调用,需要空间记录返回位置,所以大多数比循环迭代的空间复杂度高。

怎么使用递归

类比循环:我们在使用循环时:要有循环退出条件,以及每轮循环进行后变量的变化,这个变量最终要满足退出循环的条件。
递归的使用:1. 递归退出条件;2. 递归表达式;3. 递归的入口
相似的,递归表达式应该与递归退出的条件相关,随着表达式中变量的变化,最终应该要退出递归。

递归的使用经验

在使用递归时,找递归表达式的时候,要抓大放小:例如:24.两两交换链表中的节点,递归表达式应该是 n1 -> swap(n3,n4),这个时候我们就不要去关注怎么交换这个两个节点的逻辑。
在找递归的出口时,通常考虑极端情况(特殊情况):就上面这个例子而言:当swap(node1,node2)中只有一个节点为空时应该就返回不为空的节点,两个都为空则返回空。
递归的入口也是比较特殊的,需要结合迭代变量(迭代中产生的变量),终止条件考虑。

时间复杂度的计算

时间复杂度的定义

执行算法所消耗的时间。

时间复杂度的理解

时间复杂度是与问题的规模,以及算法相关的,也就是说一个算法在问题规模为n下的执行时间。通常用O(n)、O(n^2)、O(nlogn)这种形式来比较。其衡量可以转化为:代码中被执行最多次的语句,而一个代码中执行最多的语句往往是嵌套在循环与递归中的。

时间复杂度的计算

循环:

  1. 内外循环均是与问题规模N相关:则是乘:内循环执行了n次,外循环执行力logn次,总次数:nlogn
    count = 0 for (k = 1; k<=n; k=k*2) for(j = 1; j<=n; j++) count++;
  2. 内循环次数与外循环变量有关,则是加:外循环还是执行力logn次,内循环执行k次,总次数 y = 1+2+4+...+2^x次(x为外循环执行次数),所以y=logn次。
    count = 0 for (k = 1; k<=n; k=k*2) for(j = 0; j<=i; j++) count++;

递归

int fact(int n){ if(n<=1) return 1;--------------------a else return n*fact(n-1);----------b }

  1. 考虑抽象的执行时间T(n):抽象的、全局的、与n相关的函数
  2. 递归出口:a式与n无关,时间复杂度O(1);
  3. 递归:b式,乘法与n无关:O(1);传入n-1执行T(n-1);

T(n) = O(1) ------------------n=1
T(n) = O(1) + T(n-1) ------n>1
不断迭代可以得到T(n) = (n-1)O(1) + T(1) = O(n)
总结:递归是将规模为n的问题分治为n-1...k(k为常数)的问题,计算时间复杂度时,也是考虑将T(n)逐步转化为求T(常数),然后通过递归出口找到这个常数。

主定理求解时间复杂度:

主定理:T(n) = aT(n/b) +f(n)
f(n):子问题的时间复杂度;a:子问题个数;b:子问题规模
其实主定理就是对递归中的迭代进行了一个总结,当子问题的时间复杂度达到一定程度时他会影响原问题的时间复杂度。

posted @ 2025-07-02 00:12  waterme  阅读(13)  评论(0)    收藏  举报