树的遍历——前序,后序,中序的递归与迭代解法,以及层序的队列解法

本文介绍了二叉树的四种遍历方式:前序、中序、后序和层序遍历。对于每种遍历,文章详细阐述了递归和迭代两种实现方法,并通过具体的例子解释了每种方法的思路和操作过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这一篇来谈一谈树的遍历;
其实树的遍历很简单,但又不简单,简单是因为递归的方法很容易实现,不简单是指它的迭代实现相对来说有一点难度,但是,仔细思考之后还是比较容易的;
树的遍历通常分为四种:
前序遍历(又称为先序遍历), 中序遍历, 后序遍历, 以及层序遍历;
下面先说一下这几个概念(仅限于二叉树,当然可以推广到多叉树):
前序遍历(NLR):
先遍历根节点(N),再遍历左孩子(L), 最后遍历右孩子®;
中序遍历(LNR):
先遍历左孩子(L), 再遍历根节点(N), 最后遍历右孩子®;
后序遍历(LRN):
先遍历左孩子(L), 再遍历右孩子®, 最后遍历根节点(N);
很容易的看出这三种遍历只是根节点的访问顺序改变了, 前中后,分别对应的根节点的前中后;
层序遍历(Level):
顾名思义, 一层一层的遍历树;
0对于上图中的这样一棵树, 我们先找一下其四种遍历的结果:
NLR:
1, 2, 4, 5, 6, 3, 6, 9, 7;
LNR:
4, 2, 8, 5, 1, 6, 9, 3, 7;
LRN:
4, 8, 5, 2, 9, 6, 7, 3, 1;
Level:
1, 2, 3, 4, 5, 6, 7, 8, 9;

下面我们尝试着用计算机解决问题:

NLR:

  • 递归法:
    这个思路还是很清晰的;
    根左右嘛!当前节点就是根喽;
    那么,打印根,再去找左子树,最后是右子树;
    下面看一下代码:
//递归之前序;
void NLR(TreeNode *T){
	if(T==NULL) return;
	printf("%d ", T->val);
	NLR(T->left);
	NLR(T->right);
}
  • 迭代法:
    这里就需要用到一种数据结构——栈;
    前序遍历是什么来着?
    根, 左, 右;
    那么怎么迭代呢???
    1
    如图;
    它的遍历顺序是这样的, 先顺着左子树遍历,同时每遇到一个节点,就把它打印;
    当到达叶节点时,再回到上一个节点,去遍历右子树;
    那么,对于入栈与出栈的操作就是,每push到栈中一个新的节点前先打印该节点,入栈之后,找左子树;当到达叶子节点时,就去找右子树同时把当前节点出栈;直到栈为空,算法结束;
//迭代之前序;
void NLR(TreeNode *T){
	if(T==NULL) return;//空树就直接return;
	stack<TreeNode*>sta;
	TreeNode* cur = (struct TreeNode*)malloc(sizeof(TreeNode));
	cur=T;//当前节点就是根;
	while(cur || !sta.empty()){//注意条件, 当前点非空,或者栈非空; 第一个条件是给一开始用的;
		if(cur){//
			printf("%d ", cur->val);
			sta.push(cur);
			cur=cur->left;
		}
		else{//当前点为空, 说明上一个节点为叶节点, 找它的右子树;
			cur=sta.top();
			sta.pop();//注意要把当前节点删掉;
			cur=cur->right;
		}
	}
}

LNR:

  • 递归法:
    什么顺序?
    左, 根, 右;
    还是那句话, 当前节点即为根;
    先找左子树, 再打印当前节点, 最后再去遍历右子树;

代码实现:

//递归之中序;
void LNR(TreeNode* T){
	if(T==NULL) return;
	LNR(T->left);
	printf("%d ", T->val);
	LNR(T->right);
}
  • 迭代法:
    还是要用栈来实现;
    2
    如图;
    上边就是中序的顺序;
    什么时候才能打印当前的节点呢?
    注意我们对于当前节点有三种可以的操作:

    1. 打印;
    2. 找左子树;
    3. 找右子树;

    对这三种顺序排个序就是2->1->3;
    也就是说,对于当前的非空节点,先将其入栈, 然后再去找左子树;
    找不到左子树时,回退到上一个节点, 即栈顶元素, 然后打印,当前元素出栈;
    接着去找右子树;
    直到栈为空, 算法结束;
    代码:

