前段时间,考研群有很多同学问起递归到底怎了理解,递归怎么使用。今天就写下此篇推文,带同学们好好理解一下什么是递归?递归到底怎么使用!
定义
程序调用自身的编程技巧称为递归( recursion)。递归作为一种算法在程序设计语言中广泛应用。一个方法或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。递归是一种非常重要的算法思想,无论是在大厂的算法面试题中还是开发工作中,都是经常用到的。 例如求和问题:若要对1+2+3+……+100进行求和,我们可以通过循环的方法进行求解,代码如下:int sum = 0;for(int i = 0; i<=100; i++){ sum = sum + i;}
那么,如何通过递归方式求解呢?我们发现 1 + 2 + 3 + 4 + …. + 100 可以分解为 ( 1 + 2 + 3 + 4 + …. + 99) + 100,可以看出
S
100
= S
99
+ 100
,可以得出
S
n
= S
n-1
+ n
。
通过递归的方式代码如下:
int sum(int n){ if(n == 1) return 1; else return sum(n-1) + n;}
一、递归的特点
事实上,递归有两个明显的特点,终止条件(递归出口)和自身调用。 那么什么样的问题可以才可以使用递归的方式求解呢? ① 自身调用:原问题可以分解为若干子问题,子问题和原问题有同样的求解方法,且子问题比原问题更容易求解。 ② 终止条件:递归不能无限制的调用自身,必须有递归出口。 回到上述的求和的例子,再来看下递归的特点:二、递归的经典应用场景
哪些问题我们可以可以使用递归来解决呢? ☛阶乘问题 ☛斐波那契数列 ☛二叉树遍历问题 ☛翻转二叉树三、递归的解题思路
解决递归问题一般分为三个步骤,分别是: ☛第一步:定义函数功能 ☛第二部:寻找递归终止条件 ☛第三步:递推函数的等价关系 没错,这就是递归的三板斧,看文字有点抽象,我们就拿最简单的阶乘来举例子康康吧~1
定义函数功能
首先,这个大家都是知道的,我们定义定义一个函数是需要使用它的,所以我们得清楚函数的干嘛用的。比如,你要解决阶乘问题,定义的函数功能就是n的阶乘,如下:/*定义一个求阶乘的函数(n > 0)*/
int factorial(itn n)
{
}
2
寻找函数的递归出口
递归的一个终止条件就是必须要有一个终止的条件,也就是不能无限循环地递归调用自己。所以,用递归的思路去解决问题的时候,就需要寻找递归终止条件是什么。正如,在n == 1时,不能再递归下去,可以跳出循环,因此 n == 1 可以作为递归的终止条件,如下:/*定义一个求阶乘的函数(n > 0)*/
int factorial(itn n)
{
if(n == 1)
return 1;
}
3
递归函数的等价关系
递归的本质就是原问题可以分解为若干子问题,且若干子问题更容易解决。即『原问题和子问题都可以用同一个函数关系表示。地推函数的等价关系式,这个步骤就等价于寻找原问题与子问题的关系,如何用一个公式把这个函数表示清楚』。阶乘的公式就可以表示为:f(n) = n*f(n-1)。因此,递归程序可以写成如下:/*定义一个求阶乘的函数(n > 0)*/
int factorial(itn n)
{
if(n == 1)
return 1;
else
return n*factorial(n-1);
}
四、实例
1
阶乘问题
阶乘问题的数学表达式为:n! = n * (n-1) * (n-2) * …* 1 (n>0)。通过分析可以得出n! = (n-1)! * n。令F(n) = n!,则F(n) = F(n-1) * n。则阶乘问题符合条件(1)。由0! = 1,可以得出F(0) = 1。则阶乘问题符合条件(2),递归出口为F(0) = 1。利用递归求解阶乘问题代码如下:int factorial(int n){ int sum = 0; if (0 == n) return 1; else sum = n * factorial(n-1); return sum;}
2
斐波那契数列
斐波那契数列(Fibonacci sequence),因数学家列昂纳多·斐波那契(Leonardoda Fibonacci)以兔子繁殖为例子而引入,故又称为“兔子数列”,指的是这样一个数列:1、1、2、3、5、8、13、21、34、…… 在数学上,斐波纳契数列以如下被以递推的方法定义: F(1)=1,F(2)=1,,F(n) = F(n-1) + F(n-2)(n>=3,n∈N*)。 问题分析: 斐波那契数列的对于原问题F(n)的求解可以转为对F(n-1)、F(n-2)两个子问题的求解,故符合条件(1)。由F(1)=1,F(2)=1,可以得出斐波那契数列问题是有递归出口的,递归出口对应F(1) = 1,F(2) = 1。求解斐波那契数列的代码如下:
int Fib(int n)
{
if(n <= 2)
return 1;
else
return Fib(n-1) + Fib(n-2);
}
3
二叉树的遍历
/*前序遍历算法*/void PreOderTraverse(BiTree T){ if(T == NULL) return; printf(“%c",T->data); //显示结点数据,可以更改为其他对结点操作 PreOderTraverse(T->lchild); //先遍历左子树 PreOderTraverse(T->rchild); //最后遍历右子树 } /*中序遍历递归算法*/void InOderTraverse(BiTree T){ if(T == NULL) return ; InOderTraverse(T->lchild); //中序遍历左子树 printf(“%c",T->data); //显示结点数据,可以更改为其他对结点的操作 InOderTraverse(T->rchild); //最后中序遍历右子树 } /*后序遍历递归算法*/void PostOderTraverse(T){ if(T==NULL) return; PostOderTraverse(T->lchild); //先遍历左子树 PostsOderTraverse(T->rchild); //再遍历右子树 printf(“%c",T->data); //显示结点数可以更改为其他对结点数据 }
通过代码可以看出,二叉树的遍历过程使用递归方式实现既有助于理解,又简化了代码量。
4
反转二叉树
输入: 输出: 我们按照上面写的递归函数的三板斧来解题: ①『定义函数功能』 首先我们要明确的是,函数的功能是将给定的二叉树进行反转,所以函数可以定义为:/*翻转一棵二叉树*/void invertTree(BiTree *bt){ }
②『寻找递归出口』
这棵二叉树在什么时候不用反转了呢?当然是当前结点为NULL的时候或则当前结点为叶子结点的时候。因此,递归函数的终止条件(即递归出口)是:
void invertTree(BiTree *bt)
{
if(bt == NULL || bt->lchild == NULL && bt->rchild == NULL)
return bt;
}
③『递推函数的等价关系式』
原问题是你要翻转一棵二叉树,那么是不是可以拆分为子问题,我们分别去翻转该二叉树的左右子树?子问题是翻转它的左右子树,是不是又开一拆分为,翻转该左子树的左子树,以及它左子树的右子树呢?然后一直翻转到叶子结点为止。那么,就让我们来康康图解喽~
首先,要翻转根结点为4的二叉树,就需要
「 翻转它的左子树(根结点为2)和它的右子树(根结点为7
)」。
这个就是递归的
递
的过程了。
然后,根结点为2的树,不是叶子结点,需要继续
「翻转它的左子树(根结点为1)和右子树(根结点为3)」
。因为1和3都是叶子结点,所以就可以返回了,这也是递归的
递
的过程~
同理,根结点为7的子二叉树,也不是叶子结点,需要翻转
「它的左子树(根结点为6)和右子树(根结点为9)」
。
因为6和9都是叶子结点,所以也被返回了
。
左子树(根结点为2)和右子树(根结点为7)都被翻转完后
,这几个步骤就是
归
的过程,翻转的任务就完成了~
从上面的推导过程,我们可以总结出
「递推关系式」
就是:
invertTree(bt) = invertTree(bt->lchild) + invertTree(bt->rchild)
因此,我们可以很容易的写下如下代码:
我们上述的翻转二叉树的代码就完成了,简直无情?~
抓码计算机考研qq群
总群:625590924
广大:1143982604
暨大:1071137230
广工:938111325
华工:428389734
深大:729770764
浙大:978938582
厦大:1125268501
中大:921801084
南航:281118241
华农:515681663
重邮:736197896
北邮:1126650806
南邮:1109929146
广外:976231252
东北大学:1128523098
华南师大:428389734
南昌大学:923249141
给个“在看”支持一下我