前言:二叉树有三种遍历:前序,中序,后序.
针对这三种遍历方式,又有算法:递归遍历,迭代遍历.
一.递归遍历
这是代码比较简洁的遍历,但是又不容易理解.不过遇到题目直接按照以下三步骤来:
-
确定递归函数的参数和返回值: 确定哪些参数是递归的过程中需要处理的,以及返回值是什么.
-
确定终止条件: 写完了递归算法, 运行的时候,经常会遇到栈溢出的错误,就是没写终止条件或者终止条件写的不对,操作系统也是用一个栈的结构来保存每一层递归的信息,如果递归没有终止,操作系统的内存栈必然就会溢出。
-
确定单层递归的逻辑: 确定每一层递归需要处理的信息。在这里也就会重复调用自己来实现递归的过程。(摘编自代码随想录)
其实又想了一下递归,简单理解就是,本质还是函数的相互调用,执行return其实就是返回上一个调用该函数的函数,然后继续往下执行的过程.一直到根节点执行完毕,该程序也执行完毕.
如果还不好理解的话,可以借助二叉树的图,把每一个节点当做一个函数,该节点会调用函数,执行相应的return操作等.
前序,中序,后序的区别就在于遍历的顺序,所以代码都是差不多的,这里就以其中一个为例:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
//上面就是定义二叉树的节点的结构
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> result=new LinkedList<Integer>();
traversal(root,result);
}
return result;
}
public void traversal(TreeNode root,List<Integer> list){
if(root==null)
return;
list.add(root.val);
traversal(root.left,list);
traversal(root.right,list);
}
}
这个是前序遍历的.如果是其他遍历,只需要交换traversal函数里面的最后三行顺序即可.不知道怎么交换的话,可以都试试.
二.迭代遍历
这对我来说是个新东西,因为之前没怎么学.
1.前序遍历:
节点定义和上面是一样的,这里就没写了.
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> result=new LinkedList<Integer>();
if(root==null)
return result;
Stack<TreeNode> stack=new Stack<>();
//先将根节点压入
stack.push(root);
while(!stack.isEmpty()){
TreeNode node=stack.pop();
result.add(node.val);
if(node.right!=null){
stack.push(node.right);
}
if(node.left!=null){
stack.push(node.left);
}
}
return result;
}
}
由于栈是"先进后出"的,因此在循环中要先将右节点压入栈.这样才能先让左节点先出栈.
2.中序遍历
中序遍历不能只改一下前序的代码.因为前序遍历时,我们访问的节点顺序和要遍历的数据顺序是一致的,而中序是"左,根,右"的顺序,这和我们代码访问节点的顺序不一致,因此要调整一下.
List<Integer> result=new LinkedList<>();//存放结果用
Stack<TreeNode> stack=new Stack<>();//新建一个栈用来存结点
if(root==null)
return result;//如果是一个空树就直接返回
TreeNode cur=root;
while(cur!=null||!stack.isEmpty()){
if(cur!=null){
stack.push(cur);
cur=cur.left;
}
else{
cur=stack.pop();
result.add(cur.val);
cur=cur.right;
}
}
return result;
这段代码的巧妙之处就在于:到了叶子结点的时候,它的左子树为空,就让该节点出栈,并将数值加入结果中,并让cur指向右.而右边也为空,那再让元素出栈,此时出栈的就是它的父节点.这就实现了"指针上移",然后又添加父节点的右子树(恰好刚刚没有添加).
针对循环条件来说,因为cur为空时,程序并没有结束,我们希望它继续,所以加一个"栈不为空".
当cur指向根节点时,栈为空,但是它还有右子树.cur不为空,所以还能继续执行.
3.后序遍历
待更新....