二叉树的遍历是二叉树操作的基础核心,递归遍历实现简单但存在栈溢出风险,在处理深度较大的二叉树时,非递归遍历凭借手动维护栈的方式更具稳定性。本文将详细讲解二叉树前序、中序、后序的非递归遍历实现思路,结合 C 语言代码完整实现,帮助大家理解非递归遍历的核心逻辑。
一、二叉树基础准备
1.1 二叉树节点结构定义
首先定义二叉树的节点结构体,包含数据域和左右孩子指针,同时定义栈结构用于非递归遍历的节点暂存,栈的最大容量通过宏定义MAXSIZE设置。
cpp
#include <stdio.h>
#include <stdlib.h>
#define MAXSIZE 100
typedef char ElemType;
// 二叉树节点结构体
typedef struct TreeNode {
ElemType data; // 节点数据(字符型)
struct TreeNode *lchild; // 左孩子指针
struct TreeNode *rchild; // 右孩子指针
} TreeNode;
typedef TreeNode* BiTree;
// 栈结构体(用于非递归遍历手动维护节点)
typedef struct {
BiTree data[MAXSIZE];
int top; // 栈顶指针,初始为-1表示空栈
} Stack;
1.2 二叉树的构建
本文所有遍历均基于前序遍历序列 构建二叉树,序列中用#表示空节点,示例序列为ABDH#K###E##CFI###G#J##。
构建逻辑为:递归读取序列字符,非#则创建节点,依次递归构建左子树和右子树,#则置为空节点。

cpp
char str[] = "ABDH#K###E##CFI###G#J##";
int idx = 0; // 全局索引,用于遍历构建序列
// 前序构建二叉树
void createTree(BiTree *T)
{
ElemType ch = str[idx++];
if (ch == '#')
{
*T = NULL; // 空节点
}
else
{
*T = (BiTree)malloc(sizeof(TreeNode));
(*T)->data = ch;
createTree(&(*T)->lchild); // 先构建左子树
createTree(&(*T)->rchild); // 后构建右子树
}
}
1.3 栈的基本操作
非递归遍历的核心是手动操作栈,实现栈的初始化、判空、入栈、出栈基础功能,为后续遍历提供支撑:
cpp
// 初始化栈
void initStack(Stack *s) {
s->top = -1;
}
// 判断栈是否为空
int isEmpty(Stack *s) {
return s->top == -1;
}
// 入栈:成功返回1,栈满返回0
int push(Stack *s, BiTree p) {
if (s->top >= MAXSIZE - 1) return 0;
s->data[++(s->top)] = p;
return 1;
}
// 出栈:成功返回1,栈空返回0,p接收出栈节点
int pop(Stack *s, BiTree *p) {
if (isEmpty(s)) return 0;
*p = s->data[(s->top)--];
return 1;
}
二、非递归前序遍历
2.1 遍历规则
前序遍历的核心规则是:根节点 → 左子树 → 右子树,即先访问当前根节点,再依次遍历左、右子树。
2.2 非递归实现思路
- 若二叉树为空,直接返回;
- 初始化栈,将根节点压入栈中;
- 循环处理栈,直到栈空:
- 弹出栈顶节点,立即访问该节点(根节点处理);
- 先将右孩子 压入栈,再将左孩子压入栈(栈是后进先出,保证左孩子先出栈被访问);
- 循环结束,完成前序遍历。
2.3 完整代码实现
cpp
// 非递归前序遍历
void preOrderNonRecursive(BiTree T) {
if (T == NULL) return;
Stack s;
initStack(&s);
push(&s, T); // 根节点入栈
while (!isEmpty(&s)) {
BiTree p;
pop(&s, &p); // 弹出栈顶节点
printf("%c ", p->data); // 访问根节点
// 先压右,后压左,保证左子树先遍历
if (p->rchild) {
push(&s, p->rchild);
}
if (p->lchild) {
push(&s, p->lchild);
}
}
}
2.4 遍历结果

基于示例序列构建的二叉树,前序非递归遍历结果为:A B D H K E C F I G J。
三、非递归中序遍历
3.1 遍历规则
中序遍历的核心规则是:左子树 → 根节点 → 右子树,即先遍历左子树至最底层,再访问根节点,最后遍历右子树。
3.2 非递归实现思路
中序遍历无法像前序那样直接入栈根节点,需要先遍历左子树,核心思路为:
- 初始化栈,定义节点指针
p指向根节点; - 循环处理,条件为
p不为空 或 栈不为空:- 若
p不为空,将p压入栈,p指向其左孩子(一直向左走,遍历左子树); - 若
p为空,弹出栈顶节点,访问 该节点(左子树遍历完成,处理根节点),然后p指向其右孩子(遍历右子树);
- 若
- 循环结束,完成中序遍历。
3.3 完整代码实现
cpp
// 非递归中序遍历
void inOrderNonRecursive(BiTree T) {
Stack s;
initStack(&s);
BiTree p = T;
while (p != NULL || !isEmpty(&s)) {
// 遍历左子树,依次入栈
while (p != NULL) {
push(&s, p);
p = p->lchild;
}
// 左子树遍历完成,处理根节点和右子树
if (!isEmpty(&s)) {
pop(&s, &p);
printf("%c ", p->data); // 访问根节点
p = p->rchild; // 遍历右子树
}
}
}
3.4 遍历结果

