目录
二叉树
代码实现
- 1、根据层序遍历结果构建二叉树(百度实习面试考过)
- 2、二叉树的三种遍历(百度实习一面考过)
递归
#include<iostream>
#include<vector>
#include<queue>
using namespace std;
//定义树的基本框架
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};
//构造二叉树
TreeNode* CreatTree(vector<int>& num) {
int length = num.size();
//如果传入容器为空,则跳出函数,无法构建二叉树
if (length == 0) return nullptr;
//赋值的索引位置
int index = 0;
//构建一个二叉树
TreeNode *root = new TreeNode(num[index++]);
//构建二叉树时所用的工具
TreeNode *p = nullptr;
queue<TreeNode*>trans;
trans.push(root);
//当队列不为空,此时还有数字没插入树中
while (!trans.empty() && index < length) {
p = trans.front();
trans.pop();
if (index < length && num[index] != -1) {
TreeNode *LeftNode = new TreeNode(num[index]);
p->left = LeftNode;
trans.push(LeftNode);
}
index++;
if (index < length&&num[index] != -1) {
TreeNode *RightNode = new TreeNode(num[index]);
p->right = RightNode;
trans.push(RightNode);
}
index++;
}
return root;
}
//前序遍历 根左右
void Preorder(TreeNode *root){
//根为空,则跳出函数
if (!root)
return;
//输出根的值
cout << root->val << " ";
//递归获取左子树的值
Preorder(root->left);
//递归获取右子树的值
Preorder(root->right);
}
//中序遍历 左根右
void Midorder(TreeNode *root){
//根为空,则跳出函数
if (!root)
return;
//递归获取左子树的值
Midorder(root->left);
cout << root->val << " ";
Midorder(root->right);
}
//后序遍历 左右根
void Postorder(TreeNode *root){
if (!root)
return;
Postorder(root->left);
Postorder(root->right);
cout << root->val << " ";
}
int main(){
vector<int> num = { 3,5,1,6,2,0,8,-1,-1,7,4,-1,-1,-1,-1 };
TreeNode *tree = CreatTree(num);
//前序遍历
Preorder(tree);
//中序遍历
Midorder(tree);
//后序遍历
Postorder(tree);
return 0;
}
非递归遍历的代码,这个讲得不错:https://blog.csdn.net/zhangxiangDavaid/article/details/37115355
先序遍历(非递归)
思路比较简单:
- 设置一个指针指向根节点。当该指针p不为空时,打印该节点的值,并令指针指向该节点的左孩子。
- 如果左子节点为空,则从栈中获取栈顶元素,将栈顶元素弹出,令指针指向该元素的右子节点。
原则就是入栈前打印该节点的值。每次遍历过的非空节点存入栈中,方便查找。
对比递归版和非递归版,递归版的基本思路就是通过一根指针指向当前节点,
- 如果节点为空,则退回,指向上一次遍历指向节点的右子节点。
- 如果节点不为空,则打印该节点,并优先对左子节点进行遍历。
非递归版就是对递归版的模仿。
//前序遍历 根左右
void Preorder(TreeNode *root){
//根为空,则跳出函数
if (!root)
return;
//输出根的值
cout << root->val << " ";
//递归获取左子树的值
Preorder(root->left);
//递归获取右子树的值
Preorder(root->right);
}
void preorder(TreeNode *root){
if(root==nullptr) return;
TreeNode*p=root;
stack<TreeNode*>st;
while(st.size() || p){
//边遍历边打印,并存入栈中
while(p){
cout<<p->val<<" ";
st.push(p);
p=p->left;
}
//p为空时,说明左子树遍历完毕,该遍历右子树了
if(st.size()){
p=st.top();
st.pop();
p=p->right;
}
}
cout<<endl;
}
中序遍历(非递归)
对比递归版代码和非递归代码:
递归版使用一根指针指向当前节点。若指针不为空,则继续向下遍历。遍历过程优先遍历左子节点。若当前节点的左子节点为空,则打印当前节点,并开始对节点的右子树进行探索。
非递归也是使用一根指针指向当前节点,优先遍历左子树。当指针指向节点为空时,说明上一次遍历的节点的左子节点为空,此时从栈中获取上一次遍历的节点,打印该节点,并将指针指向该节点的右子树。整个遍历过程中,栈顶元素用于存放上一次遍历过的非空节点。
//中序遍历 左根右
void Midorder(TreeNode *root){
//根为空,则跳出函数
if (!root)
return;
//递归获取左子树的值
Midorder(root->left);
cout << root->val << " ";
Midorder(root->right);
}
void midorder(TreeNode *root){
if(root==nullptr) return;
stack<TreeNode *>st;
TreeNode *p=root;
while(st.size() || p){
while(p){
st.push(p);
p=p->left;
}
//说明当前节点 左子节点为空,从栈中取出该节点进行打印
if(st.size()){
p=st.top();
st.pop();
cout<<p->val<<" ";
p=p->right;
}
}
cout<<endl;
}
后序遍历(非递归)
对比递归版和费递归版:
递归版先遍历左子树,然后遍历右子树。
- 若当前节点右子树为空;
- 或者右子树节点刚刚打印过
打印当前节点。
为什么是上面两个条件满足其一就可以打印当前节点了?因为右子树遍历结束,递归函数回退,才能打印当前节点。而触发右子树遍历结束的原因只有两个
- 要么右子树节点为空,递归回退到当前节点,此时可以对当前节点进行打印;
- 或者右子树已经打印过了,此时递归也会回退到当前节点
同理,根据递归代码,我们可以写出非递归版面。非递归版需要记录上一次打印的节点,因为下面判断条件需要用到。详细原因见下文。
非递归代码先遍历左子树,直到当前节点指向的是这棵树最左子节点时,才会开始遍历右子节点(通利于递归版)。因此,先用一个while循环一边遍历左子节点一边将节点压入栈中。
之后遍历右子节点。在遍历的过程中,从栈中获取栈顶元素,并将栈顶元素弹出。如果该元素的右子树为空或者其右子节点刚刚打印过(对比递归版的两个条件),则说明该节点可以被打印,打印之后,记得同步记录被打印的节点。
如果该节点不符合被打印的条件,则将该节点压回栈中,探索该节点的右子树。在探索右子树时,也是优先探索左子节点,因此用一个while循环不断向左子树探索,探索过程中记得压栈操作。
实现代码如下:
//后序遍历
void Postorder(TreeNode *root){
if (!root)
return;
Postorder(root->left);
Postorder(root->right);
cout << root->val << " ";
}
void postorder(TreeNode *root){
if(root==nullptr) return;
stack<TreeNode*>st;
TreeNode* p=root;
//pre始终记录上一个被打印的节点
TreeNode* pre=nullptr;
//先遍历到该树的最左子节点,遍历过程中将节点入栈
while(p){
st.push(p);
p=p->left;
}
while(st.size()){
p=st.top();
st.pop();
//当前节点的右子节点为空,或者其右子节点刚刚打印过
if(p->right==nullptr || p->right==pre){
cout<<p->val<<" ";
pre=p;
}else{
//此时说明p指向的节点不能打印,则将该节点入栈
st.push(p);
p=p->right;
//遍历右子树,优先遍历右子树的左子节点
while(p){
st.push(p);
p=p->left;
}
}
}
}
层序遍历
层序遍历的主要思路就是先把根节点存入,然后输出,输出的同时把根节点的左右孩子存入,再把左孩子输出,同样输出的同时把左孩子的孩子在存入,直到遍历完成,例如:
a
b c
d e f g
先把a存入,然后输出a,输出a的同时把b c存入,然后再输出b,输出b的同时存入d e,继续输出c,然后存入f g,直到全部输出
void level(TreeNode * root){
if(root==nullptr) return;
queue<TreeNode*>q;
q.push(root);
TreeNode*p=root;
while(q.size()){
//获取当前层的节点个数
int size=q.size();
while(size--){
p=q.front();
q.pop();
//打印当前节点的值
cout<<p->val<<" ";
//将当前节点的左右子节点压入队列中
if(p->left){
q.push(p->left);
}
if(p->right){
q.push(p->right);
}
}
}
}