一、二叉树遍历原理
二叉树的遍历是指从根结点出发,按照某种次序访问二叉树中所有结点,使得每个结点被访问一次且仅被访问一次。
二、二叉树结点定义
cpp
/* 二叉链表有效结点 */
typedef int ElemType;
typedef struct BTNode
{
ElemType data; //数据域
struct BTNode* leftchild; //左孩子指针域
struct BTNode* rightchild; //右孩子指针域
}BTNode;
三、二叉树遍历方法
(一)前序遍历(根---左---右)
1.遍历规则
若二叉树为空,则空操作返回,否则先访问根结点,然后前序遍历左子树,再前序遍历右子树。
2.遍历算法及代码实现
(1)递归版
cpp
void preOrder(BTNode* root)
{
if (root == NULL) return;
//处理根结点
printf("%c ", root->data);
//递归处理左子树
preOrder(root->leftchild);
//递归处理右子树
preOrder(root->rightchild);
}
(2)非递归版(单栈法)
cpp
void preOrder_NoRecursion(BTNode* root)
{
//0.判空,空树直接返回
if (root == NULL) return;
//1.申请一个栈,并将根节点入栈
std::stack<BTNode*> st;
st.push(root);
//2.进入while循环,循环条件:栈不为空
while (!st.empty())
{
//3.取出栈顶结点,并将其值打印处理
BTNode* tmp = st.top();
printf("%c ", tmp->data);
st.pop();
//4.将刚处理的tmp结点的两个孩子按照先右再左的顺序进行判断处理,如果存在则压入栈中
if (tmp->rightchild != NULL)
{
st.push(tmp->rightchild);
}
if (tmp->leftchild != NULL)
{
st.push(tmp->leftchild);
}
}
//5.当while结束,即栈空,遍历结束
}
(二)中序遍历(左---根---右)
1.遍历规则
若二叉树为空,则空操作返回,否则从根结点开始(注意并不是先访问根结点),中序遍历根结点的左子树,然后是访问根结点,最后中序遍历右子树。

2.遍历算法及代码实现
(1)递归版
cpp
void inOrder(BTNode* root)
{
if (root == NULL) return;
//递归处理左子树
inOrder(root->leftchild);
//处理根结点
printf("%c ", root->data);
//递归处理右子树
inOrder(root->rightchild);
}
(2)非递归版(单栈法)
cpp
void inOrder_NoRecursion(BTNode* root)
{
//0.判空,空树直接返回
if (root == NULL) return;
//1.申请栈存储节点,tag标记节点是否为第一次遇见
bool tag = true;
std::stack<BTNode*> st;
st.push(root);
//2.栈不为空持续循环遍历
while (!st.empty())
{
//3.首次遇见节点且存在左子树,持续向左入栈,捋完所有左分支
while (tag && st.top()->leftchild != NULL)
{
st.push(st.top()->leftchild);
}
//4.左子树处理完毕,访问当前根节点、出栈
BTNode* tmp = st.top();
printf("%c ", tmp->data);
st.pop();
//5.判断并处理右子树,更新标记位状态
if (tmp->rightchild != NULL)
{
//有右孩子:右节点入栈,标记为首次访问
st.push(tmp->rightchild);
tag = true;
}
else
{
//无右孩子:当前节点分支遍历完毕,后续节点为回退二次访问
tag = false;
}
}
//6.栈空,整棵树中序遍历结束
}
(三)后续遍历(左---右---根)
1.遍历规则
若二叉树为空,则空操作返回,否则从左到右先叶子后结点的方式遍历访问左右子树,最后是访问根结点。

2.遍历算法及代码实现
(1)递归版
cpp
void postOrder(BTNode* root)
{
if (root == NULL) return;
//递归处理左子树
postOrder(root->leftchild);
//递归处理右子树
postOrder(root->rightchild);
//处理根结点
printf("%c ", root->data);
}
(2)非递归版
法一:双栈法
cpp
void postOrder_NoRecursion1(BTNode* root)
{
//0.判空,空树直接返回
if (root == NULL) return;
//1.申请两个栈:S1用于遍历结点,S2用于逆序存储后序结果
std::stack<BTNode*> S1;
std::stack<BTNode*> S2;
//2.根结点先入遍历栈S1
S1.push(root);
//3.S1不为空,持续遍历所有结点
while (!S1.empty())
{
//4.S1栈顶结点出栈,存入结果栈S2(暂不打印)
BTNode* tmp = S1.top();
S2.push(tmp);
S1.pop();
//5.先左、后右入S1,保证后续S2出栈顺序为:左---右---根
if (tmp->leftchild != NULL) S1.push(tmp->leftchild);
if (tmp->rightchild != NULL) S1.push(tmp->rightchild);
}
//6.S1遍历完毕,S2中结点逆序输出即为后序遍历结果
while (!S2.empty())
{
printf("%c ", S2.top()->data);
S2.pop();
}
}
法二:单栈法
cpp
void postOrder_NoRecursion2(BTNode* root)
{
//0.判空,空树直接返回
if (root == NULL) return;
///1.申请一个栈,额外申请bool tag,再额外申请BTNode *preNode
std::stack<BTNode*> st;
st.push(root);
bool tag = true; //标记是否首次访问节点
BTNode* preNode = NULL; //记录上一个已访问的节点
//3.进入while循环,循环条件是栈不空即可
while (!st.empty())
{
//4.1 tag为true:栈顶是新节点,优先遍历左子树,捋完所有左分支
while (tag == true && st.top()->leftchild != NULL)
{
st.push(st.top()->leftchild);
tag = false;
}
//4.2 tag为false:栈顶是回溯老节点,左子树已处理完毕,准备处理右子树
//5.判定右子树是否未被处理
//右孩子存在且不是上一个访问节点 说明右子树未遍历
if (st.top()->rightchild != NULL && st.top()->rightchild != preNode)
{
st.push(st.top()->rightchild);
tag = true;
}
//5.2 右子树为空 / 右子树已处理完毕:左右处理完成,访问根节点
else
{
//6.处理根节点:出栈打印,更新标记位与前驱结点
BTNode* tmp = st.top();
printf("%c ", tmp->data);
st.pop();
tag = false;
preNode = tmp;
}
}
//7.栈空,后序遍历结束
}
(四)层序遍历
1.普通
(1)遍历规则
若树为空,则空操作返回,否则从树的第一层,也就是根结点开始访问,从上而下逐层遍历,在同一层中,按从左到右的顺序对结点逐个访问。