基于示例序列构建的二叉树,中序非递归遍历结果为:H K D B E A I F C G J。
四、非递归后序遍历
4.1 遍历规则
后序遍历的核心规则是:左子树 → 右子树 → 根节点 ,是三种遍历中最复杂的,因为根节点需要在左右子树都遍历完成后才能访问,本文采用双栈法实现,逻辑清晰易理解。
4.2 双栈法实现思路
利用两个栈s1(主栈)和s2(辅助栈),通过反转遍历顺序实现后序,核心思路:
- 若二叉树为空,直接返回;
- 初始化两个栈,将根节点压入
s1; - 循环处理
s1,直到s1为空:- 弹出
s1栈顶节点,将其压入s2(辅助栈暂存); - 先将左孩子 压入
s1,再将右孩子 压入s1(使s1弹出顺序为根→右→左,s2存储顺序为左→右→根的反序);
- 弹出
- 循环处理
s2,依次弹出节点并访问,即为左→右→根的后序遍历结果。
4.3 完整代码实现
cpp
// 非递归后序遍历(双栈法)
void postOrderNonRecursive(BiTree T) {
if (T == NULL) return;
Stack s1, s2;
initStack(&s1);
initStack(&s2);
push(&s1, T);
BiTree p;
// 主栈s1处理,节点按根→右→左压入辅助栈s2
while (!isEmpty(&s1)) {
pop(&s1, &p);
push(&s2, p);
// 先压左,后压右,保证s1弹出右→左
if (p->lchild) {
push(&s1, p->lchild);
}
if (p->rchild) {
push(&s1, p->rchild);
}
}
// 弹出s2,得到后序遍历结果
while (!isEmpty(&s2)) {
pop(&s2, &p);
printf("%c ", p->data);
}
}
4.4 遍历结果

