二叉樹在面試過程中出現的頻率非常高,因此熟練掌握二叉樹是吊打面試官的必備技能。
基本認識
二叉樹:是節點的一個有限集合,該集合要麼爲空,要麼由一個根節點加上左子樹和右子樹組成。
特點:
- 每個節點最多有兩顆子樹,即二叉樹不存在度大於 2 的節點。
- 二叉樹的子樹有左右之分,左子樹在左,右子樹在右。
二叉樹的存儲結構
二叉樹的存儲結構有:
- 順序存儲
- 鏈式存儲
順序存儲
順序存儲是使用一維數組存儲二叉樹中的節點,節點的存儲位置就是數組的下表索引。
可以看到,順序存儲結構在存儲非完全二叉樹時,會出現空間利用不完全的問題。對於某種極端情況,比如只有左子樹,或只有右子樹,採用順序存儲結構是十分浪費空間的。因此順序存儲一般適用於完全二叉樹。
鏈式存儲
鏈式存儲是使用鏈表來存儲,每個節點包含三個域: 數據域和左右孩子域。
二叉樹遍歷
二叉樹的遍歷方式有:
- 先序遍歷
- 中序遍歷
- 後序遍歷
- 層級遍歷
其中先中後序都是相對於根節點而言的,先序就是先根再左右孩子,中序就是先左孩子再根最後右孩子,後續就是先左孩子再右孩子最後根。層級遍歷就是從上往下一層層的訪問節點。
例如上面二叉樹鏈式存儲結構圖,
先序: A B D E C
中序: D B E A C
後續: D E B C A
層級: A B C D E
二叉樹遍歷代碼實現
定義二叉樹節點類:
/**
* 二叉樹的節點
*/
public class Node {
private int value;
private Node left;
private Node right;
public Node() {
}
public Node(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
public Node getLeft() {
return left;
}
public void setLeft(Node left) {
this.left = left;
}
public Node getRight() {
return right;
}
public void setRight(Node right) {
this.right = right;
}
@Override
public String toString() {
return "ListNode{" +
"value=" + value +
", left=" + left +
", right=" + right +
'}';
}
}
定義二叉樹類, 通過根節點來定義:
/**
* 二叉樹類
*/
public class BinaryTree {
/**
* 根節點
*/
private Node root;
public BinaryTree() {
}
public BinaryTree(int value) {
Node node = new Node(value);
setRoot(node);
}
public Node getRoot() {
return root;
}
public void setRoot(Node root) {
this.root = root;
}
}
1. 插入節點
/**
* 往二叉樹中插入節點
*
* @param value
*/
public void add(int value) {
Node newNode = new Node(value);
// 沒有根節點時,插入到根節點
if (root == null) {
root = newNode;
} else { // 有節點
Node curNode = root;
while (true) {
// 插入節點的值小於當前節點的值,放到當前節點的左邊
if (value < curNode.getValue()) {
// 如果當前節點沒有左孩子,則直接放入,否則繼續循環
if (curNode.getLeft() == null) {
curNode.setLeft(newNode);
break;
}
curNode = curNode.getLeft();
} else if (value > curNode.getValue()) { // 插入節點大於當前節點的值,放到節點的右邊
if (curNode.getRight() == null) {
curNode.setRight(newNode);
break;
}
curNode = curNode.getRight();
}
}
}
}
2. 先序遍歷
/**
* 先序遍歷,輸出到 List 集合中
*
* @return
*/
private void pre2(Node node, List<Integer> list) {
if (node == null) {
return;
}
list.add(node.getValue());
pre2(node.getLeft(), list);
pre2(node.getRight(), list);
}
3. 中序遍歷
/**
* 中序遍歷
*
* @param node
* @param list
*/
private void middle(Node node, List<Integer> list) {
if (node == null || list == null) {
return;
}
middle(node.getLeft(), list);
list.add(node.getValue());
middle(node.getRight(), list);
}
4. 後序遍歷
/**
* 後續遍歷
*
* @param node
* @param list
*/
private void after(Node node, List<Integer> list) {
if (node == null || list == null) {
return;
}
after(node.getLeft(), list);
after(node.getRight(), list);
list.add(node.getValue());
}
5. 層級遍歷
/**
* 層級遍歷(最基本的): 通過隊列來實現
*/
public List<Integer> levelTraversal() {
List<Integer> list = new ArrayList<>();
if (root == null) {
return list;
}
Queue<Node> queue = new LinkedList<>(); // 定義一個隊列
queue.add(root); // 根節點先插入隊列
Node curNode;
while (!queue.isEmpty()) { // 隊列不爲空,循環取出元素
curNode = queue.poll();
list.add(curNode.getValue());
if (curNode.getLeft() != null) {
queue.add(curNode.getLeft());
}
if (curNode.getRight() != null) {
queue.add(curNode.getRight());
}
}
return list;
}
6. 層級遍歷, 並把每層分成一組
/**
* 層級遍歷,將每一層分成一個單獨的組
*/
public List<List<Integer>> levelTraversalGroup() {
List<List<Integer>> resultList = new ArrayList<>(); // 包含每層的外部 list
if (root == null) {
return resultList;
}
Queue<Node> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
List<Integer> sublist = new ArrayList<>(); // 存放每一層中的元素 list
int size = queue.size(); // 此時的 size() 大小就是每層中元素的個數
for (int i = 0; i < size; i++) {
Node curNode = queue.poll();
sublist.add(curNode.getValue());
if (curNode.getLeft() != null) {
queue.offer(curNode.getLeft());
}
if (curNode.getRight() != null) {
queue.offer(curNode.getRight());
}
}
resultList.add(sublist); // 將每層的list 加入到外部 list 中
}
return resultList;
}
7. 層級遍歷, 把每層分成一組, 並按照奇數層從右往左,偶數層從左往右
/**
* 層級遍歷,將每一層分爲單獨的一組,並且按照 z 字形輸出
*
* @return
*/
public List<List<Integer>> levelTraversalGroupZ() {
List<List<Integer>> resultList = new ArrayList<>(); // 包含每層的外部 list
if (root == null) {
return resultList;
}
Queue<Node> queue = new LinkedList<>(); // LinkedList 實現隊列
queue.offer(root);
boolean right2Left = true;
while (!queue.isEmpty()) {
List<Integer> sublist = new ArrayList<>(); // 存放每一層中的元素 list
int size = queue.size(); // 此時的 size() 大小就是每層中元素的個數
for (int i = 0; i < size; i++) {
Node curNode = queue.poll();
if (right2Left) {
sublist.add(0, curNode.getValue());
} else {
sublist.add(curNode.getValue());
}
if (curNode.getLeft() != null) {
queue.offer(curNode.getLeft());
}
if (curNode.getRight() != null) {
queue.offer(curNode.getRight());
}
}
right2Left = !right2Left;
resultList.add(sublist); // 將每層的list 加入到外部 list 中
}
return resultList;
}
8. 深度優先遍歷
/**
* 二叉樹深度遍歷,利用堆棧,先將右子樹壓棧,再將左子樹壓棧,這樣左子樹就再棧頂。
*
* @return
*/
private List<Integer> depthTraversal(Node root) {
List<Integer> list = new ArrayList<>();
if (root == null) {
return list;
}
Stack<Node> stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()) {
Node node = stack.pop();
list.add(node.getValue());
if (node.getRight() != null) {
stack.push(node.getRight());
}
if (node.getLeft() != null) {
stack.push(node.getLeft());
}
}
return list;
}
9. 獲取第 k 層節點
/**
* 獲取第 k 層元素
*
* @param level
* @return
*/
public List<Integer> getDataByLevel(int level) {
List<Integer> list = new ArrayList<>();
if (root == null || level < 1) {
return list;
}
Queue<Node> queue = new LinkedList<>();
queue.offer(root);
int curLevel = 1;
while (!queue.isEmpty()) {
int size = queue.size(); // 此時的 size() 大小就是每層中元素的個數
for (int i = 0; i < size; i++) {
Node curNode = queue.poll();
if (level == curLevel) {
list.add(curNode.getValue());
}
if (curNode.getLeft() != null) {
queue.offer(curNode.getLeft());
}
if (curNode.getRight() != null) {
queue.offer(curNode.getRight());
}
}
curLevel++;
}
return list;
}
10. 查找某個值
/**
* 查找某個值
*/
private boolean query(Node node, int value) {
if (node == null) {
return false;
}
if (value < node.getValue()) {
return query(node.getLeft(), value);
} else if (value > node.getValue()) {
return query(node.getRight(), value);
} else {
return true;
}
}
10. 獲取二叉樹的深度
private int getTreeDepth(Node node) {
if (node == null) {
return 0;
}
int left = getTreeDepth(node.getLeft());
int right = getTreeDepth(node.getRight());
return left > right ? left + 1 : right + 1;
}
11. 判斷二叉樹是否是平衡二叉樹
private boolean isBalanceTree(Node node) {
if (node == null) {
return true;
}
int lh = getTreeDepth(node.getLeft());
int rh = getTreeDepth(node.getRight());
return Math.abs(lh - rh) <= 1 && isBalanceTree(node.getLeft()) && isBalanceTree(node.getRight());
}
測試代碼
public class BinaryTreeDemo {
public static void main(String[] args) {
// 構建一個二叉樹
/**
* 6
* / \
* 4 9
* / \ / \
* 2 5 7 10
* / \
* 1 3
* /
* 0
*
*
*/
BinaryTree binaryTree = new BinaryTree();
int[] arr = {6, 4, 9, 2, 5, 7, 10, 1, 3, 0};
for (int i : arr) {
binaryTree.add(i);
}
// 二叉樹先序遍歷,輸出到 List 中
List<Integer> preOrderList = binaryTree.preOrder2();
System.out.println("二叉樹先序遍歷: " + preOrderList);
// 二叉樹後序遍歷,輸出到 List 中
List<Integer> afterOrderList = binaryTree.afterOrder();
System.out.println("二叉樹後序遍歷: " + afterOrderList);
// 二叉樹層級遍歷
List<Integer> levelList = binaryTree.levelTraversal();
System.out.println("二叉樹層級遍歷: " + levelList);
// 二叉樹層級遍歷, 並將每層顯示爲一組
List<List<Integer>> levelGroupList = binaryTree.levelTraversalGroup();
System.out.println("二叉樹層級遍歷,並分組: " + levelGroupList);
// 二叉樹層級遍歷, 並將每層顯示爲一組, Z 字形輸出(奇數層逆序,偶數層順序)
List<List<Integer>> levelTraversalGroupZ = binaryTree.levelTraversalGroupZ();
System.out.println("二叉樹層級遍歷,並分組,Z 字形: " + levelTraversalGroupZ);
//
List<Integer> depthList = binaryTree.depthTraversal();
System.out.println("二叉樹深度優先遍歷: " + depthList);
// 獲取二叉樹第 k 層元素
int level = 1;
List<Integer> level_k_List = binaryTree.getDataByLevel(level);
System.out.println("第 " + level + " 層元素: " + level_k_List);
// 獲取二叉樹深度
int depth = binaryTree.getTreeDepth();
int depth2 = binaryTree.getTreeDepth2();
System.out.println("二叉樹深度: " + depth);
System.out.println("二叉樹深度(非遞歸): " + depth2);
// 判斷一個數是不是平衡二叉樹
boolean isBalance = binaryTree.isBalanceTree();
System.out.println("是否是平衡二叉樹: " + isBalance);
// 二叉查找
int value = 8;
boolean exist = binaryTree.query(value);
System.out.println("二叉查找:是否存在 value = " + value + ": " + exist);
}
}
輸出結果
二叉樹先序遍歷: [6, 4, 2, 1, 0, 3, 5, 9, 7, 10]
二叉樹後序遍歷: [0, 1, 3, 2, 5, 4, 7, 10, 9, 6]
二叉樹層級遍歷: [6, 4, 9, 2, 5, 7, 10, 1, 3, 0]
二叉樹層級遍歷,並分組: [[6], [4, 9], [2, 5, 7, 10], [1, 3], [0]]
二叉樹層級遍歷,並分組,Z 字形: [[6], [4, 9], [10, 7, 5, 2], [1, 3], [0]]
二叉樹深度優先遍歷: [6, 4, 2, 1, 0, 3, 5, 9, 7, 10]
第 1 層元素: [6]
二叉樹深度: 5
二叉樹深度(非遞歸): 5
是否是平衡二叉樹: false
二叉查找:是否存在 value = 8: false