(2)遍历算法及代码实现
cpp
void Level_Traverse(BTNode* root)
{
//0.判空,空树直接返回
if (root == NULL) return;
//1.申请队列,根结点入队
std::queue<BTNode*> q;
q.push(root);
//2.队列不为空,持续循环遍历
while (!q.empty())
{
//3.取出队头结点、访问数据、出队
BTNode* tmp = q.front();
printf("%c ", tmp->data);
q.pop();
//先左后右,子结点存在则入队,保证层序从左到右
if (tmp->leftchild != NULL)
{
q.push(tmp->leftchild);
}
if (tmp->rightchild != NULL)
{
q.push(tmp->rightchild);
}
}
//4.队列为空,遍历完成
}
2.正S
(1)遍历规则
若树为空,则空操作返回,否则从树的第一层,也就是根结点开始访问,从上而下逐层遍历,在奇数层中,按从左到右的顺序对结点逐个访问,在偶数层中,按从右到左的顺序对结点逐个访问。

(2)遍历算法及代码实现
cpp
void S_Level_Traverse(BTNode* root)
{
//0.判空,空树直接返回
if (root == NULL) return;
//1.根结点(第一层/奇数层)入栈S1
std::stack<BTNode*> s1, s2;
s1.push(root);
//2.两个栈任意一个非空,持续遍历
while (!s1.empty() || !s2.empty())
{
//3.处理奇数层:S1非空、S2为空,从左向右打印
while (!s1.empty())
{
BTNode* tmp = s1.top();
printf("%c ", tmp->data);
s1.pop();
//先右后左入栈,保证下一层(偶数层)正序输出
if (tmp->rightchild != NULL)
{
s2.push(tmp->rightchild);
}
if (tmp->leftchild != NULL)
{
s2.push(tmp->leftchild);
}
}
//4.处理偶数层:S2非空、S1为空,从右向左打印
while (!s2.empty())
{
BTNode* tmp = s2.top();
printf("%c ", tmp->data);
s2.pop();
//先左后右入栈,保证下一层(奇数层)逆序输出
if (tmp->leftchild != NULL)
{
s1.push(tmp->leftchild);
}
if (tmp->rightchild != NULL)
{
s1.push(tmp->rightchild);
}
}
}
}
3.倒S
(1)遍历规则
若树为空,则空操作返回,否则从树的第一层,也就是根结点开始访问,从上而下逐层遍历,在奇数层中,按从右到左的顺序对结点逐个访问,在偶数层中,按从左到右的顺序对结点逐个访问。

(2)遍历算法及代码实现
cpp
void S_Level_Traverse(BTNode* root)
{
//0.判空,空树直接返回
if (root == NULL) return;
//1.根结点(第一层/奇数层)入栈S1
std::stack<BTNode*> s1, s2;
s1.push(root);
//2.两个栈任意一个非空,持续遍历
while (!s1.empty() || !s2.empty())
{
//3.处理奇数层:S1非空、S2为空,从右向左打印
while (!s1.empty())
{
BTNode* tmp = s1.top();
printf("%c ", tmp->data);
s1.pop();
//先左后右入栈,保证下一层(偶数层)逆序输出
if (tmp->leftchild != NULL)
{
s2.push(tmp->leftchild);
}
if (tmp->rightchild != NULL)
{
s2.push(tmp->rightchild);
}
}
//4.处理偶数层:S2非空、S1为空,从左向右打印
while (!s2.empty())
{
BTNode* tmp = s2.top();
printf("%c ", tmp->data);
s2.pop();
//先右后左入栈,保证下一层(奇数层)正序输出
if (tmp->rightchild != NULL)
{
s1.push(tmp->rightchild);
}
if (tmp->leftchild != NULL)
{
s1.push(tmp->leftchild);
}
}
}
}