基于示例序列构建的二叉树,后序非递归遍历结果为:K H D E B I F J G C A。
五、主函数测试
将二叉树构建和三种非递归遍历整合到主函数,直接运行即可输出遍历结果:
cpp
int main(int argc, char const *argv[])
{
BiTree T;
createTree(&T); // 构建二叉树
printf("非递归前序遍历:");
preOrderNonRecursive(T);
printf("\n非递归中序遍历:");
inOrderNonRecursive(T);
printf("\n非递归后序遍历:");
postOrderNonRecursive(T);
printf("\n");
return 0;
}
完整代码如下
cpp
#include <stdio.h>
#include <stdlib.h>
// 栈最大容量,根据二叉树大小调整
#define MAXSIZE 100
// 定义二叉树节点的数据类型(这里用字符型,方便演示)
typedef char ElemType;
// ==================== 1. 二叉树节点结构体定义 ====================
typedef struct TreeNode {
ElemType data; // 节点数据域
struct TreeNode *lchild; // 左孩子指针
struct TreeNode *rchild; // 右孩子指针
} TreeNode;
// 给二叉树指针起别名,简化代码
typedef TreeNode* BiTree;
// ==================== 2. 栈结构体定义(用于非递归遍历) ====================
typedef struct {
BiTree data[MAXSIZE]; // 栈存储二叉树节点指针
int top; // 栈顶指针,-1表示空栈
} Stack;
// ==================== 3. 栈的基础操作函数 ====================
// 初始化栈
void initStack(Stack *s) {
s->top = -1;
}
// 判断栈是否为空,空返回1,非空返回0
int isEmpty(Stack *s) {
return s->top == -1;
}
// 入栈操作:将节点p压入栈s
int push(Stack *s, BiTree p) {
// 栈满判断
if (s->top >= MAXSIZE - 1) return 0;
// 栈顶指针+1,存入节点
s->data[++(s->top)] = p;
return 1;
}
// 出栈操作:将栈顶节点弹出,存入p
int pop(Stack *s, BiTree *p) {
// 栈空判断
if (isEmpty(s)) return 0;
// 取出栈顶节点,栈顶指针-1
*p = s->data[(s->top)--];
return 1;
}
// ==================== 4. 二叉树创建(前序遍历序列创建,#表示空节点) ====================
// 测试用二叉树序列:ABDH#K###E##CFI###G#J##
char str[] = "ABDH#K###E##CFI###G#J##";
int idx = 0; // 全局索引,遍历序列字符串
// 前序递归创建二叉树
void createTree(BiTree *T) {
// 读取当前字符
ElemType ch = str[idx++];
// #代表空节点
if (ch == '#') {
*T = NULL;
} else {
// 分配节点内存
*T = (BiTree)malloc(sizeof(TreeNode));
// 赋值节点数据
(*T)->data = ch;
// 递归创建左子树
createTree(&(*T)->lchild);
// 递归创建右子树
createTree(&(*T)->rchild);
}
}
// ==================== 5. 非递归前序遍历 ====================
// 规则:根 -> 左 -> 右
void preOrderNonRecursive(BiTree T) {
// 二叉树为空,直接返回
if (T == NULL) return;
Stack s;
// 初始化栈
initStack(&s);
// 根节点先入栈
push(&s, T);
// 栈不为空,循环遍历
while (!isEmpty(&s)) {
BiTree p;
// 弹出栈顶节点
pop(&s, &p);
// 【访问节点】前序:先访问根
printf("%c ", p->data);
// 栈是后进先出,所以先压右孩子,再压左孩子
// 保证出栈时左孩子先被访问
if (p->rchild) {
push(&s, p->rchild);
}
if (p->lchild) {
push(&s, p->lchild);
}
}
}
// ==================== 6. 非递归中序遍历 ====================
// 规则:左 -> 根 -> 右
void inOrderNonRecursive(BiTree T) {
Stack s;
initStack(&s);
// 遍历指针p,初始指向根节点
BiTree p = T;
// 循环条件:p不为空 或 栈不为空
while (p != NULL || !isEmpty(&s)) {
// 1. 一直向左走,把所有左节点入栈,直到左节点为空
while (p != NULL) {
push(&s, p);
p = p->lchild;
}
// 2. 左节点为空,弹出栈顶(根节点)并访问
if (!isEmpty(&s)) {
pop(&s, &p);
// 【访问节点】中序:左走完访问根
printf("%c ", p->data);
// 3. 访问右子树
p = p->rchild;
}
}
}
// ==================== 7. 非递归后序遍历(双栈法,最易理解) ====================
// 规则:左 -> 右 -> 根
// 双栈法思路:s1存节点,s2存逆序后序序列,最后弹出s2即为后序
void postOrderNonRecursive(BiTree T) {
if (T == NULL) return;
Stack s1, s2;
initStack(&s1);
initStack(&s2);
// 根节点入s1
push(&s1, T);
BiTree p;
// 处理s1,将节点按 根->右->左 压入s2
while (!isEmpty(&s1)) {
pop(&s1, &p);
push(&s2, p);
// 先压左,后压右,保证s1弹出顺序为 根->右->左
if (p->lchild) {
push(&s1, p->lchild);
}
if (p->rchild) {
push(&s1, p->rchild);
}
}
// 弹出s2所有节点,就是后序遍历结果
while (!isEmpty(&s2)) {
pop(&s2, &p);
// 【访问节点】后序:左、右都走完访问根
printf("%c ", p->data);
}
}
// ==================== 8. 主函数:测试三种遍历 ====================
int main() {
BiTree T;
// 1. 创建二叉树
createTree(&T);
// 2. 测试非递归前序遍历
printf("非递归前序遍历结果:");
preOrderNonRecursive(T);
printf("\n");
// 3. 测试非递归中序遍历
printf("非递归中序遍历结果:");
inOrderNonRecursive(T);
printf("\n");
// 4. 测试非递归后序遍历
printf("非递归后序遍历结果:");
postOrderNonRecursive(T);
printf("\n");
return 0;
}
运行截图如下

六、核心总结
- 前序遍历 :根先出,利用栈后进先出,先压右、后压左,实现根→左→右的访问顺序;
- 中序遍历 :先遍历左子树至最底层,左空出根访问,再处理右子树,循环条件需同时判断节点和栈;
- 后序遍历(双栈法) :通过两个栈反转顺序,s1 实现根→右→左 ,s2 暂存后弹出即为左→右→根;
- 非递归遍历的核心是手动模拟递归的栈帧过程,通过栈暂存未处理的节点,控制遍历顺序。
三种非递归遍历的时间复杂度均为O(n) (每个节点入栈、出栈、访问各一次),空间复杂度为O(n) (最坏情况,二叉树退化为链表,栈存储所有节点),在实际开发中,非递归遍历更适合处理深度较大的二叉树 ,避免递归的栈溢出问题。