演算法筆記-Tree-2
Binary Search Tree
二元搜尋樹有以下特性:
- 一個節點的左子樹只包含鍵值小於該節點的節點。
- 一個節點的右子樹只包含鍵值大於該節點的節點。
- 左子樹和右子樹也都是二分搜尋樹。
而且以中序遍歷二元搜尋樹會得到一個遞增的數列。
操作
搜索
根據待查元素val
與當前 root -> val
的關係判斷:
root -> val == val
回傳當前root
。root -> val > val
查找左子樹。root -> val < val
查找右子樹。
1 | TreeNode* searchBST(TreeNode* root, int val) { |
插入
根據待查元素val
與當前 root -> val
的關係判斷:
root == nullptr
插入新節點。root -> val > val
重新構造左子樹。root -> val < val
重新構造右子樹。
1 | TreeNode* insertIntoBST(TreeNode* root, int val) { |
刪除
先用遞迴的方式找到要刪除的節點,並根據其左右子樹的情況做處理:
- 左右子樹皆為空:直接刪除節點,回傳
nullptr
。 - 左子樹為空:以右子樹取代該節點,回傳
root -> right
。 - 右子樹為空:以左子樹取代該節點,回傳
root -> left
。 - 左右子樹皆不為空:把左子樹插入
右子樹最小的節點的左邊
,以右子樹取代該節點,回傳root -> right
。
1 | TreeNode* deleteNode(TreeNode* root, int key) { |
還原
前序
因為二元搜尋樹的特性,在區分左、右子樹時不需要利用到中序遍歷,只要在前序遍歷中找到鍵值大於根結點的地方就是右子樹。
1 | TreeNode* buildTree(vector<int> preorder, int left, int right) { |
後序
和前序遍歷類似,只是根節點的位置要從最後面找。
中序
因為無法確定根結點所在的位置 (一定為遞增數列),因此無法構造出唯一的樹。
Segment Tree
線段樹是二元平衡樹的一種,用以儲存區間或線段,每個節點代表一個區間,而根結點代表區間的全部範圍,葉節點代表長度為 1 的區間,而編號方式以及代表的區間規則如下:
- 根結點編號為 0,並且代表整個區間的範圍。
- 假設當前節點編號為
i
,代表的區間為[left, right]
,假設mid = (left + right) / 2
則:- 左節點編號為
i * 2
,代表的區間為[left, mid]
。 - 右節點編號為
i * 2 + 1
,代表的區間為[mid + 1, right]
。
- 左節點編號為
構造
假設有一數組 v
,要建構一個線段樹來儲存每個區間的數字和
,我們利用 tree
來儲存該線段樹的資料,建構的方式是使用遞迴不斷建立左右子節點,直到長度為 1 的葉節點。
1 | vector<int> tree; |
查詢
查詢的過程依舊是由上而下進行遞迴,根據查詢的區間 [q_left, q_right]
以及結點所儲存的資料區間 [left, mid]
,來決定回傳的結果,可以分為三種情況:
查詢區間
和節點區間
沒有交集,則回傳0
。查詢區間
包含節點區間
,則回傳節點的值。查詢區間
與節點區間
有部分交集,則分別對左右子樹進行查詢,接著將結果進行聚合 (聚合的方式視線段樹的規則而訂)。
1 | int query(int node, int left, int right, int q_left, int q_right){ |
更新
由上而下遞迴進行更新,不斷更新節點儲存的值,並且對子樹也進行更新,直到目標節點時才停止。
1 | void update(int node, int left, int right, int index, int val){ |
Disjoint set & Union-find algorithm
並查集是一種資料結構,用於處理一些不交集的合併及查詢問題,而使用的 Union-find algorithm
主要包含了以下功能:
- 查找 (Find):查詢元素屬於哪個集合。
- 合併 (Union):將兩個集合合併為一個。
使用樹的概念儲存每個集合,樹的根結點就用來代表該集合,而所有樹所形成的森林則是所有的元素。
查找
因為樹的根結點代表該集合,因此在查找的時候只要不斷往回推,直到該節點指向自己 (根節點) 就好。
1 | int Find(int x){ |
合併
合併兩個集合其實就是把兩棵樹接起來,首先要先判斷各自的根結點,如果根結點相同則代表是相同集合,否則則要進行合併,將一個集合的根結點指向另一個集合的根結點即可。
1 | void Union(int a,int b){ |
優化
路徑壓縮
經過路徑壓縮後,可以減少回推根結點時的迭代次數,增加效率,通常在查詢時順便進行壓縮。
1 | int Find(int x){ |
按秩合併
每次進行合併時,把秩
比較小的樹根結點指向秩
較大的樹根結點,秩
主要有兩種定義方式,分別是樹的深度
以及元素個數
。
練習
LeetCode 110. Balanced Binary Tree
Given a binary tree, determine if it is height-balanced.
Example 1:

Input: root = [3,9,20,null,null,15,7]
Output: true
Example 2:

Input: root = [1,2,2,3,3,null,null,4,4]
Output: false
Example 3:
Input: root = []
Output: true
Solution:
1 | class Solution { |
建立一個 treeHeight
函數,在所在子樹為平衡二元樹時回傳其高度,否則回傳 -1
,因此最終只要傳入根結點並判斷其回傳值是否為 -1
就好。
LeetCode 307. Range Sum Query - Mutable
Given an integer array nums
, handle multiple queries of the following types:
- Update the value of an element in
nums
. - Calculate the sum of the elements of
nums
between indicesleft
andright
inclusive whereleft <= righ
t.
Implement the NumArray
class:
NumArray(int[] nums)
Initializes the object with the integer arraynums
.void update(int index, int val)
Updates the value ofnums[index]
to beval
.int sumRange(int left, int right)
Returns the sum of the elements ofnums
between indicesleft
andright
inclusive (i.e.nums[left] + nums[left + 1] + ... + nums[right]
).
Example 1:
Input
[“NumArray”, “sumRange”, “update”, “sumRange”]
[[[1, 3, 5]], [0, 2], [1, 2], [0, 2]]
Output
[null, 9, null, 8]
Solution:
1 | class NumArray { |
利用線段樹的方式儲存各區間的數字和,使用構造、查詢、更新三個函數。
LeetCode 399. Evaluate Division
You are given an array of variable pairs equations
and an array of real numbers values
, where equations[i] = [Ai, Bi]
and values[i]
represent the equation Ai / Bi = values[i]
. Each Ai or Bi is a string that represents a single variable.
You are also given some queries
, where queries[j] = [Cj, Dj]
represents the jth query where you must find the answer for Cj / Dj = ?
.
Return the answers to all queries. If a single answer cannot be determined, return -1.0
.
Note: The input is always valid. You may assume that evaluating the queries will not result in division by zero and that there is no contradiction.
Note: The variables that do not occur in the list of equations are undefined, so the answer cannot be determined for them.
Example 1:
Input: equations = [[“a”,“b”],[“b”,“c”]], values = [2.0,3.0], queries = [[“a”,“c”],[“b”,“a”],[“a”,“e”],[“a”,“a”],[“x”,“x”]]
Output: [6.00000,0.50000,-1.00000,1.00000,-1.00000]
Example 2:
Input: equations = [[“a”,“b”],[“b”,“c”],[“bc”,“cd”]], values = [1.5,2.5,5.0], queries = [[“a”,“c”],[“c”,“b”],[“bc”,“cd”],[“cd”,“bc”]]
Output: [3.75000,0.40000,5.00000,0.20000]
Example 3:
Input: equations = [[“a”,“b”]], values = [0.5], queries = [[“a”,“b”],[“b”,“a”],[“a”,“c”],[“x”,“y”]]
Output: [0.50000,2.00000,-1.00000,-1.00000]
Solution:
1 | class Solution { |
使用並查集以及一個 unordered map
來儲存變數之間的關係以及倍數,FindTimes
函數則是往回推算該變數與其根結點變數之間的倍數關係,在進行查詢時,可以分為幾種情況:
- 查詢變數不在並查集中:回傳
-1
。 - 查詢變數不在相同的樹中 (根結點相異):回傳
-1
。 - 查詢變數在相同的樹中 (根結點相同):回傳兩變數分別與根節點的倍數關係相除
FindTimes(queries[i][1]) / FindTimes(queries[i][0])
。
LeetCode 765. Couples Holding Hands
There are n
couples sitting in 2n
seats arranged in a row and want to hold hands.
The people and seats are represented by an integer array row
where row[i]
is the ID of the person sitting in the ith seat. The couples are numbered in order, the first couple being (0, 1)
, the second couple being (2, 3)
, and so on with the last couple being (2n - 2, 2n - 1)
.
Return the minimum number of swaps so that every couple is sitting side by side. A swap consists of choosing any two people, then they stand up and switch seats.
Example 1:
Input: row = [0,2,1,3]
Output: 1
Example 2:
Input: row = [3,2,0,1]
Output: 0
Solution:
1 | class Solution { |