二叉树的前序、中序、后序、层序、之字形遍历等是数据结构和算法中很基础和重要的一部分。使用递归方法我们可以很直观和方便地写出二叉树的各种遍历,但是递归会咋成一些多余的内存空间的浪费(每次调用函数都会在栈中创建栈帧,保存参数)。使用非递归的方法进行二叉树的遍历稍微有些麻烦,但是相对地节约了内存空间,也是笔试/面试中经常考的,下面我们来看一下各种遍历方法。
二叉树的前序、中序、后序指的是根节点的顺序。前序遍历是先输出根结点,然后依次访问左子树和右子树(每个子树都是这样的顺序)。中序遍历是先访问左子树,输出根节点,然后访问右子树。后序遍历是先访问左子树,再访问右子树,最后输出根节点。
层序遍历中,对二叉树逐层输出。
二叉树的结点
我们定义二叉树的结点如下,每个结点包含一个int型值,左子结点和右子结点的引用。
public class TreeNode {
int value=0;
TreeNode left;
TreeNode right;
}
二叉树的前序遍历
在二叉树的前序遍历中,先输出根节点的值,然后依次访问左子树和右子树,每一个子树都是这样的顺序。
递归
根据这个思路,我们很容易地写出了前序遍历的递归方法
public List<Integer> PreOrderSearch(TreeNode head){
List<Integer> res= new ArrayList<>();
loop(res,head);
return res;
}
private void loop(List<Integer> res,TreeNode node) {
if(node == null)
return ;
res.add(node.value); //输出根节点
loop(res,node.left); //访问左子树
loop(res,node.right); //访问右子树
}
非递归
使用非递归方法来前序遍历二叉树,我们可以使用栈。对于每一个子树,我们都先输出其根节点,然后依次将右子结点和左子结点压缩栈中,不断地弹出结点,输出结点值,然后将右子结点和左子结点压栈,最后完成二叉树的前序遍历。
public List<Integer> PreOrderSearch(TreeNode head) {
List<Integer> res= new ArrayList<>();
if(head == null)
return res;
Stack<TreeNode> stack = new Stack<TreeNode>();
TreeNode cur = head;
stack.push(cur);
while(!stack.isEmpty()) {
cur = stack.pop();
res.add(cur.value); //输出结点值
if(cur.right != null) //将右子结点压栈
stack.push(cur.right);
if(cur.left != null) //将左子结点压栈
stack.push(cur.left);
}
return res;
}
中序遍历
在二叉树的中序遍历中,先访问左子树,再输出节点的值,然后访问右子树,每一个子树都是这样的顺序。
递归
public List<Integer> InOrderSearch(TreeNode head){
List<Integer> res= new ArrayList<>();
loop(res,head);
return res;
}
private void loop(List<Integer> res,TreeNode node) {
if(node == null)
return ;
loop(res,node.left); //先访问左子树
res.add(node.value); //输出结点值
loop(res,node.right); //访问右子树
}
非递归
我们同样可以用栈来实现非递归中序遍历。优点类似深度优先搜索和回溯。原理用语言不容易说清楚,大家仔细看以下代码。
public List<Integer> InOrderSearch(TreeNode head){
List<Integer> res = new ArrayList<Integer>();
if(head == null)
return res;
TreeNode cur = head;
Stack<TreeNode> stack = new Stack<TreeNode>();
while(cur != null || !stack.isEmpty()){
while(cur != null) { //将结点及其左子结点压栈
stack.push(cur);
cur = cur.left;
}
cur = stack.pop(); //从栈中弹出一个结点,输出结点值,如果该结点有右子结点,移动指针cur到右子结点,重新开始压栈操作。
res.add(cur.value);
cur = cur.right;
}
return res;
}
后序遍历
递归
public List<Integer> PostOrderSearch(TreeNode head){
List<Integer> res= new ArrayList<>();
loop(res,head);
return res;
}
private void loop(List<Integer> res,TreeNode node) {
if(node == null)
return ;
loop(res,node.left); //先访问左子树
loop(res,node.right); //然后访问右子树
res.add(node.value); //最后输出根结点
}
非递归
我们依然使用栈来实现,我们还需要维护一个指针pre,指向前一个访问的结点。具体看代码。
public List<Integer> PostOrderSearch(TreeNode root) {
List<Integer> res = new ArrayList<Integer>();
if(root == null)
return res;
Stack<TreeNode> stack = new Stack();
TreeNode cur = root; //指针,指向当前结点
TreeNode pre = null; //指针,指向前一个访问的结点
while(!stack.isEmpty()||cur != null){
while(cur != null){ //首先,将每一个结点和其左子结点压栈
stack.push(cur);
cur = cur.left;
}
pre = null;
while(!stack.isEmpty()){
cur = stack.peek();
if(cur.right == pre || cur.right == null){ //如果当前栈顶元素的右子结点已经访问过或者右子结点为null,输出本结点;否则将当前指针指向右子结点,重新压栈。
cur = stack.pop();
res.add(cur.value);
pre = cur;
cur = null;
}else{
cur = cur.right;
break;
}
}
}
return res;
}
层序遍历
递归
递归实现是对二叉树进行深度优先搜索,然后使用一个int值来保存当前的层数。
public List<List<Integer>> levelOrderSearch(TreeNode root) {
List<List<Integer>>list= new ArrayList<List<Integer>>();
addlevel(list,0,root);
return list;
}
public void addlevel(List<List<Integer>>list,int level,TreeNode node){
if(node==null) return;
if(list.size()-1<level) list.add(new ArrayList<Integer>());
list.get(level).add(node.value);
addlevel(list,level+1,node.left);
addlevel(list,level+1,node.right);
}
非递归
非递归实现中使用队列来对二叉树进行广度优先搜索,并且维护两个int值来保存当前层中结点数和下一层中节点数。
public List<List<Integer>> LevelOrderSearch(TreeNode node){
List<List<Integer>> res = new ArrayList<List<Integer>>();
if(node == null)
return res;
TreeNode cur = null;
int curLevelCount = 1;
int nextLevelCount = 0;
Queue<TreeNode> queue = new LinkedList<TreeNode>();
queue.offer(node);
res.add(new ArrayList<Integer>());
while(!queue.isEmpty()) {
cur = queue.poll();
res.get(res.size()-1).add(cur.value);
curLevelCount--;
if(cur.right != null) {
queue.offer(cur.right);
nextLevelCount++;
}
if(cur.left != null) {
queue.offer(cur.left);
nextLevelCount++;
}
if(curLevelCount <= 0) {
curLevelCount = nextLevelCount;
nextLevelCount = 0;
if(curLevelCount>0)
res.add(new ArrayList<Integer>());
}
}
return res;
}