一文搞懂二叉树的递归实现原理(图解)

一文搞懂二叉树的递归实现原理(图解)

1、活动记录

调用函数时会发生genbg什么?如果这个函数存在形参,形参就初始化为实参传递来的值。另外,函数结束后,系统要知道从哪里继续执行程序。而递归的实现就需要知道 从哪里继续执行。指示函数从何处调用的信息保存在系统中。为了做到这一点,返回地址被存储在主内存中预留的特定区域。

对于函数调用来说,需要存储的信息不只是返回地址。因此使用栈进行动态分配效果会好一些。那么调用函数时,需要保存哪些信息?

每个函数(包括主函数main())的状态由以下因素决定:函数中的所有局部变量的内容,函数参数的返回值,表明在调用函数的何处重新开始的返回地址。----包含这些信息的数据区称为活动记录或者栈框架!!!位于允许时栈。只要函数在执行,其活动记录就一下都在。活动记录一般还包括:一个指向调用程序的活动记录的指针以及非void类型的函数的返回值。
在这里插入图片描述

因此根据活动记录就能理解递归如何实现。递归只是被调用函数的名称正好和调用者相同。因此,递归调用不是表面上的函数调用自身,而是一个函数的实例调用同一个函数的另一个实例

2、举例说明

(1)创建树

输入信息:[15,4,20,17,19,22] 会创建如下二叉树(二叉树按照左小右大的特点创建)
在这里插入图片描述

(2)以先序递归遍历进行讲解

void preorder(BSTNode<T>* root)
{
	if (root != NULL)
	{
		visit(root);
		preorder(root->left);
		preorder(root->right);//递归内部表示为活动记录
	}
}

程序调用—创建活动记录

我们使用结点值表示栈中活动记录变化

(1)15 入栈

在这里插入图片描述

(2)当到34行处,第一次递归调用 preorder 函数,因此会创建实例放入栈中,4入栈

在这里插入图片描述

(3)继续运行,当再次运行到34行时,继续创建实例。

在这里插入图片描述

(4)但是由于p为空,那么该实例结束运行,接下来跳转到35行。

由于栈中存在15/4,那么下一个函数调用可以找到4对应的地址。然后继续向下执行,执行35行,向右子树递归寻找,然后从30行继续执行。值得注意的是假设右子树存在左子树,当运行到34行时会继续创建实例往左子树递归。

在这里插入图片描述

(5)由于4的右子树为空因此,程序执行到37行,**注意:**此时一轮递归才完成。那么4出栈。

在这里插入图片描述

(6)4出栈后,返回15的地址

在这里插入图片描述

(7)返回的时候会返回最近的递归(栈顶元素,栈是后入先出)。也就是调用35行,重复上述过程。难点:二叉树的递归遍历为双层套娃。当运行35行时,必然会运行到34行,也就是只有当34行一轮递归(所有活动记录出栈)完成后,才能进行35行。

3、绘图说明(一定要看)

虽然通过上面的描述可以大概理解到递归的实现,但是双重套娃(动态链接)还是无法在想象出如何实现。如下绘图展示:

说明:LS(left recursion,向左子树递归),RS(right recursion),数字表示简化的活动记录

在这里插入图片描述

以上就是左子树整个递归过程,然后继续往右边进行。

二叉树的递归难以理解就在于,递归函数中还存在递归。当递归结束(函数的实例结束),还需要再进行递归,所以函数的返回地址在活动记录中,就知道从哪里继续执行程序。

以上就是整个流程,如果有什么问题欢迎分享,自己最好单步运行进行理解。

4、代码

头文件:GenericBST.h

// 以下为基本二叉树需要实现的函数
//--------------generic binary search tree-----------------
#pragma once
#include<iostream>
using namespace std;

template<class T>
class BSTNode{
public:
	BSTNode(){
		left = right = NULL;
	}
	BSTNode(const T& val, BSTNode<T>* l = NULL, BSTNode<T>* r = NULL){
		data = val;
		left = l;
		right = r;
	}

public:
	T data;
	BSTNode<T>* left, * right;
};