//迭代之中序;
void LNR(TreeNode *T){
	if(T==NULL) return;
	stack<TreeNode*>sta;
	TreeNode *cur=(struct TreeNode*)malloc(sizeof(TreeNode));
	cur=T;
	while(cur || !sta.empty()){
		if(cur){//非空节点先找左子树;
			sta.push(cur);
			cur=cur->left;
		}
		else{//空节点返回父亲, 打印之后, 再找右子树, 同时栈顶元素出栈;
			cur=sta.top();
			sta.pop();
			printf("%d ", cur->val);
			cur=cur->right;
		}
	}
}

LRN:

  • 递归法:
    L, R, N, 就是找完左右子树之后再打印当前节点呗;
//递归之后序;
void LRN(TreeNode *T){
	if(T==NULL) return;
	LRN(T->left);
	LRN(T->right);
	printf("%d ", T->val);
}
  • 迭代法
    后序的迭代实现有两种方法;
    一是一个栈实现, 二是两个栈实现;
    先说一个栈的操作:
    后序是不是只有遇到叶节点时才会打印当前节点?
    是!!!
    记住这句话,后序遍历时只有遇到叶子结点才打印,打印过之后,要将该节点删除;
    所以对于拿到手的当前节点先找它的右子树,再找左子树,一直入栈, 同时,要将该节点重置为叶节点, 即将左右子树置为空;
    直到遇到叶节点,打印,再返回父亲;
    细心地同学也许注意到了,先找的右子树,后找的左子树;但是LRN是先左再右呐!!!怎么回事?注意找的节点后,并没有打印,而是先入栈;在弹出栈时才打印;根据栈FILO(先进后出)的性质,先弹出栈的是左子树;
    它是这样的:
    3
    来来来,大家一起把迭代的过程顺一遍;
    1入栈;
    3入栈, 2入栈;
    5入栈, 4入栈;
    4为叶子, 打印, 出栈;
    8入栈, 叶子, 打印, 出栈;
    5变为叶子(8已经打印, 被删除了), 打印,出栈;
    2, 叶子, 打印, 出栈;
    7, 入栈, 6入栈;
    9入栈, 叶子, 打印 ,出栈;
    6, 叶子, 打印, 出栈;
    7, 叶子, 打印, 出栈;
    3, 叶子, 打印, 出栈;
    1, 叶子, 打印,出栈;
    空栈;
    算法结束;
//迭代之后序——一个栈的实现;
void LRN(TreeNode *T){
	if(T==NULL) return;
	stack<TreeNode*>sta;
	sta.push(T);
	TreeNode *cur=(struct TreeNode*)malloc(sizeof(TreeNode));
	while(!sta.empty()){
		cur=sta.top();//取出栈顶元素;
		if(cur->left==NULL && cur->right==NULL){//打印叶子节点;
			printf("%d ", cur->val);
			sta.pop();//打印后的节点出栈;
		}
		else{
			if(cur->right){//先右子树进栈;
				sta.push(cur->right);
				cur->right=NULL;
			}
			if(cur->left){//后左子树进栈;
				sta.push(cur->left);
				cur->left=NULL;
			}
		}
	}
} 

那么两个栈怎样运用呢?
其实原理是一样的, 只不过两个栈很好的保护了树的完整性, 借助于第二个栈, 将打印顺序逆序存储;还有一点不同是:先找左子树,再找右子树;
因为,要倒腾到辅助栈中,所以顺序要颠倒一下;

//迭代之后序——两个栈的巧妙结合;
void LRN(TreeNode *T){
	if(T==NULL) return;
	stack<TreeNode*>sta1, sta2;
	sta1.push(T);
	TreeNode *cur=(struct TreeNode*)malloc(sizeof(TreeNode));
	while(!sta1.empty()){
		cur=sta1.top();
		sta2.push(cur);//栈顶进入sta2;
		if(cur->left) sta1.push(cur->left);//先左后右;
		if(cur->right) sta1.push(cur->right);
	}
	while(!sta2.empty()){
		cur=sta2.top();
		sta2.pop();
		printf("%d ", cur->val);
	}
}

Level:
层序就很简单了, 借助队列, 一层层的遍历就OK了;

//层序——队列实现;
void level(TreeNode *T){
	if(T==NULL) return;
	queue<TreeNode*> que;
	que.push(T);
	TreeNode *cur=(struct TreeNode*)malloc(sizeof(TreeNode));
	while(!que.empty()){
		cur=que.front();
		que.pop();
		printf("%d ", cur->val);
		if(cur->left) que.push(cur->left);
		if(cur->right) que.push(cur->right);
	}
}

下面是完整代码, 方便大家测试;
建的是二叉排序树;

