二叉树相关问题细谈递归(上)

简介: 二叉树相关问题细谈递归

递归

前言

 递归的思想如其名就是递和归,一步一步展开,最后在合回去,从而解决问题,通过每次递归不断改变一定的数据,将大问题转化成一个一个和大问题相似的小问题来求解,只需要简短的程序,一步一步完成某个工作。

举一个生活中的例子

 校长想要知道疫情返校人数,他可以一个一个寝室挨个查,也可以动用院长辅导员等人帮忙统计

递归就是如此

要学会递归,就要深刻了解递归的思想和方法,最重要的是他的逻辑性。

  • 首先你要了解写出这个递归函数到底是用来做什么的,这是递归函数的第一要素,递归函数短小精悍,但一定要明确知道自己的需求。
  • 在递归的过程中会有前进段和返回段,满足一定的条件就进行前进,如果不满足则return返回,不能死递归下去,不然就会爆栈,这就是递归的第二要素。
  • 在明确需求后,找出函数的等价关系式,这是递归的第三要素,会在后边的学习中进行说明。
    以下两道题帮助理解,精彩还在后边。

求n的阶乘

int factorial(int n)
{
 if(n <= 1)
  return 1;
 else
  return n * factorial(n-1);
}

 给这个函数传入参数,如果小于等于1,就返回1,假使传入的值为5,运行时return结果为nfactorical(4),这个函数的返回值重新开辟了一个函数,传入值为4,这样一直递归,直至传入结果为1,当一个函数返回时,会回到之前进入函数的位置,然后在n=2的函数中继续return,直至函数结束。
通过不断地传递,当遇到限制条件后开始归,如图所示

 这个函数的作用是求n的阶乘,所以函数f(n)的等价关系式是n
f(n-1),在后边的函数中也都一样,改变的是n的值。

求第n个斐波那契数递归解法

int fib(int n)
{
  if (n <= 2)         
    return 1;
    else
      return fib(n - 1) + fib(n - 2);
}

注意:前两个数的结果为1,后边的数都是前两个相加,函数结束的标志是n<=2,函数的功能是实现找到第n个斐波那契数,要知道第n个斐波那契数,就要知道第n-1个斐波那契数和第n-2个斐波那契数,要知道第n-1个斐波那契数,就要知道第n-2和第n-3个斐波那契数,一直递归下来,可以看到,在求第n个斐波那契数时,第n-2个斐波那契数被求了两次,第n-3个斐波那契数被求了3次,如果n非常大的话,利用递归来查找结果,会多余计算很多次。

我们可以来实验一下

#include <stdio.h>
#include <stdlib.h>
int count = 0;//全局变量
int fib(int n)
{
  if (n == 3)
    count++;
  if (n <= 2)
    return 1;
  else
    return fib(n - 1) + fib(n - 2);
}
int main()
{
  fib(10);
  printf("%d", count);
  return 0;
}

运行后如图

 传入10时,仅仅是3的斐波那契数就进行了21次,如果传入的值再大一点呢?

 这个时候使用递归的弊端就显示出来了,虽然代码原理浅显易懂,但有些情况下要进行的运算太多了。

传入50跑了许久都跑不出来。如果数据再大一点的话,会一直开辟栈空间,知道栈空间被耗尽,造成栈溢出。

这道题告诉我们虽然递归逻辑清晰,代码块小,但某种情况下我们还是利用非递归的方式来写。

思路:第三个数是1+1,第四个数是2+1,只要保存更新两个相加数即可。

int fib(int n)
{
  int ret=1,pre=1;//结果
  int older_ret;//第一个加数
  while (n > 2)
  {
    n -= 1;
    older_ret = pre;
    pre = ret;//上一次的结果赋值
    ret = pre + older_ret;
  }
  return ret;
}

总结:

1. 许多问题是以递归的形式进行解释的,这只是因为它比非递归的形式更为清晰。

2. 但是这些问题的迭代实现往往比递归实现效率更高,虽然代码的可读性稍微差些。

3. 当一个问题相当复杂,难以用迭代实现时,此时递归实现的简洁性便可以补偿它所带来的运行时开销。


 但是当我们在计算二叉树相关的问题时,在遍历树的过程中递归就非常的好用。

我写了另一篇二叉树及Topk问题,详解了二叉树,感兴趣的朋友可以去看一看。

从基本讲起,随后会有几道力扣题巩固理解

前中后序遍历

二叉树有三种遍历方式

1,前序遍历 根,左,右。

2,中序遍历 左,根,右。

3,后序遍历 左,右,根。

结构体声明如下

typedef int BTDataType;
typedef struct BinaryTreeNode
{
  BTDataType data;
  struct BinaryTreeNode* left;
  struct BinaryTreeNode* right;
}BTNode;

其中根为子树的根节点,左为左孩子节点,右为右节点。

前序遍历

