代码编织梦想

什么是递归?

递归和迭代的区别联系

很多人其实都不太清楚递归与迭代的具体差别,迭代指的是你在某一个条件下一直循环
比如

for(int i=0;i<100;i++>)

指的就是迭代100次,迭代100次就停止了。

而递归不一样,递归指的是调用自己本身,例如:

int function1(int n,int m)
{
    n++;m++;
    return function1(n,m);
}

相当于是重复的调用function1这个函数本身。

递归

简而言之就是直接或者间接调用自己的算法,最基本的例子就是函数里调用函数本身就是一种递归调用。
所以每个递归过程都有一个递归函数,递归函数就是用函数自身定义给出定义的函数。

递归算法可以将一个大型的复杂问题转化为一个与原问题相似的规模较小的问题求解,其优势在于用有限的语句定义无限的集合,可以有效减少代码量,使程序简洁易懂;其缺点在于运行效率低空间消耗大,容易造成堆栈溢出

但是,需要注意的是,每个递归函数都必须有非递归定义的初始值,以确保递归函数完成计算。

举个例子1

阶乘函数:
n ! = 1 ( n = 0 ) 或 n ! = n ( n − 1 ) ! ( n > 0 ) n!=1(n=0)\\ 或\\ n!=n(n-1)!(n>0) n!=1(n=0)n!=n(n1)!(n>0)
相当于是递归的调用 n ∗ ( n − 1 ) n*(n-1) n(n1)
你可能想到了迭代,那我们可以就此写个for循环,但是我们现在讲的是递归,我们写一段Java代码看看他们之间的区别:

递归实现:

public static int facterial(int n) {
  if (n == 0)
   return 1;
  else if (n < 0)
   return -1;//发生异常,退出
  else
   return n * facterial(n - 1);
 }

迭代实现:

public static int f(int n) {
  if (n == 0)
   return 1;
  else if (n < 0)
   return -1;//发生异常,退出
  else {
   for (int i = n - 1; i >= 1; i--) {
    n *= i;
   }
   return n;
  }
 
 }

比较算法的好坏我们一般就是用算法的时间复杂度,那我们来比较一下:
针对问题规模为n的问题:

  • 递归实现:时间复杂度O(n);空间复杂度O(n)。
  • 迭代实现:时间复杂度O(n);空间复杂度O(1)。

比较可知两种实现的时间复杂度等价,空间复杂度递归占用的略大一下,但是代码的结构清晰度递归更清晰一些。

举个例子2

我们来看一个斐波那契数列的例子
它的递归是这样的:
F ( n ) = 1 ( n = 0 , 1 ) 或 者 F ( n ) = F ( n − 1 ) + F ( n − 2 ) ( n > 1 ) F(n)=1 (n=0,1)\\ 或者\\ F(n)=F(n-1)+F(n-2) (n>1) F(n)=1(n=0,1)F(n)=F(n1)+F(n2)(n>1)

那我们用代码分别实现一下递归与迭代:

递归实现:

public static int fibonacci(int n ){
  if(n<0)
   return -1; //发生异常,退出
  else if(n<=1)
   return 1;
  else 
   return fibonacci(n-1)+fibonacci(n-2);
 }

迭代实现:

public static int F(int n){
  if(n<0)
   return -1; //发生异常,退出
  else if(n<=1)
   return 1;
  else {
   int f0=1,f1=1,fx=0;
   for(int i=2;i<=n;i++){
    fx=f0+f1;
    f0=f1;
    f1=fx;
   }
   return fx;
  }
 }

分析比较一下两种实现方法:

  • 递归实现:时间复杂度O(1.618的n次方);空间复杂度O(n)。
  • 迭代实现:时间复杂度O(n);空间复杂度O(1)。

比较可知递归实现的时间复杂度已经非常大了,空间复杂度递归占用的略大一下,但是代码的清晰度递归更清晰一些。

而真正使用起来递归实现的代码是无用代码,用n=40这个数测试一下便知,递归实现的耗时太长了,有兴趣的可以测试一下。

递归优化

那为什么递归看上去这么没用还要介绍呢?

其实递归效率性能较低的原因有以下两点:

  1. 需递归调用工作栈支持(无法避免)

  2. 有可能出现子问题重叠现象(必须尽力避免)