#include<bits/stdc++.h>
using namespace std;
struct TreeNode{
	int val;
	struct TreeNode *left, *right;
};
TreeNode* buildTree(TreeNode *root, int val){
	TreeNode *T = (struct TreeNode*)malloc(sizeof(TreeNode));
	if(root==NULL){
		T->val=val;
		T->left=NULL;
		T->right=NULL;
		return T;
	}
	T=root;
	if(val<=root->val) T->left=buildTree(root->left, val);
	else T->right=buildTree(root->right, val);
	return T;
} 

//递归遍历; 
//前序; 
void prePrint(TreeNode *root){
	if(root==NULL) return;
	printf("%d ", root->val);
	prePrint(root->left);
	prePrint(root->right);
}
//中序; 
void midPrint(TreeNode *root){
	if(root==NULL) return;
	midPrint(root->left);
	printf("%d ", root->val);
	midPrint(root->right);
}
//后序; 
void sufPrint(TreeNode *root){
	if(root==NULL) return;
	sufPrint(root->left);
	sufPrint(root->right);
	printf("%d ", root->val);
}

//迭代遍历;
//前序;
void __prePrint(TreeNode *T){
	if(T==NULL) return;
	stack<TreeNode*> sta;
	TreeNode *cur=(struct TreeNode*)malloc(sizeof(TreeNode));
	cur=T;
	while(cur || !sta.empty()){
		if(cur){
			printf("%d ", cur->val);
			sta.push(cur);
			cur=cur->left;
		}
		else{
			cur=sta.top();
			sta.pop();
			cur=cur->right;
		}
	}
} 
//中序;
void __midPrint(TreeNode *T){
	if(T==NULL) return;
	stack<TreeNode*>sta;
	TreeNode *cur=(struct TreeNode*)malloc(sizeof(TreeNode));
	cur=T;
	while(cur || !sta.empty()){
		if(cur){
			sta.push(cur);
			cur=cur->left;
		}
		else{
			cur=sta.top();
			sta.pop();
			printf("%d ", cur->val);
			cur=cur->right;
		}
	} 
} 
//后序之一, 两个栈的巧妙运用;
void __sufPrint_1(TreeNode *T){
	stack<TreeNode*> sta1, sta2;
	TreeNode *cur=(struct TreeNode*)malloc(sizeof(TreeNode));
	sta1.push(T);
	while(!sta1.empty()){
		cur=sta1.top();
		sta1.pop();
		sta2.push(cur);
		if(cur->left) sta1.push(cur->left);
		if(cur->right) sta1.push(cur->right);
	}
	while(!sta2.empty()){
		cur=sta2.top();
		printf("%d ", cur->val);
		sta2.pop();
	}
}
//后序之二: 一个栈实现; 会破坏树的原本结构; 
void __sufPrint_2(TreeNode* T){
	if(T==NULL) return;
	stack<TreeNode*> sta;
	TreeNode* cur=(struct TreeNode*)malloc(sizeof(TreeNode));
	cur=T;
	sta.push(cur);
	while(!sta.empty()){
		cur=sta.top();
		if(cur->left==NULL && cur->right==NULL){
			printf("%d ", cur->val);
			sta.pop();
		}
		else{
			if(cur->right!=NULL){
				sta.push(cur->right);
				cur->right=NULL;
			}
			if(cur->left!=NULL){
				sta.push(cur->left);
				cur->left=NULL;
			}
		}
	}
} 

 
//层序遍历;
void levelPrint(TreeNode *T){
	if(T==NULL) return;
	queue<TreeNode*> que;
	TreeNode *cur=(struct TreeNode*)malloc(sizeof(TreeNode));
	que.push(T);
	while(!que.empty()){
		cur=que.front();
		que.pop();
		printf("%d ", cur->val);
		if(cur->left!=NULL) que.push(cur->left);
		if(cur->right!=NULL) que.push(cur->right);
	}
} 


int main(){
	TreeNode *T = (struct TreeNode*)malloc(sizeof(TreeNode));
	T=NULL;
	int n;
	scanf("%d", &n);
	while(n--){
		int x;
		scanf("%d", &x);
		T=buildTree(T, x);
	}
	puts("level:");
	levelPrint(T);
	puts("");
	puts("pre:");
	prePrint(T);
	puts("");
	puts("迭代:");
	__prePrint(T);
	puts("");
	puts("mid:");
	midPrint(T);
	puts("");
	puts("迭代:");
	__midPrint(T);
	puts("");
	puts("suf:");
	sufPrint(T);
	puts("");
	puts("迭代_1:");
	__sufPrint_1(T);
	puts("");
	puts("迭代_2");
	__sufPrint_2(T);
	puts("");
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值