//前序遍历
void BinaryTreePrevOrder(BTNode* root)
{
  if (root == NULL)
  {
    return;
  }
  printf("%d ", root->data);//先访问该节点
  BinaryTreePrevOrder(root->left);//再访问左子树和右子树
  BinaryTreePrevOrder(root->right);
}

 前序遍历就是先访问根节点,然后走左节点,再走右节点,首先访问该节点,然后找他的左,判断条件为如果该节点为空,就停止递,开始归。

假设我们已经有了一个二叉树

递归展开如下:

中序遍历和前序遍历相差不大

//中序遍历
void BinaryTreeInOrder(BTNode* root)
{
  if (root == NULL)
  {
    return;
  }
  BinaryTreePrevOrder(root->left);//先访问左子树
  printf("%d ", root->data);//再访问该节点
  BinaryTreePrevOrder(root->right);//最后访问右子树
}

下边的问题都将利用这个二叉树来进行解释,这个树可以展现出我们会遇到的大多种情况。

递归展开图如下

 上边标注了步骤及打印顺序,遇到有关递归的题目,有不懂的地方只要画一画递归展开图就会豁然开朗

 同样,后序还是更改一下顺序,按照左右根的顺序来访问打印,如果想要更加清晰地建看出,可以访问空节点返回前打印一个*号。

后序代码如下

//后序遍历
void BinaryTreePostOrder(BTNode* root)
{
  if (root == NULL)
  {
    printf("*");
    return;
  }
  BinaryTreePrevOrder(root->left);//先访问左子树和右子树
  BinaryTreePrevOrder(root->right);
  printf("%d ", root->data);//最后访问该节点
}

二叉树全局遍历问题

树的节点个数

要找树的节点个数,分析,访问全树,如果为空树就返回,如果不是空树就加1。可以找一个变量来存储这个值,但由于局部变量在一个函数是单个个体,全局变量的安全性不高,我们可以传过来一个指针变量,每当访问到非空节点就解引用加一,空节点就return。

也可以利用返回值直接返回每个函数返回的值。

代码如下

//树的节点个数
int BinaryTreeSize(BTNode* root)
{
  if (root == NULL)
  {
    return;
  }
  return BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1;
  //return root==NULL?0:BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1;//三目操作符,前序
}

控制每个函数返回一个值,最后总和起来的就是节点的个数。归的条件是节点为空,返回的是统计的节点个数。

仍然利用上图讲解

递归展开图如下

到了这里,你是否已经开始熟悉递归的思路了。

相同类型的题目,不过要判断条件

叶子节点的个数

目录
相关文章
|
1月前
|
存储 算法
数据结构与算法学习十六:树的知识、二叉树、二叉树的遍历(前序、中序、后序、层次)、二叉树的查找(前序、中序、后序、层次)、二叉树的删除
这篇文章主要介绍了树和二叉树的基础知识,包括树的存储方式、二叉树的定义、遍历方法(前序、中序、后序、层次遍历),以及二叉树的查找和删除操作。
25 0
|
5月前
|
算法 数据可视化 数据挖掘
LeetCode题目104: 二叉树的最大深度(递归\迭代\层序遍历\尾递归优化\分治法实现 )
LeetCode题目104: 二叉树的最大深度(递归\迭代\层序遍历\尾递归优化\分治法实现 )
LeetCode题目104: 二叉树的最大深度(递归\迭代\层序遍历\尾递归优化\分治法实现 )
|
5月前
|
存储 算法 编译器
技术经验解读:二叉树的遍历:先序中序后序遍历的递归与非递归实现及层序遍历
技术经验解读:二叉树的遍历:先序中序后序遍历的递归与非递归实现及层序遍历
38 0
|
6月前
|
算法
递归算法:二叉树前序、中序、后序遍历解析与递归思想深度剖析
递归算法:二叉树前序、中序、后序遍历解析与递归思想深度剖析
97 0
二叉树相关问题细谈递归(下)
二叉树相关问题细谈递归(下)
63 0
|
算法 UED 容器
【算法入门&二叉树】从先中后序的遍历到用中后序列构造二叉树|如何抵挡递归法该死的魅力(上)
【算法入门&二叉树】从先中后序的遍历到用中后序列构造二叉树|如何抵挡递归法该死的魅力
120 0
|
算法 容器
【算法入门&二叉树】从先中后序的遍历到用中后序列构造二叉树|如何抵挡递归法该死的魅力(下
【算法入门&二叉树】从先中后序的遍历到用中后序列构造二叉树|如何抵挡递归法该死的魅力
87 0
如何根据二叉树的两种遍历方式重建二叉树(理论篇)
如何根据二叉树的两种遍历方式重建二叉树(理论篇)
118 0
|
存储 机器学习/深度学习 Java
《Java数据结构》这些树和二叉树的性质你还记得吗?
《Java数据结构》这些树和二叉树的性质你还记得吗?
120 0