这一篇来谈一谈树的遍历;
其实树的遍历很简单,但又不简单,简单是因为递归的方法很容易实现,不简单是指它的迭代实现相对来说有一点难度,但是,仔细思考之后还是比较容易的;
树的遍历通常分为四种:
前序遍历(又称为先序遍历), 中序遍历, 后序遍历, 以及层序遍历;
下面先说一下这几个概念(仅限于二叉树,当然可以推广到多叉树):
前序遍历(NLR):
先遍历根节点(N),再遍历左孩子(L), 最后遍历右孩子®;
中序遍历(LNR):
先遍历左孩子(L), 再遍历根节点(N), 最后遍历右孩子®;
后序遍历(LRN):
先遍历左孩子(L), 再遍历右孩子®, 最后遍历根节点(N);
很容易的看出这三种遍历只是根节点的访问顺序改变了, 前中后,分别对应的根节点的前中后;
层序遍历(Level):
顾名思义, 一层一层的遍历树;
对于上图中的这样一棵树, 我们先找一下其四种遍历的结果:
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);
}
- 迭代法:
这里就需要用到一种数据结构——栈;
前序遍历是什么来着?
根, 左, 右;
那么怎么迭代呢???
如图;
它的遍历顺序是这样的, 先顺着左子树遍历,同时每遇到一个节点,就把它打印;
当到达叶节点时,再回到上一个节点,去遍历右子树;
那么,对于入栈与出栈的操作就是,每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->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(先进后出)的性质,先弹出栈的是左子树;
它是这样的:
来来来,大家一起把迭代的过程顺一遍;
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;
}