关于二叉树的大多数算法题目都是与二叉树的遍历方式有关的,大都解决方案对二叉树的遍历改写即可。下面就主要介绍下二叉树的常用的四种遍历方式。
其中DFS(Depth-First-Search)的有三种分别为先序遍历,中序遍历及后序遍历,而使用BFS的只有层序遍历。
一.先序遍历
先序遍历的顺序为对于当前结点先访问其本身,再访问其左子树,最后访问其右子树。
递归实现由于过于简单就只给代码不加描述了,
public static void recurrentSelution(TreeNode root) {
if (root == null) {
return;
}
System.out.print(root.val + " ");
recurrentSelution(root.left);
recurrentSelution(root.right);
}
非递归实现大体思路:
需要借助一个堆栈,首先将头结点入栈。
若堆栈不为空:
从堆栈弹出一个元素进行访问;
若其右孩子存在,则将右孩子入栈;
若其左孩子存在,则将左孩子入栈。
如此即可完成非递归的先序遍历。
代码如下:
public static void nonRecurrentSelution(TreeNode root) {
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()) {
TreeNode current = stack.pop();
System.out.print(current.val + " ");
if (current.right != null) {
stack.push(current.right);
}
if (current.left != null) {
stack.push(current.left);
}
}
}
二.中序遍历
中序遍历的顺序为对于每一个结点先访问左子树,在访问当前结点,在访问右子树。对于二叉搜索树而言中序遍历的结果即为排序输入的结果。
递归实现代码:
public static void inOrderRecurr(TreeNode root) {
if (root == null) {
return ;
}
inOrderRecurr(root.left);
System.out.print(root.val + " ");
inOrderRecurr(root.right);
}
非递归实现:
首先申请一个堆栈,并令当前结点等于根结点。
若堆栈不为空或当前结点不为空时:
将当前结点及其左孩子以及左孩子的左孩子....依次入栈,直到左孩子为空为止;
从堆栈中弹出一个元素并访问它;
令当前元素等于上一步弹出元素的右孩子。
如此即可完成先序遍历的非递归实现
实现代码如下:
public static void nonInOrderRecurr(TreeNode root) {
Stack<TreeNode> stack = new Stack<>();
TreeNode current = root;
while (current != null || !stack.isEmpty()) {
while (current != null) {
stack.push(current);
current = stack.peek().left;
}
current = stack.pop();
System.out.print(current.val + " ");
current = current.right;
}
}
三.后序遍历
后序遍历的顺序为先访问当前结点的左子树,再访问当前节点的右子树,最后访问当前结点。
递归实现代码如下:
public static void postOrderRecurr(TreeNode root) {
if (root == null) {
return ;
}
postOrderRecurr(root.left);
postOrderRecurr(root.right);
System.out.print(root.val + " ");
}
非递归实现:
细心地小伙伴可能已经发现了,后序遍历的逆序跟先序遍历类似,后序遍历为左,右,中,而先序遍历为中,左,右因此可以对先序遍历的代码进行稍微的改写完成中,右,左的遍历顺序,然后再将该方式逆序即可。
在先序的非递归遍历中,我们是先让右孩子入栈,再将左孩子入栈的;将其改写为先让左孩子入栈,再让右孩子入栈。同时访问的时候将其先存到另一堆栈,最后将存入新堆栈的元素依次倒入访问即可。
实现代码如下:
public static void postOrderNonRecurr(TreeNode root) {
Stack<TreeNode> stack1 = new Stack<>();
Stack<TreeNode> stack2 = new Stack<>();
stack1.push(root);
while (!stack1.isEmpty()) {
TreeNode current = stack1.pop();
stack2.push(current);
if (current.left != null) {
stack1.push(current.left);
}
if(current.right != null) {
stack1.push(current.right);
}
}
while(!stack2.isEmpty()) {
System.out.print(stack2.pop().val + " ");
}
}
四.层序遍历
层序遍历,顾名思义一层一层的遍历啊。从上至下,从左至右依次遍历。
层序遍历的非递归实现:
首先创建一个队列,并将头结点入队
若队列不为空时:
从队列中弹出一个元素,并访问他;
若其左孩子存在则将左孩子入队;
若其右孩子存在则将右孩子入队。
如此即可完成对于队列的层序遍历。
实现代码如下:
public static void levelOrderTraversal(TreeNode root) {
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
while (!queue.isEmpty()) {
TreeNode current = queue.remove();
if (current.left != null) {
queue.add(current.left);
}
if (current.right != null) {
queue.add(current.right);
}
System.out.print(current.val + " ");
}
}
对于层序遍历更进一步的要求是在遍历过程中需要输出换行,使得同一层元素保持的同一行。
对于该问题使用双指针的方案解决。
初始化时定义另个结点,currentLast和nextLast,分别维持当前层的最后一个结点及下一层的最后一个结点。currentLast = root,nextLast = null。
在访问结点的时候若其等于currentLast时,输出换行,currentLast = nextLast
在每一个结点入队时,将nextLast指向入队结点。这样就可以保证在访问到当前层最后一个结点时,此时的nextLast指向的是下一层的最后一个结点。
实现代码如下:
public static void levelOrderTraversal(TreeNode root) {
Queue<TreeNode> queue = new LinkedList<>();
TreeNode last = root;
TreeNode nLast = root.right == null ? root.left : root.right;
queue.add(root);
while (!queue.isEmpty()) {
TreeNode current = queue.remove();
if (current.left != null) {
queue.add(current.left);
nLast = current.left;
}
if (current.right != null) {
queue.add(current.right);
nLast = current.right;
}
System.out.print(current.val + " ");
if (current == last) {
System.out.println();
last = nLast;
}
}
}