目录
二叉树的储存结构(定义)
struct TreeNode{
int val; //节点储存数据(可以是整数,字符,字符串)
TreeNode *left; //指向左节点的指针
TreeNode *right; //指向右节点的指针
TreeNode(int x):val(x),left(NULL),right(NULL){}; //构造函数
};
(一)前序遍历
(1)递归版本
思路:
若二叉树为空,什么都不做,否则:
- 先访问根节点
- 再前序遍历左子树
- 最后前序遍历右子树
算法实现:
void PreOrder(TreeNode T){
if(T!=NULL){
visit(T); //访问结点
PreOrder(T->lelt); //遍历节点左子树
PreOrder(T->right); //遍历节点右子树
}
}
void visit(TreeNode T){
print("树节点的值:%d",T->data);
}
理解递归可以参照栈的定义
(2)非递归版本
前序遍历的非递归算法,就是将上面递归函数隐式调用栈的过程给显示表示出来,即利用一个辅助栈,来进行访问结点并入栈遍历左子树,结点出栈遍历右子树。
- 首先检查根节点是否为空
- 使用一个stack栈来辅助遍历。初始时,将根节点压入栈中
- 当左节点不为空时,压入栈中
- 当节点为空而栈不为空时,栈顶元素出栈,遍历栈顶元素右子树
- 节点为空并且栈为空,遍历结束
实现代码
void PreOrder(TreeNode T){
SqStack S; //申请一个辅助栈
InitStack(&S); //初始化
TreeNode p=T; //p为遍历指针
while(p||!IsEmpty(S)) //当p不为空或栈不为空时循环
{
if(p){ //一路向左
visit(p); //访问当前节点并入栈
Push(&S,p);
p=p->left; //左孩子不为空,一直向左走
}else //出栈并转向栈节点的右子树
{
Pop(&S,p); //栈顶元素出栈
p=p->right; //向右子树走,p赋值为当前节点的右节点
}
} //返回while循环继续if-else语句
}
(二)中序遍历
(1)递归版本
思路:
二叉树为空,什么也不做,否则:
- 首先遍历左子树
- 然后访问根节点(通常是打印节点的值或执行某种操作)
- 最后遍历右子树
实现代码
void InOrder(TreeNode T){
if(T!=NULL) //当树不为空时
{
InOrder(T->left); //遍历左子树
visit(T); //访问根节点
InOder(T->right); //遍历右子树
}
}
void visit(TreeNode T)
{
printf("根节点的值:%d\n",T->data);
}
(2)非递归版本
思路:
- 二叉树为空,啥也不做
- 节点不为空,入栈,遍历左子树
- 节点为空但栈不为空,出栈,遍历出栈元素的右子树
- 节点为空且栈也为空,遍历结束
算法实现
void InOrder(TreeNode T)
{
SqStack S; //申请一个栈
InitStack(&S); //初始化
TreeNode p=T; //p为遍历指针
while(p||!IsEmpty(S)) //当p不为空或栈不为空时循环
{if(p) //一路向左
{Push(&S,p); //当前节点入栈
p=p->left; //左孩子不为空,一直向左走
}else //出栈,并转向当前节点的右子树
{
Pop(&S,p); //顶栈元素出栈
visit(p); //访问出栈节点
p=p->right; //右孩子不为空,一直向右走
}
} //返回while循环执行if-else语句
(三)后序遍历
(1)递归版本
思路:
若二叉树为空,什么也不用左,否则:
- 后序遍历左子树
- 后序遍历右子树
- 访问根节点
实现代码
void PostOrder(TreeNode T){
if(T!=NULL) //当二叉树不为空时
{T=T->left; //遍历节点左子树
T=T->right; //遍历节点右子树
visit(T); //访问节点
}
}
void visit(T){
printf("当前节点的值为:%c",T->data);
}
(2)非递归版本
后序遍历非递归算法最需要解决的问题就是——上面的两次出栈问题(即判别哪次出栈是访问右子树,哪次出栈是访问自身),这是其利用辅助栈需要解决的事,而解决此事也有很多方法,这里介绍标志法,即设立一个标志来判别出栈。
思路
- 当二叉树为空时,什么都不用做
- 当节点不为空时,入栈并设立标志tag=0,遍历当前节点的左子树
- 当节点为空时,需判断栈是否为空,为空则遍历结束;不为空则有以下两种情况:
- 当tag=0时(说明栈顶元素还没有遍历右子树),则重新标志tag=1(此时还在栈中),并遍历右子树
- 当tag=1时(说明栈顶元素左右子树都已遍历),出栈访问栈顶元素
实现代码
struct stack{
TreeNode t; //指向二叉树的遍历指针
int tag=0; //设置标志
} //tag=0时,只有左子树都已遍历;tag=1时,左右子树都已遍历
void PostOrder(TreeNode T)
{
struct stack s[Maxsize]; //定义一个stack类型的数组作为栈空间,大小为maxsize
int top=-1; //初始化一个栈顶指针
while(T!=NULL||top>=0) //当二叉树不为空或栈不为空时,执行循环
{
s[++top]->t=T; //将节点压入栈中
s[++top]->tag=0; //设置tag=0,表明右子树还未遍历
T=T->left; //沿左子树一路向下
}
while(top!=-1&&s[top]->tag==1) //当无法继续向左遍历时(即T=NULL),且当前节点的左右子树都已遍历
{
visit(s[top--]->t); //弹出栈顶元素
if(top!=-1)
{
s[top]->tag=1; //设置tag=1,表面开始访问右子树
T=s[top].t->right; //将 T 更新为栈顶节点的右子节点
} //继续向下遍历右子树
}
}
(四)总结
二叉树的前序、中序和后序遍历是三种基本的二叉树遍历方法,每种方法访问节点的顺序不同。下面是对这三种遍历方法的总结:
前序遍历(Preorder Traversal)
前序遍历的顺序是:根节点 -> 左子树 -> 右子树。
通常用于打印结构化的输出,如打印目录结构。
中序遍历(Inorder Traversal)
中序遍历的顺序是:左子树 -> 根节点 -> 右子树。
对于二叉搜索树,中序遍历可以按顺序访问所有节点。
后序遍历(Postorder Traversal)
后序遍历的顺序是:左子树 -> 右子树 -> 根节点。
常用于删除二叉树或者释放资源,因为在释放节点前已经处理了子节点。
递归与非递归
递归方法简单直观,但可能会导致栈溢出(尤其是对于深度很大的二叉树)。非递归方法使用栈来模拟递归过程,避免了栈溢出的风险,但代码相对复杂。
有错误请大家指出呀~
有帮助请点赞收藏呀~