二叉树的遍历
遍历的方式
先序遍历
先序遍历:又称先根遍历,从根节点开始,按照“根-左-右”的顺序依次访问各节点。即先访问根节点,然后递归地前序遍历左子树,最后递归地前序遍历右子树。
void preo(struct TreeNode* root) {
if (root == NULL) return; // 空节点不遍历
printf("%d ", root->val); // 访问根节点
preo(root->left); // 遍历左子树
preo(root->right); // 遍历右子树
}
中序遍历:
中序遍历:又称中根遍历,从根节点开始,按照“左-根-右”的顺序依次访问各节点。即先递归地中序遍历左子树,然后访问根节点,最后递归地中序遍历右子树。
void inor(struct TreeNode* root) {
if (root == NULL) return; // 空节点不遍历
inor(root->left); // 遍历左子树
printf("%d ", root->val); // 访问根节点
inor(root->right); // 遍历右子树
}
后序遍历:
后序遍历:又称后根遍历,从根节点开始,按照“左-右-根”的顺序依次访问各节点。即先递归地后序遍历左子树,然后递归地后序遍历右子树,最后访问根节点。
void post(struct TreeNode* root) {
if (root == NULL) return; // 空节点不遍历
post(root->left); // 遍历左子树
post(root->right); // 遍历右子树
printf("%d ", root->val); // 访问根节点
}
层序遍历:
层序遍历:是一种按照树的层级结构逐层访问节点的方式。
其具体步骤如下:
- 从树的根节点开始。
- 将根节点放入队列中。
- 循环执行以下步骤,直到队列为空:
- 从队列中取出一个节点。
- 访问该节点。
- 如果该节点有左子节点,将左子节点放入队列。
- 如果该节点有右子节点,将右子节点放入队列。
#include <bits/stdc++.h>
using namespace std;
struct TreeNode {
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
void level(TreeNode* root) {
if (root == NULL) {
return;
}
queue<TreeNode*> q;
q.push(root); // 将根节点入队
while (!q.empty()) {
TreeNode* tempNode = q.front(); // 出队,获取队首元素
cout << tempNode->val << " "; // 打印当前节点的数据
q.pop(); // 出队后从队列中移除该节点
if (tempNode->left != NULL) { // 如果左子节点存在,则入队左子节点
q.push(tempNode->left);
}
if (tempNode->right != NULL) { // 如果右子节点存在,则入队右子节点
q.push(tempNode->right);
}
}
}
遍历的使用
先序+中序
例题:P1827 [USACO3.4] 美国血统 American Heritage - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
题目描述
农夫约翰非常认真地对待他的奶牛们的血统。然而他不是一个真正优秀的记帐员。他把他的奶牛 们的家谱作成二叉树,并且把二叉树以更线性的“树的中序遍历”和“树的前序遍历”的符号加以记录而 不是用图形的方法。
你的任务是在被给予奶牛家谱的“树中序遍历”和“树前序遍历”的符号后,创建奶牛家谱的“树的 后序遍历”的符号。每一头奶牛的姓名被译为一个唯一的字母。(你可能已经知道你可以在知道树的两 种遍历以后可以经常地重建这棵树。)显然,这里的树不会有多于 $26$ 个的顶点。
这是在样例输入和样例输出中的树的图形表达方式:
C / \ / \ B G / \ / A D H / \ E F
附注:
- 树的中序遍历是按照左子树,根,右子树的顺序访问节点;
- 树的前序遍历是按照根,左子树,右子树的顺序访问节点;
- 树的后序遍历是按照左子树,右子树,根的顺序访问节点。输入格式
第一行一个字符串,表示该树的中序遍历。
第二行一个字符串,表示该树的前序遍历。
输出格式
单独的一行表示该树的后序遍历。
样例输入
ABEDFCHG
CBADEFGH样例输出
AEFDBHGC
思路:
利用先序遍历确定根节点,再拆分中序遍历,并记录左右长度,如果左右长度不为0,则继续拆分;递归地调用out
函数处理左子树和右子树;在递归返回后,输出根节点。
代码:
#include<bits/stdc++.h>
using namespace std;
void out(string in,string pr){
char root=pr[0]; //先序遍历最先遍历根节点,所以用一个字符记录根节点
int l,r;
l=in.find(root); //在中序遍历中找出根节点
r=in.size()-l-1; //并将中序遍历拆分左右,用两个数记录左右字符长度
//如果左右字符长度不为 0,那么就继续拆分
if(l){
string s1_l=in.substr(0,l),s2_l=pr.substr(1,l); //拆分
out(s1_l,s2_l);
}
if(r){
string s1_r=in.substr(l+1,r),s2_r=pr.substr(l+1,r); //拆分
out(s1_r,s2_r);
}
printf("%c",root); //题目要求输出后序遍历
}
int main(){
string s1,s2;
cin>>s1>>s2; //输入中序和先序遍历,注意顺序,先输入中序,再输入先序
out(s1,s2);
return 0;
}
中序+后序
例题:P1030 [NOIP2001 普及组] 求先序排列 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
题目描述
给出一棵二叉树的中序与后序排列。求出它的先序排列。(约定树结点用不同的大写字母表示,且二叉树的节点个数)。
输入格式
共两行,均为大写字母组成的字符串,表示一棵二叉树的中序与后序排列。
输出格式
共一行一个字符串,表示一棵二叉树的先序。
样例输入
BADC
BDCA
样例输出
ABCD
思路: 同美国血统差不多
代码:
#include<bits/stdc++.h>
using namespace std;
void pre(string in,string out){
char root=out[out.size()-1]; //后序遍历的最后一个字符,即根节点
int l,r; //记录中序遍历的左子树和右子树的长度
l=in.find(root); //找出根节点在中序遍历中的位置
r=in.size()-l-1; //计算右子树的长度
printf("%c",root); //题目要求输出先序遍历
if(l!=0){
string s1_l=in.substr(0,l),s2_l=out.substr(0,l); //如果左子树或右子树的长度不为0(即存在非空子树),则递归拆分
pre(s1_l,s2_l); //递归地调用pre函数处理左子树和右子树
}
if(r!=0){
string s1_r=in.substr(l+1,r),s2_r=out.substr(l,r);
pre(s1_r,s2_r);
}
}
int main(){
string s1,s2;
cin>>s1>>s2; //需要注意的是,输入的顺序是先中序遍历,再后序遍历
pre(s1,s2);
return 0;
}
先序遍历和后序遍历,先序遍历和层序遍历,后序遍历和层序遍历,均不能确定唯一二叉树。