今天我们来学习二叉树的中序遍历算法。
二叉树有多种遍历方法,前中序遍历和层次遍历。我们今天的主角是中序遍历,它的遍历顺序为:
1. 左子树
2. 根节点
3. 右子树
如下图所示:
我们知道树的定义本身就是递归式的,左子树就是一棵以根节点的左孩子为根节点的树,右子树也同理,所以遍历左子树或者右子树直接是把原来的根节点换成根节点的左孩子或者右孩子即可。这样我们可以很快地写出递归的中序遍历算法。
import java.util.ArrayDeque;import java.util.ArrayList;import java.util.Collections;import java.util.Deque;import java.util.List;/** * 中序遍历递归实现 * @param root 根节点 * @return 按照中序遍历顺序得到的节点元素列表 */public static List recursiveInorderTraversal(TreeNode root) { if (root == null) { // 根节点为null,返回空列表 return Collections.emptyList(); } List elems = new ArrayList<>(); recursiveInorderTraversalHelper(root, elems); return elems;}private static void recursiveInorderTraversalHelper(TreeNode root, List elems) { if (root == null) { // 根节点为null,不用添加任何元素 return; } // 遍历左子树 recursiveInorderTraversalHelper(root.left, elems); // 遍历根节点,在这里我们的遍历操作为添加元素到一个列表中 elems.add(root.val); // 遍历右子树 recursiveInorderTraversalHelper(root.right, elems);}
这里我们需要用到一个工具类TreeNode来表示二叉树节点,里面有一个方法,可以将一个int数组拼装成一个二叉树,并返回根节点,因为今天主要是学习中序遍历算法,不对其进行详细讲述。这里贴下代码:
import java.util.ArrayDeque;import java.util.ArrayList;import java.util.Arrays;import java.util.Deque;import java.util.List;import java.util.Objects;/** * created at 2020/06/24 17:19:35 * * @author Yohohaha */public class TreeNode { public int val; public TreeNode left; public TreeNode right; public TreeNode(int val) { this.val = val; } public static void main(String[] args) { TreeNode root = convertArray2Tree(new Integer[]{1, 23, null, 1, 34, 35}); System.out.println(root); } @Override public String toString() { return Arrays.toString(convertTree2Array(this)); } /** * 将根节点所表示的二叉树转为int数组 * @param root root node * @return int数组 */ public static Integer[] convertTree2Array(TreeNode root) { if (root == null) { return null; } if (root.left == null && root.right == null) { return new Integer[]{root.val}; } Deque queue = new ArrayDeque<>(); List list = new ArrayList<>(); TreeNode none = new TreeNode(0); while (root != null) { // 根节点不为null表示有节点需要处理,为null表示处理结束 if (root == none) { // 如果为NONE节点,表示节点为null // 因为java的队列实现不支持保存null // 所以这里使用一个特殊对象来表示 list.add(null); root = queue.poll(); continue; } // 得到左孩子和右孩子 TreeNode left = root.left; TreeNode right = root.right; // 将根节点的值放入数组中 list.add(root.val); // 将左孩子和右孩子放入待处理队列中 if (left == null) { queue.add(none); } else { queue.add(left); } if (right == null) { queue.add(none); } else { queue.add(right); } // 获取下一个待处理节点作为根节点 root = queue.poll(); } Integer[] arr = new Integer[list.size()]; return list.toArray(arr); } /** * 将int数组转为二叉树,并返回根节点 * @param arr int数组 * @return root node */ public static TreeNode convertArray2Tree(Integer[] arr) { Objects.requireNonNull(arr, "数组为null"); int arrLen = arr.length; if (arrLen == 0) { // 数组长度为0,生成一棵空树 return null; } Deque queue = new ArrayDeque<>(arrLen); TreeNode root = getNodeOrReturnNull(arr, 0); if (root == null) { // 数组首元素为0,生成一棵空树 return null; } // 保存整棵二叉树根节点,待后面返回 TreeNode realRoot = root; // 定义索引 int rightIdx = 2; // 使用add可以报错 while (root != null) { // 根节点不为null的时候表示二叉树需要继续生成,否则表示二叉树到此结束了 int leftIdx = rightIdx - 1; if (rightIdx < arrLen) { // 左右索引都合法 TreeNode left = getNodeOrReturnNull(arr, leftIdx); TreeNode right = getNodeOrReturnNull(arr, rightIdx); // 添加左右子树,当然左右子树都可能为null,不过不影响结果 root.left = left; root.right = right; // 如果节点有效,则添加到队列中,作为以后使用的根节点 if (left != null) { queue.add(left); } if (right != null) { queue.add(right); } } else { if (leftIdx < arrLen) { // 左索引合法,右索引非法 // 添加一下左子树,这里也无需放入队列中,因为队列结果后面不再处理元素了 root.left = getNodeOrReturnNull(arr, leftIdx); } else { // 左右索引都非法 // 无需做任何事情 } // 右索引超过数组范围,说明没有后续元素,结束循环 break; } // 使用poll不要报错,在while条件中判断是否需要继续生成二叉树 root = queue.poll(); rightIdx += 2; } return realRoot; } private static TreeNode getNodeOrReturnNull(Integer[] arr, int i) { if (arr[i] == null) { return null; } return new TreeNode(arr[i]); }}
测试代码如下:
public static void main(String[] args) { System.out.println(recursiveInorderTraversal(TreeNode.convertArray2Tree(new Integer[]{31, 32, null, 34, 41, 42, 43})));}
我们来看看对不对,我们把数组{31, 32, null, 34, 41, 42, 43}转换成二叉树,如下所示:
按照定义,我们可以很快地写出遍历结果为:42, 34, 43, 32, 41, 31。
来看上面的输出结果:
[42, 34, 43, 32, 41, 31]
符合我们的预期。