二叉树的构建、递归和非递归版本的三种遍历、层序遍历

目录

二叉树

先序遍历(非递归)

中序遍历(非递归)

后序遍历(非递归)

层序遍历


二叉树

 

代码实现

  • 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

 

先序遍历(非递归)

思路比较简单:

  1. 设置一个指针指向根节点。当该指针p不为空时,打印该节点的值,并令指针指向该节点的左孩子。
  2. 如果左子节点为空,则从栈中获取栈顶元素,将栈顶元素弹出,令指针指向该元素的右子节点。

原则就是入栈前打印该节点的值。每次遍历过的非空节点存入栈中,方便查找。

对比递归版和非递归版,递归版的基本思路就是通过一根指针指向当前节点,

  • 如果节点为空,则退回,指向上一次遍历指向节点的右子节点。
  • 如果节点不为空,则打印该节点,并优先对左子节点进行遍历。

非递归版就是对递归版的模仿。

//前序遍历  根左右
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);
			}
		}
	}
}

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值