二叉树的链式表示

一、二叉树的表示

前面我们讲过了二叉树的顺序表示(堆),现在来看一下如何用链式结构表示二叉树。

要储存二叉树,就要存储各个节点,每个节点包含该节点的值和指向左右子树的指针

cpp 复制代码
typedef int BTDataType;
typedef struct BinaryTreeNode
{
	BTDataType data;
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
}BTNode;

二、二叉树的遍历

二叉树的遍历方式主要分为两大类:深度优先遍历(DFS)广度优先遍历(BFS)

1.深度优先遍历(DFS)

遍历方式 访问顺序 应用举例
前序遍历(Preorder) 根 → 左子树 → 右子树 复制树、前缀表达式求值
中序遍历(Inorder) 左子树 → 根 → 右子树 二叉搜索树(BST)可得到有序序列
后序遍历(Postorder) 左子树 → 右子树 → 根 释放树节点、后缀表达式求值

(1)前序遍历

这里详细展示前序遍历过程

cpp 复制代码
void PrevOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}
	printf("%d ", root->data);
	PrevOrder(root->left);
	PrevOrder(root->right);
}

按照红色箭头执行,前序遍历二叉树的逻辑过程

  • 逻辑上:每个函数调用都有自己"独立"的栈帧,互不干扰。

  • 物理上 :系统会高效重用已释放的栈内存,从而避免频繁分配/释放物理页,提升性能。

  • 这种重用对程序员透明,不需要(也不能)主动控制。

(2)中序遍历

cpp 复制代码
void InOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}
	
	InOrder(root->left);
	printf("%d ", root->data);
	InOrder(root->right);
}

(3)后序遍历

cpp 复制代码
void PostOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}
	
	PostOrder(root->left);
	PostOrder(root->right);
	printf("%d ", root->data);
}

2.广度优先遍历(BFS)

三、二叉树的节点计算

1.树的节点总数

树的节点总数的计算,类似统计全校在校师生个数,让校长一个一个计数,计算起来是很麻烦的,也不可能用这种方式实现,应该从最底层开始计数,让每一层上报。

若为空,则返回0,否则就返回左子树与右子树节点个数之和+1。

cpp 复制代码
int TreeSize(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	return TreeSize(root->left) + TreeSize(root->right) + 1;
}

2.树的叶子节点数

若root==NULL,则返回0,若该节点指向左右子树的指针都为空,则返回1,否则返回左子树节点数+右子树节点数

cpp 复制代码
int TreeSizeLeaf(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	if (root->left == NULL && root->right == NULL)
	{
		return 1;
	}
	return TreeSizeLeaf(root->left) + TreeSizeLeaf(root->right);
}

3.树的层数

如果root==NULL,返回0,否则返回左右子树中较大的层数+1

cpp 复制代码
int TreeHeihgt(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	return TreeHeihgt(root->left) > TreeHeihgt(root->right) ? TreeHeihgt(root->left) + 1 : TreeHeihgt(root->right) + 1;
}

但其实这样没有记录每次算出来的层数,会重复执行多次,当数据量较大的时候,效率会低,应该用变量将其记录下来

cpp 复制代码
int TreeHeihgt(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	int leftHeight = TreeHeihgt(root->left);
	int rightHeight = TreeHeihgt(root->right);
	return  leftHeight> rightHeight ? leftHeight + 1 : rightHeight + 1;
}

四、递归调用的本质

  • 代码只有一份 :函数的机器指令存储在内存的代码段中,每次调用(包括递归调用)都是执行同一段指令。

  • 数据不同 :每次调用会创建新的栈帧(stack frame),其中存放:

    • 参数(如当前节点指针)

    • 局部变量

    • 返回地址(决定了递归返回后继续执行的指令位置)

  • 结果不同:正是由于栈帧中的数据不同(例如当前遍历到的节点不同),同一份指令才会产生不同的执行效果和最终结果。

一个形象的类比

就像一本烹饪书(一份指令),不同的人(不同的栈帧数据)按照同样的步骤做菜,但因为各自拿到的食材不同(参数不同),做出来的菜自然不同。做菜时,每人都有自己的操作台(栈帧),互不干扰。

补充一点:返回地址也是"数据"

"压进栈的数据"不仅包括参数和局部变量,还包括返回地址。返回地址决定了函数执行完毕后该回到哪里继续执行。即便都是调用同一个递归函数,每次调用返回后会回到调用点下一行代码,这个控制流差异也是依靠栈帧中保存的返回地址实现的。

相关推荐
CHHH_HHH1 小时前
【C++】二叉搜索树全面升级,深度剖析AVL树
开发语言·数据结构·c++·算法·stl
Mumu12181 小时前
P3211 [HNOI2011] XOR和路径
算法
高一学习c++会秃头吗1 小时前
页面置换算法实现
算法
不会就选b1 小时前
数据结构之双向循环链表
数据结构·链表
yuanyuan2o21 小时前
Transformers NLP 任务:阅读理解问答
人工智能·算法·自然语言处理·nlp·github
悠仁さん1 小时前
数据结构OJ 简单算法题
数据结构
菜菜的顾清寒1 小时前
力扣HOT100(52)动态规划 - 最长递增子序列
算法·leetcode·动态规划
WBluuue2 小时前
数据结构与算法:树上启发式合并
数据结构·c++·算法·启发式算法
不是光头 强2 小时前
feign-list-param-crash-cpp
java·数据结构·list