二叉树是一种树形的数据结构,而树形结构应用非常广泛,例如我们操作系统中的文件系统。那么我们对树形结构的使用大多数都是基于树的遍历的,掌握了二叉树的基本遍历思想,对于其他普通的树进行遍历那就是小case。
下面进入我们今天的正题吧。
对树形结构进行遍历实际上就是找到树中所有结点的一个线性序列。对于二叉树我们一般有4种遍历方法:
- 先(前)序遍历
- 中序遍历
- 后序遍历
- 层序遍历
中序遍历的算法如下:
如果当前访问结点为空,则什么都不做,直接返回
如果不为空,则
- 访问左子树
- 访问当前结点
- 访问右子树
由此可见,二叉树的中序遍历非常适合使用递归。从算法中我们可以看到(实际上从树的定义中也可以看出对树的遍历适合用递归),递归的结束条件就是当前访问结点为空结点。那么我们很容易的给出递归的代码:
public List<Integer> solution1(TreeNode root) {
//如果当前访问结点为空,那么直接返回
if(root == null)
return new ArrayList<Integer>();
List<Integer> res = new ArrayList();
//访问左子树
res.addAll(solution1(root.left));
//访问当前结点
res.add(root.val);
//访问右子树
res.addAll(solution1(root.right));
return res;
}
递归的代码非常容易理解,那么能不能采用非递归的方法呢?答案是可以的。但是采用非递归遍历就有一个难点:假如我们访问完了某个结点的左子树,那么我们该怎么退回到该结点呢?(因为在二叉树的二叉链表结构中是没有指向双亲结点的指针的)这就要求在访问左子树之前,我们得先把该结点保存起来,要不然找不到回来的路就尴尬了。我们用栈作为存储结构保存该结点的祖先,那么就有了非递归的代码:
public List<Integer> solution2(TreeNode root) {
List<Integer> res = new ArrayList();
//初始化一个栈
Stack<TreeNode> s = new Stack();
//先将根结点入栈,栈中的结点的意义是当前返回结点的祖先,
//同时也是没有被访问过的结点
s.push(root);
//栈不空的时候也就是还有结点没有被访问到
while(!s.empty()){
//把当前栈顶的左子树入栈,因为中序遍历时访问的第一个
//结点一定是该树中最左下边的结点
while(s.peek() != null) s.push(s.peek().left);
//空指针出栈
s.pop();
//这里还要进行栈是否为空的判断,因为
//如果在这时对所有结点访问完毕那么栈就空了
if(!s.empty()) {
//访问当前结点
TreeNode p = s.pop();
res.add(p.val);
//该访问右子树了,也是一样的过程,需要找到
//右子树的最左下的结点
s.push(p.right);
}
}
return res;
}
还有一种非递归的方式,思想都是一样的:
public List<Integer> solution3(TreeNode root) {
List<Integer> res = new ArrayList();
Stack<TreeNode> s = new Stack();
TreeNode p = root;
while(p != null || !s.empty()) {
//找到最左下的结点
if(p != null) {
s.push(p);
p = p.left;
}
else{
//访问当前结点
p = s.pop();
res.add(p.val);
//访问右子树
p = p.right;
}
}
return res;
}
ok,以上就是对二叉树进行中序遍历的三种方式,你get到了嘛?