三种递归遍历
如图为一个简单的二叉树:
前序
它的遍历顺序大致如上图,因为他其实每一步都在重复先打印当前节点,在遍历他的左孩子节点,最后遍历右孩子节点,所以完全可以用帝归来实现,中序后序根本上和前序一样,只是顺序不同而已
前序遍历结果为:
public static void preorder(TreeNode root){
if (root==null)
return;
System.out.printf("%c ",root.val);
preorder(root.left);
preorder(root.right);
}
中序
中序遍历结果为:
public static void inorder(TreeNode root){
if (root==null)
return;
inorder(root.left);
System.out.printf("%c ",root.val);
inorder(root.right);
}
后序
后序遍历结果为:
public static void postorder(TreeNode root){
if (root==null)
return;
postorder(root.left);
postorder(root.right);
System.out.printf("%c ",root.val);
}
层序遍历(队列)
相对比于递归遍历和非递归遍历,层序遍历就比较简单好想了,就是按照二叉树的层级第一层,第二层一直到最后一层,将节点依次打印。由于考虑 节点顺序和层序,所以需要借助队列,大致思想为:
1、首先将二叉树的根节点push到队列中,判断队列不为NULL,就输出队头的元素,
2、判断节点如果有孩子,就将孩子push到队列中,
3、遍历过的节点出队列,
4、循环以上操作,直到Tree == NULL。
public static void levelOrder(TreeNode root) {
if (root == null)
return ;
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
while (!queue.isEmpty()) {
TreeNode node = queue.remove();
System.out.printf("%c ",node.val);
if (node.left != null) {
queue.add(node.left);
}
if (node.right != null) {
queue.add(node.right);
}
}
}
结果如下:
三种非递归遍历
非递归遍历肯定是需要用到栈的,前序和中序相比较后序会简单一点,但共同之处是都会用到回溯的思想
前序
原理上来说,就是先将父节点入栈且打印,在判断是否存在左节点,存在则继续上述过程,不存在则回溯到上一步的父节点,判断它的右节点是否存在,存在则继续上述过程,不存在再次回溯,直到栈空。
public static void preorder(TreeNode root) {
TreeNode cur = root;
Stack<TreeNode> stack = new Stack<>();
while (cur != null || !stack.isEmpty()) {
while (cur != null) {
System.out.printf("%c ",cur.val);
stack.push(cur);
cur = cur.left;
}
TreeNode pop = stack.pop();
cur = pop.right;
}
}
中序
先将父节点入栈但不打印,判断左节点是否存在,存在则继续上述过程,不存在则回溯并打印当前节点,继续判断右节点是否存在,存在则继续 上述过程,不存在回溯到上一步,直到栈空。
public static void inorder(TreeNode root) {
TreeNode cur = root;
Stack<TreeNode> stack = new Stack<>();
while (cur != null || !stack.isEmpty()) {
while (cur != null) {
stack.push(cur);
cur = cur.left;
}
TreeNode pop = stack.pop();
System.out.printf("%c ",cur.val);
cur = pop.right;
}
}
后序
后序遍历整体与前中序遍历过程相似,相比较而言麻烦的问题是对于父节点的打印,需要在其右子树遍历完成的前提下进行。所以不能像前中序遍历一样,在遍历完左子树后,就直接出栈。我们需要利用这个未出栈的栈顶元素去获取右子树,在遍历完右子树后,就可以出栈,并对此节点进行打印,所以我们需要添加一个标记做判断以区分是从左子树取栈还是从右子树出栈。
public static void postorder(TreeNode root) {
TreeNode cur = root;
TreeNode last = null; // 记录上次刚刚后序遍历过的结点
Stack<TreeNode> stack = new Stack<>();
while (cur != null || !stack.isEmpty()) {
while (cur != null) {
stack.push(cur);
cur = cur.left;
}
TreeNode pop = stack.peek();
if (pop.right == null) {
stack.pop();
System.out.printf("%c ",pop.val);
last = pop;
} else if (pop.right == last) {
stack.pop();
System.out.printf("%c ",pop.val);
last = pop;
} else {
cur = pop.right;
}
}
}
总结
二叉树的几种遍历代表了不同的思考问题的思路(递归,栈,队列),各有优劣,递归简便但执行效率低(重复调用太频繁,浪费空间和时间),非递归较递归而言略复杂不是很好思考,效率较高,而且更重要的是在这个过程中,我们更多地掌握了新的存储形式,以便以后拓宽更多的思考问题的思路。