今天我們來學習二叉樹的中序遍歷算法。
二叉樹有多種遍歷方法,前中序遍歷和層次遍歷。我們今天的主角是中序遍歷,它的遍歷順序爲:
- 左子樹
- 根節點
- 右子樹
如下圖所示:
我們知道樹的定義本身就是遞歸式的,左子樹就是一棵以根節點的左孩子爲根節點的樹,右子樹也同理,所以遍歷左子樹或者右子樹直接是把原來的根節點換成根節點的左孩子或者右孩子即可。這樣我們可以很快地寫出遞歸的中序遍歷算法。
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<Integer> recursiveInorderTraversal(TreeNode root) {
if (root == null) {
// 根節點爲null,返回空列表
return Collections.emptyList();
}
List<Integer> elems = new ArrayList<>();
recursiveInorderTraversalHelper(root, elems);
return elems;
}
private static void recursiveInorderTraversalHelper(TreeNode root, List<Integer> 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<TreeNode> queue = new ArrayDeque<>();
List<Integer> 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<TreeNode> 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]
符合我們的預期。