看懂二叉树的三种遍历

本文详细介绍了二叉树的三种遍历方式:先序遍历、中序遍历及后序遍历,并通过实例图解说明每种遍历的具体过程。

转载自

https://blog.youkuaiyun.com/soundwave_/article/details/53120766
二叉树有三种遍历方式,前序遍历(preorder traversal ),中序遍历(inorder traversal ),后序遍历(postorder traversal )。下面给出详细的解释:
1.先(根)序遍历的递归算法定义:
若二叉树非空,则依次执行如下操作:
⑴ 访问根结点;
⑵ 遍历左子树;
⑶ 遍历右子树。
2.中(根)序遍历的递归算法定义:
若二叉树非空,则依次执行如下操作:
⑴遍历左子树;
⑵访问根结点;
⑶遍历右子树。
3.后(根)序遍历得递归算法定义:
若二叉树非空,则依次执行如下操作:
⑴遍历左子树;
⑵遍历右子树;
⑶访问根结点。

二叉树的遍历分为以下三种:

先序遍历:遍历顺序规则为【根左右】

中序遍历:遍历顺序规则为【左根右】

后序遍历:遍历顺序规则为【左右根】

什么是【根左右】?就是先遍历根,再遍历左孩子,最后遍历右孩子;

举个例子,看下图(图从网上找的):
这里写图片描述

先序遍历:ABCDEFGHK

中序遍历:BDCAEHGKF

后序遍历:DCBHKGFEA

以中序遍历为例:

中序遍历的规则是【左根右】,我们从root节点A看起;

此时A是根节点,遍历A的左子树;

A的左子树存在,找到B,此时B看做根节点,遍历B的左子树;

B的左子树不存在,返回B,根据【左根右】的遍历规则,记录B,遍历B的右子树;

B的右子树存在,找到C,此时C看做根节点,遍历C的左子树;

C的左子树存在,找到D,由于D是叶子节点,无左子树,记录D,无右子树,返回C,根据【左根右】的遍历规则,记录C,遍历C的右子树;

C的右子树不存在,返回B,B的右子树遍历完,返回A;

至此,A的左子树遍历完毕,根据【左根右】的遍历规则,记录A,遍历A的右子树;

A的右子树存在,找到E,此时E看做根节点,遍历E的左子树;

E的左子树不存在,返回E,根据【左根右】的遍历规则,记录E,遍历E的右子树;

E的右子树存在,找到F,此时F看做根节点,遍历F的左子树;

F的左子树存在,找到G,此时G看做根节点,遍历G的左子树;

G的左子树存在,找到H,由于H是叶子节点,无左子树,记录H,无右子树,返回G,根据【左根右】的遍历规则,记录G,遍历G的右子树;

G的右子树存在,找到K,由于K是叶子节点,无左子树,记录K,无右子树,返回G,根据【左根右】的遍历规则,记录F,遍历F的右子树;

F的右子树不存在,返回F,E的右子树遍历完毕,返回A;

至此,A的右子树也遍历完毕;

最终我们得到上图的中序遍历为BDCAEHGKF,无非是按照遍历规则来的;

根据“中序遍历”的分析,相信先序遍历和后序遍历也可以轻松写出~

