二叉树的链式表示

一、二叉树的表示

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

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

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),其中存放:

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

    • 局部变量

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

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

一个形象的类比

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

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

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

相关推荐
To_OC5 小时前
LC 49 字母异位词分组:想到哈希表很简单,选对 key 才是精髓
javascript·算法·leetcode
用户9385156350710 小时前
从 O(n²) 到 O(nlogn):一文读懂快速排序的“快”与“妙”
javascript·算法
To_OC11 小时前
手写快排次次翻车?别死背快排模板了,这才是面试官想听的底层逻辑
javascript·算法·排序算法
饼干哥哥12 小时前
Reddit VOC调研太慢?搭一个AI专家团队半小时洞察任何品类|以猫用饮水机为例
人工智能·算法·ai编程
地平线开发者13 小时前
Transformer模型部署之性能优化指南
算法
地平线开发者14 小时前
人在途中:从“编译失败”到“模型可落地”——CUDA 自定义算子
算法·自动驾驶
半个落月17 小时前
从递归到快速排序:用 JavaScript 把分治思想讲明白
javascript·算法·面试
小月土星18 小时前
JavaScript 快速排序:从 pivot、双指针到分治思想
javascript·算法·面试
小月土星18 小时前
JavaScript 递归入门:从 1 到 n 求和,再到数组扁平化
javascript·算法·面试