但是递归可以节省代码量,使得程序更明了清楚(其实就是一部分偷懒~)


什么是分治?

字面意思,分而治之

分治策略(又称分治法)是对于一个规模为n的问题,若该问题可以容易地解决则直接解决,否则将其分解为k个规模较小的子问题,这些子问题互相独立且与原问题形式相同。分治策略递归地解这些子问题,再将各个子问题的解合并得到原问题的解

它的基本步骤,包括了:

分治法的基本步骤:

  1. 分解:将原问题分解为若干个规模较小、相互独立、与原问题形式相同的子问题;
  2. 解决:若子问题规模较小而容易解决则直接解,否则递归地解;
  3. 合并:将各子问题的解合并为原问题的解。

合并是分治法的关键所在,需要具体问题具体分析。

分治法也不是啥问题都能用
它有适用条件:

  1. 该问题缩小到一定程度可以容易地解决;
    该条件对于绝大多数问题能够满足。
  2. 该问题可以分解为若干个规模较小的相同子问题,即问题具有最优子结构性质;
    该条件是应用分治法的前提,也是大多数问题可以满足的,反映了递归思想的应用。
  3. 利用该问题分解出的子问题可以合并为该问题的解;
    能否利用分治法完全取决于该条件。若具备条件1、2而不具备条件3,可以考虑动态规划法或贪心法
  4. 该问题的各个子问题是独立的,不包含公共子问题。
    该条件涉及分治法的效率。若各子问题不是独立的,则分治法需要很多不必要的工作。

以及,分治策略往往和递归算法同时使用,因此递归算法的时间复杂度分析可适用于分治法。

搞了这么多原理,来讲个例子吧、

例子1

二分查找
给定n个元素组成的有序序列{0:n-1],在这n个元素中找到特定元素x。若使用顺序查找法,最坏情况下的时间复杂度为O(n);
利用有序的条件,采用二分查找法可以在最坏情况下将时间复杂度减少到O(log n)。

二分查找法的基本思想是:

将n个元素分成两半,取 a [ n / 2 ] a[n/2] a[n/2] x x x比较;

  1. x = a [ n / 2 ] x = a[n/2] x=a[n/2],则找到对应元素,查找结束;
  2. x < a [ n / 2 ] x <a[n/2] x<a[n/2],则在数组的左半部分继续查找;否则在右半部分继续查找;
  3. 无法再划分时,查找失败;

代码如下

int binarySearch(int a[],int x,int n)
{
    int low = 0;
    int high = n - 1;
    while(low <= high){
        int middle = (low + high) / 2;//取一半
        if(a[middle] == x)//找到值并且返回
            return middle;
        else if(x < a[middle])//如果比中间数小,就重新定义新的一半上界是中间数左边元素
            high = middle - 1;
        else
            low = middle + 1;//如果比中间数大,就重新定义新的一半下界是中间数右边元素
    }
    return -1;
}
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/CODE_WangZIli/article/details/125333890

五大经典算法一 递归与分治_star_li_92的博客-爱代码爱编程

我们要讲到分治算法,我觉得有必要说一下递归,他们就像一对孪生兄弟,经常同时应用在算法设计中,并由此产生许多高效的算法。 递归算法:直接或者间接不断反复调用自身来达到解决问题的方法。要求原始问题

详解分治算法-爱代码爱编程

详解分治算法 文章目录 详解分治算法概念适用条件解题步骤summary时间复杂度常见题型快排和归并排序汉诺(Hanoi)塔问题引入思路分析时间复杂度分治法-动态规划联系相同点不同点例题leetcode-241-为运算表达式设计优先级leetcode-53-最大子序和Kadane algorithmleetcode-169-多数元素leetcode

分治算法值递归概述-爱代码爱编程

分治算法之递归概述 文章目录 分治算法之递归概述递归定义直接调用间接调用递归的要素能用递归解决的问题需要满足的条件递归的应用场景递归的优缺点实例---汉诺塔问题递归算法设计问题合集问题1:递归算法执行中的递归状态表示问题2:递归算法执行中临时参数的初始化递归函数转化为非递归函数 递归定义 在调用一个函数的过程中又出现了直接或间接调用该函数本