template<class T>
class BST{
public:
	BST(){
		root = NULL;
	}
	~BST(){
		root = NULL;//不能只free(root)...
	}
	void insertelem(const T&);
	void preorder(BSTNode<T>* root);
	void inorder(BSTNode<T>* root);
	void postorder(BSTNode<T>* root);
	void breadthFirst();
	void iterativePreorder();
	void iterativeInorder();
	void iterativePostorder();
	T* search(BSTNode<T>* root, const T& el) const;

	int depth(BSTNode<T>* root);
	int getNodeNum(BSTNode<T>* root);
	int getLeafNum(BSTNode<T>* root);
	BSTNode<T>* getroot(){
		return this->root;
	}

};

BSTachieve.hpp文件实现

#pragma once
#include <queue>
#include <stack>
#include"GenericBST.h"

template<class T>
T* BST<T>::search(BSTNode<T>* root, const T& el) const
{
	BSTNode<T>* p = root;
	while (p != NULL)
	{
		if (p->data == el)//终止条件
			return &p->data;
		else if (el < p->data)
			p = p->left;
		else
			p = p->right;
	}
	return NULL;//while结束,说明没找到
}
template<class T>
void BST<T>::visit(BSTNode<T>* p)
{
	std::cout << p->data << " ";
}


template<class T>
void BST<T>::preorder(BSTNode<T>* p)
{
	if (p != NULL)
	{
		visit(p);
		preorder(p->left);
		preorder(p->right);
	}
}


template<class T>
void BST<T>::inorder(BSTNode<T>* root)
{
	BSTNode<T>* p = root;
	if (p != NULL)
	{
		inorder(p->left);
		visit(p);
		inorder(p->right);
	}
}
template<class T>
void BST<T>::postorder(BSTNode<T>* root)
{
	BSTNode<T>* p = root;
	if (p != NULL)
	{
		inorder(p->left);
		inorder(p->right);
		visit(p);
	}
}

// ------------头文件中的其他函数可以自行实现------------------
#include"GenericBST.h"
#include"BSTachieve.hpp"//含有模板需要包含实现文件 因为模板后绑定,最好写hpp。
#include "GenericBST.h"
using namespace std;

int main()
{
	BST<int> st;
	for (int i = 0; i < 5; i++)
	{
		int val[] = { 15,4,20,17,19 };
		st.insertelem(val[i]);
	}
	st.preorder();

	return 0;
}
  • 19
    点赞
  • 50
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
叉树是一种常见的数据结构,在归算法中也有很多应用。创建二叉树归算法可以分为前序遍历和中序遍历两种方式。 前序遍历方式下,我们按照“根-左-右”的顺序来构建二叉树。具体步骤如下: 1. 如果输入的节点值为null,则返回空节点。 2. 创建一个新的节点,将其值设置为当前的节点值。 3. 归调用函数,将左子树的根节点设置为当前节点的左子节点。 4. 归调用函数,将右子树的根节点设置为当前节点的右子节点。 5. 返回当前节点。 中序遍历方式下,我们按照“左-根-右”的顺序来构建二叉树。具体步骤如下: 1. 如果输入的节点值为null,则返回空节点。 2. 归调用函数,将左子树的根节点设置为当前节点的左子节点。 3. 创建一个新的节点,将其值设置为当前的节点值。 4. 归调用函数,将右子树的根节点设置为当前节点的右子节点。 5. 返回当前节点。 下面是一个示例代码,以前序遍历方式为例: ``` class TreeNode { int val; TreeNode left; TreeNode right; TreeNode(int x) { val = x; } } public class Solution { public TreeNode buildTree(int[] preorder, int[] inorder) { return build(preorder, inorder, 0, preorder.length - 1, 0, inorder.length - 1); } private TreeNode build(int[] preorder, int[] inorder, int preStart, int preEnd, int inStart, int inEnd) { if (preStart > preEnd || inStart > inEnd) { return null; } int rootVal = preorder[preStart]; int index = 0; for (int i = inStart; i <= inEnd; i++) { if (inorder[i] == rootVal) { index = i; break; } } TreeNode root = new TreeNode(rootVal); root.left = build(preorder, inorder, preStart + 1, preStart + index - inStart, inStart, index - 1); root.right = build(preorder, inorder, preStart + index - inStart + 1, preEnd, index + 1, inEnd); return root; } } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值