前几天面试美团的java后台岗位,第一题就是手写二叉树非递归先序遍历,当时我就不乐意了。然后其实能想出来的,但是没私底下实现过,还真没把握给面试官,最后挂了。所以痛定思痛,我们来手写一下二叉树的非递归先序,中序和后序遍历,并对其中的逻辑部分进行步骤的讲解,我觉得一切的东西你只要给他赋予一定的规则,那么它就没那么难了(后面的部分不适合对数据结构不熟悉的同学)
/**
* 非递归先序遍历
* @param root
*/
public void depthOrderTraversal(BinTree root){
if(root==null){
System.out.println("null");
return;
}
ArrayDeque<BinTree> stack=new ArrayDeque<BinTree>();
stack.push(root);
while(stack.isEmpty()==false){
BinTree node = stack.pop();
System.out.print(node.getData()+" ");
if(node.rChild!=null)
stack.push(node.rChild);
if(node.lChild!=null)
stack.push(node.lChild);
}
}
非递归先序遍历需要借用栈(后进先出)这种数据结构,下面我梳理一下这个过程,分步骤ABC
A:将根节点传入进方法中,也就是当作参数传递过来,你可以在节点类中写一个getRoot()方法,返回他的节点对象
B:判断节点是否为空,为空就返回return;(什么都不用返回)。因为return;这样这个方法就结束了;((●'◡'●))
接下来的C-D我们讲解一下从栈到实现先序遍历的整个过程的逻辑梳理
C:创建一个栈,并将根节点率先加入到栈中(这里为什么用ArrayDeque来创建栈呢?因为网上说用做栈的时候性能比Stack好)
D:这里我们思考一个问题,到底用什么来做循环的条件,首先我们现在有两个东西在手里,一个栈,一个根节点数据,能改变的貌似也只有栈的size()了,所以我们通过判断栈是否为空来循环我们的先序遍历的逻辑部分
E-G我们就用三句话来简短的叙述一下过程
E:栈顶出栈并输出
F:“出栈”右孩子入栈(E中出栈的那个元素称为出栈)
G:”出栈“左孩子入栈
然后E-F整个过程包括在while循环当中,当栈中元素全部出栈的时候循环结束。
接下来我们讲一下二叉树非递归中序遍历
/**
* 非递归中序遍历
* @param root
*/
public void depthMidTraversal(BinTree root) {
Stack<BinTree> s = new Stack<BinTree>();
BinTree p = root;
while(p != null || !s.empty()) {
while (p != null) {
s.push(p);
p = p.lChild;
}
p = s.pop();
System.out.print(p.getData()+" ");
if (p.rChild != null) {
p = p.rChild;
}
else p = null;
}
}
下面我还是通过A-F的步骤讲解一下非递归中序遍历的过程
A:首先创建一个栈s (BinTree p = root 方便后序编写,我没试过直接用root来写行不行,我感觉应该按道理,逻辑来说应该不行)
B:确定循环条件,p节点不为空或者栈不为空
C-F:中间这个步骤我统一说一下,其实我支持大家通过Dubug来看一下这个过程,我想了想要用语言阐述太为难我这个理科生了,中间逻辑大家看代码吧。
非递归后序遍历
/**
* 非递归后序遍历
* @param root
*/
public void depthAfterTraversal(BinTree root){
Stack<BinTree> s = new Stack<BinTree>();
BinTree p = root;
while (p != null || !s.empty()) {
while(p != null) {
s.push(p);
p = p.lChild;
}
p = s.pop();
System.out.print(p.getData()+" ");
//这里需要判断一下,当前p是否为栈顶的左子树,如果是的话那么还需要先访问右子树才能访问根节点
//如果已经是不是左子树的话,那么说明左右子书都已经访问完毕,可以访问根节点了,所以讲p复制为NULL
//取根节点
if (!s.empty() && p == s.peek().lChild) {
p = s.peek().rChild;
}
else p = null;
}
}
我把全部代码粘贴供大家参考,其中也有递归实现先序,中序,后序的方法,大家加油
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
import java.util.Vector;
public class BinTree {
private BinTree lChild;//左孩子
private BinTree rChild;//右孩子
private BinTree root;//根节点
private Object data; //数据域
private List<BinTree> datas;//存储所有的节点
private Vector<Vector<Integer>> vec;
private Vector<Integer> son;
public BinTree(BinTree lChild, BinTree rChild, Object data) {
super();
this.lChild = lChild;
this.rChild = rChild;
this.data = data;
}
public BinTree(Object data) {
this(null, null, data);
}
public BinTree() {
super();
}
public void createTree(Object[] objs){
datas=new ArrayList<BinTree>();
for (Object object : objs) {
datas.add(new BinTree(object));
}
root=datas.get(0);//将第一个作为根节点
for (int i = 0; i < objs.length/2; i++) {
datas.get(i).lChild=datas.get(i*2+1);
if(i*2+2<datas.size()){//避免偶数的时候 下标越界
datas.get(i).rChild=datas.get(i*2+2);
}
}
}
//先序遍历
public void preorder(BinTree root){
if(root!=null){
visit(root.getData());
preorder(root.lChild);
preorder(root.rChild);
}
}
//中序遍历
public void inorder(BinTree root){
if(root!=null){
inorder(root.lChild);
visit(root.getData());
inorder(root.rChild);
}
}
//后序遍历
public void afterorder(BinTree root){
if(root!=null){
afterorder(root.lChild);
afterorder(root.rChild);
visit(root.getData());
}
}
/**
* 非递归先序遍历
* @param root
*/
public void depthOrderTraversal(BinTree root){
if(root==null){
System.out.println("null");
return;
}
ArrayDeque<BinTree> stack=new ArrayDeque<BinTree>();
stack.push(root);
while(stack.isEmpty()==false){
BinTree node = stack.pop();
System.out.print(node.getData()+" ");
if(node.rChild!=null)
stack.push(node.rChild);
if(node.lChild!=null)
stack.push(node.lChild);
}
}
/**
* 非递归中序遍历
* @param root
*/
public void depthMidTraversal(BinTree root) {
Stack<BinTree> s = new Stack<BinTree>();
BinTree p = root;
while(p != null || !s.empty()) {
while (p != null) {
s.push(p);
p = p.lChild;
}
p = s.pop();
System.out.print(p.getData()+" ");
if (p.rChild != null) {
p = p.rChild;
}
else p = null;
}
}
/**
* 非递归后序遍历
* @param root
*/
public void depthAfterTraversal(BinTree root){
Stack<BinTree> s = new Stack<BinTree>();
BinTree p = root;
while (p != null || !s.empty()) {
while(p != null) {
s.push(p);
p = p.lChild;
}
p = s.pop();
System.out.print(p.getData()+" ");
//这里需要判断一下,当前p是否为栈顶的左子树,如果是的话那么还需要先访问右子树才能访问根节点
//如果已经是不是左子树的话,那么说明左右子书都已经访问完毕,可以访问根节点了,所以讲p复制为NULL
//取根节点
if (!s.empty() && p == s.peek().lChild) {
p = s.peek().rChild;
}
else p = null;
}
}
//层序遍历
// public void levelOrder(BinTree root) {
// if(root!=null){
//
// Queue<Pair<BinTree,Integer>> q = new LinkedList<>();
// Map<BinTree,Integer> record = new HashMap<>();
// record.put(root, 0);
// q.offer(record);
// while(!q.isEmpty()){
// int level = q.peek().values();
//
// }
// }
//
//
// }
private void visit(Object obj) {
System.out.print(obj+" ");
}
public Object getData() {
return data;
}
public BinTree getRoot() {
return root;
}
}
下面是调用这个类的main方法,大家加油,用Dubug看一下这个运行过程,比我用语言阐述要强的多
public class TestTree {
public static void main(String[] args) {
BinTree binTree=new BinTree();
Object[] objs={0,1,2,3,4,5,6,7};
binTree.createTree(objs);
// binTree.preorder(binTree.getRoot()); 先序遍历
// binTree.inorder(binTree.getRoot()); 中序遍历
// binTree.afterorder(binTree.getRoot()); //后序遍历
// binTree.depthOrderTraversal(binTree.getRoot()); //非递归先序遍历
// binTree.depthMidTraversal(binTree.getRoot());//非递归中序遍历
binTree.depthAfterTraversal(binTree.getRoot());
}
}
觉得比较实在的点个关注,一起加油努力学习