递归算法(图文详解)-爱代码爱编程

递归算法 一、算法概述 递归算法是一种直接或者间接调用自身函数或者方法的算法。说简单了就是程序自身的调用。 二、算法实质 递归算法就是将原问题不断分解为规模缩小的子问题,然后递归调用方法来表示 问题的解。(用同一个方法去解决规模不同的问题) 三、算法思想 递归算法,顾名思义就是有两个大的阶段:递和归,即就是有去(递去)

递归与分治策略-爱代码爱编程

递归与分治策略 1.递归算法 1.1什么是递归: 程序直接或间接地调用自身的编程技巧称为递归算法。 一个过程或函数在其定义或说明中又直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需要少量的程序就可以描述出解题过程所需要的多次重复计算,大大减少了程序的代码量。 递归的能力

第七周算法作业:递归与分治-爱代码爱编程

第七周算法作业 1.给出小于sum的正整数a1,a2……an,问凑出和为sum有多少种方法?2、m个苹果,n个盘子,问多少种不同放法? 主要讲解递归与分治部分 作业题目: 1、给出小于sum的正整数a1,a2……an,问凑出和为sum有多少种方法? 2、m个苹果,n个盘子,问多少种不同放法? 1.给出小于sum的正整数a1,a2……an

分治算法详细讲解(含经典例题分析)-爱代码爱编程

分治法思路: 将整个问题分解成若干小问题后再分而治之。如果分解得到的子问题相对来说还是太大,则可反复使用分治策略将这些子问题分成更小的同类型子问题,直至产生方便求解的子问题,必要时逐步合并这些子问题的解,从而得到问题的解。 分治算法可以由递归过程来表示,因为分治法就是一种找大规模问题与小规模问题关系的方法,是递归设计的一种具体策略。 步骤 1.分解

算法(二)分治策略,递归分析和主方法-爱代码爱编程

递归与分治策略   递归与分治策略是算法中一类常见的策略或者说思想,很多问题都可以通过递归或者分治的策略进行处理。 递归   很难说递归算法起源于哪里(因为就计算机科学来说,使用循环是更为自然的算法),我相信递归思想的出现早于计算机出现很多年,其基本思路是确定一种算法,该算法对于 少数特定规模的输入即递归基, 仅进行常数时间的运算,在其他的输入规模下

分治算法详解-爱代码爱编程

分治算法介绍 分治算法的基本思想是将一个规模为N的问题分解为K个规模较小的子问题,这些子问题相互独立且与原问题性质相同。 求出子问题的解,就可得到原问题的解。即一种分目标完成程序算法,简单问题可用二分法完成。 基本原理 当我们求解某些问题时,由于这些问题要处理的数据相当多,或求解过程相当复杂,使得直接求解法在时间上相当长,或者根本无法直接求出。

算法练习 01背包 贪心算法 递归 回溯 分治-爱代码爱编程

文章目录 最长公共子序列算法原理代码 [最常公共子序列.js](..\ab_code\ja_JavaScript_dataStructure\最常公共子序列.js)01背包问题算法原理原理图视频讲解代码输出效果贪心算法分配饼干问题题目描述代码无重叠区间题目解题思路算法原理代码递归和回溯全排列问题原理图一原理图二算法框架代码合并排序merge()原理

算法练习-链表 leetcode 160. 相交链表_yinyl03的博客-爱代码爱编程

今日心情:🥱🥱 题目描述: LeetCode 160. 相交链表 给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。 图示两个链表在节点 c1 开始相交:   题目数据 保证 整个链式结构中不存在环。 注意,函数返回结果后,链表

python每日算法 | 分治法与归并排序,你还在担心被面试官问归并算法吗?_merge_sort(lst,low,mid) merge_sort(lst,mid+1,high)_chaochao️的博客-爱代码爱编程

 创作不易,来了的客官点点关注,收藏,订阅一键三连❤😜      前言 程序=数据结构+算法,算法是数学理论和工程实现的杂糅,是一个十分有趣神奇的学问。搞懂算法用另一种视角看编程,又会是一种全新的感受,如果你也在学习算法,不妨跟主任萌新超差一起学习,拿下算法! 系列文章目录 python每日算法 | 图文+“农村包围城市”详解堆排序,手