<think>我们参考引用[2]和引用[3]中的后序遍历非递归实现,结合对初学者友好的注释,编写一个C语言版本的二叉树后序遍历非递归算法。 引用[2]使用了双栈法,引用[3]使用了单栈并借助一个标记指针(flag)来防止重复访问。这里我们选择使用单栈法,因为单栈法更节省空间,且代码逻辑清晰。 原理: 后序遍历的顺序是:左子树 -> 右子树 -> 根节点。 非递归实现难点在于:在访问根节点之前,需要确保其左右子树都已访问完毕。因此,我们需要记录每个节点的状态。 我们使用一个栈来保存节点,并用一个指针`flag`来记录最近访问过的节点。算法步骤: 1. 从根节点开始,一直向左子树遍历,并将沿途节点入栈,直到左子树为空。 2. 此时,检查栈顶节点: a. 如果栈顶节点的右子树为空,或者右子树已经被访问过(即右子树等于`flag`),则访问该节点,将其出栈,并用`flag`标记该节点(表示最近访问的节点),然后将当前节点置为空(避免重复入栈)。 b. 否则,转向栈顶节点的右子树,然后重复步骤1(即对该右子树进行同样的向左深入)。 这样,我们就能确保每个节点都是在其左右子树访问完毕后才被访问。 下面我们用C语言实现,并添加详细注释。 </think>### C语言实现二叉树后序遍历非递归算法(详细注释版) 以下代码实现了二叉树的后序遍历(非递归方式),注释详细解释了每一步的操作逻辑。后序遍历顺序为:**左子树 → 右子树 → 根节点**。 ```c #include <stdio.h> #include <stdlib.h> // 定义二叉树节点结构 typedef struct TreeNode { int val; // 节点存储的值 struct TreeNode *left; // 指向左子节点的指针 struct TreeNode *right; // 指向右子节点的指针 } TreeNode; void postorderTraversal(TreeNode* root) { // 创建节点栈用于存储未处理的节点 TreeNode* stack[100]; int top = -1; // 栈顶指针,-1表示空栈 // 当前节点指针和标记指针(用于防止重复访问) TreeNode* cur = root; TreeNode* visited = NULL; // 记录最近访问过的节点 // 主循环:当还有节点未处理或当前节点非空时继续 while (cur != NULL || top != -1) { // 步骤1:向左深入到底,沿途节点全部入栈 while (cur != NULL) { stack[++top] = cur; // 当前节点入栈 cur = cur->left; // 移动到左子节点 } // 查看栈顶节点(不弹出) TreeNode* peek = stack[top]; /* 步骤2:判断如何处理栈顶节点 */ if (peek->right == NULL || peek->right == visited) { // 情况1:无右子节点 或 右子节点已访问过 printf("%d ", peek->val); // 访问当前节点(后序位置) visited = peek; // 标记该节点为已访问 top--; // 弹出栈顶节点 cur = NULL; // 强制回溯(避免重复处理) } else { // 情况2:有未访问的右子节点 cur = peek->right; // 转向处理右子树 } } } /* 辅助函数:创建新节点 */ TreeNode* createNode(int val) { TreeNode* node = (TreeNode*)malloc(sizeof(TreeNode)); node->val = val; node->left = NULL; node->right = NULL; return node; } int main() { /* 构建示例二叉树: 1 / \ 2 3 / \ / 4 5 6 */ TreeNode* root = createNode(1); root->left = createNode(2); root->right = createNode(3); root->left->left = createNode(4); root->left->right = createNode(5); root->right->left = createNode(6); // 执行后序遍历 printf("后序遍历结果: "); postorderTraversal(root); // 输出应为: 4 5 2 6 3 1 return 0; } ``` --- ### 🔑 核心算法原理解析 #### 1. 关键变量说明 | 变量名 | 类型 | 作用描述 | |----------|------------|------------------------------------------------------------| | `stack` | 节点指针数组 | 存储待处理的节点(模拟递归调用栈) | | `cur` | 节点指针 | 当前正在处理的节点(初始为根节点) | | `visited`| 节点指针 | 记录最近访问过的节点(防止重复访问右子树) | #### 2. 算法流程详解 ```mermaid graph TD A[开始] --> B{当前节点为空且栈空?} B -->|是| C[结束] B -->|否| D[向左深入到底] D --> E{栈顶节点检查} E -->|无右子树 或 右子树已访问| F[访问并弹出节点] E -->|有未访问右子树| G[转向右子树] F --> H[标记为已访问] G --> D H --> B ``` #### 3. 关键代码段解析 ```c // 向左深入到底 while (cur != NULL) { stack[++top] = cur; // 当前节点入栈 cur = cur->left; // 移动到左子节点 } ``` **作用**:模拟递归进入左子树的过程,将所有左子节点压入栈中。 ```c if (peek->right == NULL || peek->right == visited) { printf("%d ", peek->val); visited = peek; top--; cur = NULL; } else { cur = peek->right; } ``` **作用**: - **条件判断**:检查右子树是否可访问(无右子树或右子树已处理) - **分支1**:访问节点 → 标记 → 弹出 → 强制回溯 - **分支2**:转向处理右子树 --- ### ⚙️ 时间复杂度分析 - **时间复杂度**:$O(n)$(每个节点恰好入栈/出栈一次)[^2] - **空间复杂度**:$O(h)$($h$为树高,最坏情况$O(n)$)[^4] --- ### 🌰 执行示例演示 对于二叉树: ``` 1 / \ 2 3 / \ / 4 5 6 ``` **遍历流程**: 1. 左深入:1→2→4入栈 2. 访问4(无右子节点) 3. 回溯到2(有未访问右子节点5) 4. 访问5(无右子节点) 5. 访问2(右子树已处理) 6. 回溯到1(有未访问右子节点3) 7. 左深入:3→6入栈 8. 访问6→访问3→访问1 **输出序列**:4 → 5 → 2 → 6 → 3 → 1 ---
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值