Binary Tree - 二元樹
二元樹是每個節點最多有兩個子樹的樹結構,子樹有左右之分,二元樹常被用於實現二元搜尋樹(binary search tree)和二元堆(binary heap)。
二元樹的第i層(根結點為第1層,往下遞增)至多有 個結點;深度為k的二元樹至多有 個結點;對任何一棵二元樹T,如果其終端結點數為 , 度為2的結點數為 , 則 。
一棵深度為 , 且有 個節點稱之為滿二元樹;深度為 ,有 個節點的二元樹,若且唯若其每一個節點都與深度為 的滿二元樹中,序號為 至 的節點對應時,稱之為完全二元樹。完全二元樹中重在節點標號對應。
Tree traversal 樹的遍歷
從二元樹的根節點出發,節點的遍歷分為三個主要步驟:對當前節點進行操作(稱為「訪問」節點,或者根節點)、遍歷左邊子節點、遍歷右邊子節點。訪問節點順序的不同也就形成了不同的遍歷方式。需要注意的是樹的遍歷通常使用遞迴的方法進行理解和實現,在訪問元素時也需要使用遞迴的思想去理解。
按照訪問根元素(當前元素)的前後順序,遍歷方式可劃分為如下幾種:
- 深度優先(depth-first):先訪問子節點,再訪問父節點,最後訪問第二個子節點。根據根節點相對於左右子節點的訪問先後順序又可細分為以下三種方式。
- 前序(pre-order):先根後左再右
- 中序(in-order):先左後根再右
- 後序(post-order):先左後右再根
- 廣度優先(breadth-first):先訪問根節點,沿著樹的寬度遍歷子節點,直到所有節點均被訪問為止,又稱為層次(level-order)遍歷。
如下圖所示,遍歷順序在右側框中,紅色A為根節點。使用遞迴和整體的思想去分析遍歷順序較為清晰。
二元樹的廣度優先遍歷和樹的前序/中序/後序遍歷不太一樣,前/中/後序遍歷使用遞迴,也就是用堆疊(stack)的思想對二元樹進行遍歷,廣度優先一般使用隊列(queue)的思想對二元樹進行遍歷。
節點定義
這裡的節點統一使用LeetCode的定義
Python
class TreeNode:
def __init__(self, val):
self.val = val
self.left, self.right = None, None
C++
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
Java
public class TreeNode {
public int val;
public TreeNode left, right;
public TreeNode(int val) {
this.val = val;
this.left = null;
this.right = null;
}
}
相關演算法——遞迴法遍歷
請參考Chapter 11
相關演算法——分治法(Divide & Conquer)
在計算機科學中,分治法是一種很重要的演算法。分治法即「分而治之」,把一個複雜的問題分成兩個或更多的相同或相似的子問題,再把子問題分成更小的子問題……直到最後子問題可以簡單的直接求解,原問題的解即子問題的解的合併。這個思想是很多高效演算法的基礎,如排序演算法(快速排序,合併排序)等。
分治法思想
分治法所能解決的問題一般具有以下幾個特征:
- 問題的規模縮小到一定的程度就可以容易地解決。
- 問題可以分解為若干個規模較小的相同問題,即該問題具有最優子結構性質。
- 利用該問題分解出的子問題的解可以合併為該問題的解。
- 該問題所分解出的各個子問題是相互獨立的,即子問題之間不包含公共的子問題。
分治法的三個步驟是:
- 分解(Divide):將原問題分解為若干子問題,這些子問題都是原問題規模較小的實例。
- 解決(Conquer):遞迴求解各子問題。如果子問題規模足夠小,則直接求解。
- 合併(Combine):將所有子問題的解合併為原問題的解。
分治法的經典題目:
- 二分搜索
- 大整數乘法
- Strassen矩陣乘法
- 棋盤覆蓋
- 合併排序(merge sort)
- 快速排序
- 循環賽日程表
- 河內塔(tower of Hanoi)
樹類題的複雜度分析
對樹相關的題進行複雜度分析時可統計對每個節點被訪問的次數,進而求得總的時間複雜度。