# 2024q1 Homework4 (quiz3+4)
contributed by <[marvin0102](https://github.com/marvin0102/lab0-c)>
# 第一周測驗
## 測驗 1: 計算開平方根
假設我們要求 $x$ 的平方根,將 $x$ 轉為二進位後,可以將 $x$ 拆成 2 的冪相加:
$x = (000b_0b_1b_2...b_{n-1}b_n)^2$
其中 $000b_0b_1b_2...b_{n-1}b_n$ 為 bits pattern,而 $b_0$ 為最高位的 1 。
設 $a_0 = 000...b_n$ 、 $a_1 = 000...b_{n-1}0$ ... $a_1 = 000...b_{n-2}00$ ,因此可以將 $x$ 拆成:
$x = N^2 = (a_n + a_{n-1} + a_{n-2} + ... + a_0)^2$ , $a_m=2^m$ or $a_m=0$
使用矩陣乘法可得
\begin{bmatrix}
a_0a_0&a_1a_0&a_2a_0&...&a_na_0\\
a_0a_1&a_1a_1&a_2a_1&...&a_na_1\\
a_0a_2&a_1a_2&a_2a_2&...&a_na_2\\
... \\
a_0a_n&a_1a_n&a_2a_n&...&a_na_n
\end{bmatrix}
可以將這個矩陣拆為:
\begin{bmatrix}
a_0a_0&a_1a_0&a_2a_0&...&a_na_0\\
a_0a_1&&&&\\
a_0a_2&&&&\\
... \\
a_0a_n&&&&
\end{bmatrix}
\begin{bmatrix}
a_1a_1&a_1a_2&...&a_na_0\\
a_1a_1&&&\\
a_1a_2&&&\\
... \\
a_1a_n&&&
\end{bmatrix}
至最後一向
\begin{bmatrix}
a_na_n
\end{bmatrix}
將這些矩陣結合可以整理成以下公式
\begin{align}
N^2 &= a_n^2 + [2a_n+a{n-1}]a_{n-1} + [2(a_n+a_{n-1}) + a_{n-2}]a_{n-2} + ...+[2(\sum_{i=1}^na_i)+a_0]a_0\\
&= a_n^2 + [2P_n+a{n-1}]a_{n-1} + [2P_{n-1} + a_{n-2}]a_{n-2} + ...+[2P_{0}+a_0]a_0
\end{align}
其中 $P_m = a_n + a_{n-1} + ... + a_m$
而所求 $N = P_0$ 即為所求。
可以知道
$P_m= a_n + a_{n-1} + ... + a_m = (a_n + a_{n-1} + ... +a_{m+1})+ a_m = P_{m+1} + a_m$
經由測試 $P_m^2 \le N^2$ 判斷 $a_m=2^m$ or $a_m=0$
\begin{cases}
P_m = P_{m+1}+2^m, &\text{if $P_m^2 \le N^2$}\\
P_m = P_{m+1}, &\text{otherwise}
\end{cases}
$m = 0$ 時,$P_m$ 即為所求
可以透過上一輪的差值計算比較 $P_m^2 \le N^2$ ,設 $X_m = N^2-P_m^2$ 為 $P_m^2$ 、$N^2$ 的差值,可以由上一輪的差值 $X_{m+1}$ 減去 $Y_m$ 而得, $Y_m$ 的計算如下:
- $X_m = N^2-P_m^2 = X_{m+1}-Y_m$
- $Y_m = N^2-P_{m+1}^2 - (N^2-P_{m}^2) = P_{m}^2 - P_{m+1}^2 = 2P_{m+1}a_m + a_m^2$
因此,可以由上一輪的 $P_{m+1}$ 計算 $Y_m$
將 $Y_m$ 拆成 $c_m, d_m$,可得
$$Y_m =
\begin{cases}
c_m+d_m, &\text{if $a_m = 2^m$}\\
0, &\text{if $a_m = 0$}
\end{cases}
$$
其中 $c_m = P_{m+1}2^{m+1}$ 、 $d_m = (2^m)^2$
接著嘗試通過 $c_m$ 、 $d_m$ 推論下一輪的 $c_{m-1}$ 、 $d_{m-1}$:
- $$c_{m-1} = P_{m}2^{m} = (P_{m+1}+a_m)2^{m} = P_{m+1}2^{m} + a_m2^{m} =
\begin{cases}
c_m/2+d_m, &\text{if $a_m = 2^m$}\\
c_m/2, &\text{if $a_m = 0$}
\end{cases}
$$
- $d_{m-1} = (2^{m-1})^2 = \frac{d_{m}}{4}$
接下來就可以游上往下尋找 $a_n+a_{n-1}+...+a_0$,設從 $a_n$ 開始往下尋找:
- 因為 $n$ 為最大位數,因此 $P_{n+1} = 0$ , $c_n = P_{n+1}2^{n+1} = 0$
- $d_n = (2^n)^2 = 4^n$
因為 $d_m$ 最小為 1 ,但是座位移操作最後必為 0 ,因此我們可以將 $d!=0$ 作為搜尋的判斷式。
從原始碼來解析:
```c
int i_sqrt(int x)
{
if (x <= 1) /* Assume x is always positive */
return x;
int z = 0;
for (int m = 1UL << ((31 - __builtin_clz(x)) & ~1UL); m; m >>= AAAA) {
int b = z + m;
z >>= BBBB;
if (x >= b)
x -= b, z += m;
}
return z;
}
```
因為無法計算虛數,因此須先將 $x$ 為負數的情況排除。
從 `1UL << ((31 - __builtin_clz(x)) & ~1UL)` 的作用為計算 $x$ 中最大 2 的冪為多少,也就是 $x = N^2 = (2^n+2^{n-1}+...+1)^2$ 中 $(2^n)^2$ ,因此我們可以知道 `m` 為 $b_n = 4^n$。
又由上面的公式,$d_{m-1} = (2^{m-1})^2 = \frac{d_{m}}{4}$ 可知,每一輪迴圈 `m` 須除 4 ,也就是右移 2 bits ,因此 `AAAA` 為 `2` 。
從 `return z;` 及 `z` 的起始值為 0 可推得, `z` 即為 $c_n$,且 `b` 為 $Y_n$,又因為 `z` 每次累加 $(2^n)^2$,因此需要每次迴圈除 2 ,使 `z` 變為 $P_n$,因此 `BBBB` 為 1。
### Linux 平方根運算應用案例:
在 [block/blk-wbt.c](https://elixir.bootlin.com/linux/v6.8/source/block/blk-wbt.c#L92) 中尋找到函式 `rwb_arm_timer` 使用 `int_sqrt` 的應用案例。
`int_sqrt` 被定義於 `lib/math/int_sqrt.c` 中,用於計算整數的平方根。
從註解可以了解
```c
* buffered writeback throttling. loosely based on CoDel. We can't drop
* packets for IO scheduling, so the logic is something like this:
* - Monitor latencies in a defined window of time.
* - If the minimum latency in the above window exceeds some target, increment
* scaling step and scale down queue depth by a factor of 2x. The monitoring
* window is then shrunk to 100 / sqrt(scaling step + 1).
```
如果上述 `window` 中的最小延遲超過某個目標值,則增加 `scaling step` ,並將佇列深度縮小 factor 的 2 倍。然後,`monitoring window` 縮小為 100 / sqrt(縮放步驟 + 1)。
具體程式碼:
```c
rwb->cur_win_nsec = div_u64(rwb->win_nsec << 4,
int_sqrt((rqd->scale_step + 1) << 8));
```
### 實作更快的程式碼:
TODO
## 測驗 2: 計算除法
不使用 `/` 與 `%` 求一數字除以 10 的商跟餘數,相當於乘 $\frac{1}{10}$,但因為 10 不能以 2 的冪表示,因此需要找近似值 $\frac{a}{2^N}$ 使近似於 $\frac{1}{10}$ ,當 $2^N = 128$ 、 $a=13$ 時, $\frac{128}{13} \approx 9.84$ 與 10 很接近
```c
unsigned d2, d1, d0, q, r;
d0 = q & 0b1;
d1 = q & 0b11;
d2 = q & 0b111;
q = ((((tmp >> 3) + (tmp >> 1) + tmp) << 3) + d0 + d1 + d2) >> 7;
r = tmp - (((q << 2) + q) << 1);
```
因為 $13 = 8+4+1$ 因此我們可以透過 `(((tmp >> 3) + (tmp >> 1) + tmp) << 3)` 計算 $x*13$ ,因為右移時,會遺失最低位的 3 個位元,因此須利用 `+ d0 + d1 + d2`,紀錄並將其加回。
在我們得到 $x*13$ 後,在除以 128 即為我們所求的商,也就是 `>> 7`。
而餘數可以利用公式, $f - g \times Q= r$ 得到,`r = tmp - (((q << 2) + q) << 1);`。
完整程式碼為:
```c
#include <stdint.h>
#include <stdio.h>
int main()
{
for (int n = 0; n <= 19; n++) {
unsigned d2, d1, d0, q, r;
d0 = q & 0b1;
d1 = q & 0b11;
d2 = q & 0b111;
q = ((((n >> 3) + (n >> 1) + n) << 3) + d0 + d1 + d2) >> 7;
r = n - (((q << 2) + q) << 1);
printf("q: %d r: %d\n", q, r);
}
return 0;
}
```
可包裝為以下函式:
```c
#include <stdint.h>
void divmod_10(uint32_t in, uint32_t *div, uint32_t *mod)
{
uint32_t x = (in | 1) - (in >> 2); /* div = in/10 ==> div = 0.75*in/8 */
uint32_t q = (x >> 4) + x;
x = q;
q = (q >> 8) + x;
q = (q >> 8) + x;
q = (q >> 8) + x;
q = (q >> 8) + x;
*div = (q >> CCCC);
*mod = in - ((q & ~0x7) + (*div << DDDD));
}
```
起初在觀察程式時,並沒有理解到兩程式之間的關聯性,在閱讀 [Hacker's Delight](https://web.archive.org/web/20180517023231/http://www.hackersdelight.org/divcMore.pdf) 時,發現兩程式都是用近似值計算除以 10 的結果。
`x = (in | 1) - (in >> 2)` 計算 $x = in - \frac{1}{4}in = \frac{3}{4}in$ ,`q = (x >> 4) + x` 可以得 $q = \frac{x}{16} + x = \frac{3}{64}in + \frac{3}{4}in = \frac{51}{64}in \approx 0.79in$ 近似 0.8,有此可知 `*div = (q >> CCCC)` 須將 `q` 除以 8 才可得 `in/10` ,因此 `CCCC` 為 `3`。
```c
q = (q >> 8) + x;
q = (q >> 8) + x;
q = (q >> 8) + x;
q = (q >> 8) + x;
```
而四次位移是為了縮小誤差。
而餘數的計算同樣是將 `in - div*10` 而得,`(q & ~0x7)`保留除了最後三位元以外的其他位元,能等同於 `div<<3`,因為 `10 = 0b1010`,我們已經取得了`div<<3`,引此 `(*div << DDDD)` 為 `(*div << 1)`。
## 測驗 3:
當我們計算以 2 為第底數的對數時,我們可以計算一數的 leading set bit (從 most significant bit 開始計算)得到對數的近似值,例如:
```c
log(128) = log(0b1000 0000) = 7 // first set bit of 128 is 8
log(160) = log(0b1010 0000) = 7.321 // first set bit of 160 is 8
```
我們可以使用類似二元搜尋樹的方式加快 leading set bit 的搜索,方法如下:
```c
static size_t ilog2(size_t i)
{
size_t result = 0;
while (i >= AAAA) {
result += 16;
i >>= 16;
}
while (i >= BBBB) {
result += 8;
i >>= 8;
}
while (i >= CCCC) {
result += 4;
i >>= 4;
}
while (i >= 2) {
result += 1;
i >>= 1;
}
return result;
}
```
從第一個迴圈中`i >>= 16` 可知,此迴圈一次檢查 16 位元,如果 `i` 的 leading set bit 大於 16 位時,可以直接將 i 右移 16 位元,因此 `AAAA` 為 `0xffff`。
後面的迴圈同樣分別檢查 8 、 4 、 1 位元,因此 `BBBB` 、 `CCCC` 分別為 `0xff` 與 `0xf`。
可以利用 GNU extension `__builtin_clz` 來改寫使得程式更精簡:
```c
int ilog32(uint32_t v)
{
return (31 - __builtin_clz(DDDD));
}
```
:::info
從 [GCC online documentation](https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html) 的敘述:
Built-in Function: int __builtin_clz (unsigned int x)
Returns the number of leading 0-bits in x, starting at the most significant bit position. If x is 0, the result is undefined
可以知道 `__builtin_clz` 返回從 most significant bit (32 or 64) 開始計算,到碰的第一個 set bit 為止, 0 的數量。
其中,前綴的 `__builtin_` 代表此函式為 GCC 的 built-in 函式,
:::
因此,`ilog32(n)` 就是先使用 `__builtin_clz(n)` 計算 leading bits 的數量,再用 31 減掉,就可以得到從 most significant bit 數來第一個 set bit 的位置。
### Linux log2 的應用案例:
在 [include/linux/log2.h](https://elixir.bootlin.com/linux/v6.8/source/include/linux/log2.h#L156) 中定義了 log2 相關的聚集 `ilog`,其中包含了函式 `__ilog2_u64` 及 `__ilog2_u32` ,使`ilog2` 可以計算變數大小為 64 或 32 位元的對數值。
Linux/block 負責磁碟管理,其中[block/blk-zoned.c]([block/blk-zoned.c](https://elixir.bootlin.com/linux/v6.8/source/block/blk-zoned.c)) 使用 `ilog2` 計算 zone 的個數。
```c
unsigned int bdev_nr_zones(struct block_device *bdev)
{
sector_t zone_sectors = bdev_zone_sectors(bdev);
if (!bdev_is_zoned(bdev))
return 0;
return (bdev_nr_sectors(bdev) + zone_sectors - 1) >>
ilog2(zone_sectors);
}
```
## 測驗 4:
從函式 `ewma_init` 中, `avg->factor` 為 `factor` 的對數,且函式 `ewma_read` 中,返回值為 `avg->internal >> avg->factor` 可以知道,再做 ewma 計算時,會以 $internal = factor*avg$ 做計算,當讀取數值時,須先將 $internal \div faxtor$ 才會得到正確的值。
在此前提下,分析函式 `ewma_add` :
```c
struct ewma *ewma_add(struct ewma *avg, unsigned long val)
{
avg->internal = avg->internal
? (((avg->internal << EEEE) - avg->internal) +
(val << FFFF)) >> avg->weight
: (val << avg->factor);
return avg;
}
```
對照 EWMA 的數學定義:
$$
S_t=
\begin{cases}
Y_0 &t=0\\
\alpha Y_t+(1-\alpha) S_{t-1} &t>0
\end{cases}
$$
當加入第一筆資料時 $S_0 = Y_0$ 對應到 `avg->internal = (val << avg->factor)` 符合觀察。因此, $S_t = \alpha Y_t+(1-\alpha) S_{t-1}$ 在程式內乘上 $factor$ 後,
變為 $internal = weight*(factor*val) + (1 - weight)*internal$,但在函式中卻用了
`>> avg->weight` ,在觀察程式後發現,將 $\alpha = \frac{1}{2^{avg->weight}}$ 帶入公式計算可以發現,原本的公式變為 $S_t = \frac{1}{2^{avg->weight}} Y_t+(1-\frac{1}{2^{avg->weight}}) S_{t-1}$ ,
同乘 $2^{avg->weight}$ 後 $2^{avg->weight}S_t = Y_t+(2^{avg->weight}-1) S_{t-1}$ 與 `((avg->internal << avg->weight) - avg->internal) + (val << avg->factor)` 相符,因此最後計算的結果須除 $2^{avg->weight}$ 才會得到正確的結果。
### Linux EWMA 的相關程式碼與應用案例:
[include/linux/average.h](https://elixir.bootlin.com/linux/v6.8/source/include/linux/average.h#L28) 定義了 EWMA 的巨集 `DECLARE_EWMA` ,其中可以使用巨集引數 `name` 自行定義 EWMA 相關函式名稱,分別有 `ewma_##name##_init` 、 `ewma_##name##_read` 、 `ewma_##name##_add` 三個函式與結構體 `ewma_##name`。
`DECLARE_EWMA` 引數中 `_precision` 為定點數得 fraction bit 個數,而 EWMA 數學定義中的 $\alpha$ 則是以 `1/_weight_rcp` 的形式定義。
在 [drivers/net/wireless/ath/ath11k/core.h](https://elixir.bootlin.com/linux/v6.8/source/drivers/net/wireless/ath/ath11k/core.h#L478) 中,使用了 `DECLARE_EWMA(avg_rssi, 10, 8)` 定義了結構體 `ewma_avg_rssi` ,並且用 `ath11k_sta` 包裝。
在 [drivers/net/wireless/ath/ath11k/mac.c](https://elixir.bootlin.com/linux/v6.8/source/drivers/net/wireless/ath/ath11k/mac.c#L4986) 中函式 `ath11k_mac_station_add` 初始化,確切的作用仍在理解中。
## 測驗 5:
函式 `ceil_ilog2` 的作用為計算以2為底數的對數並且向上取整,實作方法與測驗 3 類似,通過二元搜尋的方式找出從 msb開始計算的 leading set bit 的位置。
```c
r = (x > 0xFFFF) << 4;
x >>= r;
```
`r` 為記錄leading set bit 的位置,當 `x > 0xFFFF` 成立時, `r` 為 $2^4$ ,代表 leading set bit 大於 16 位元,而 `x >>= r` 則是將 `x` 右移 16 位元再做後續的比較,若 `x > 0xFFFF` 不成立時,則 `r = 0` 且 `x` 無位移。
```c
shift = (x > 0xFF) << 3;
x >>= shift;
r |= shift;
```
在檢驗完 16 位元後,接續檢驗 8 位元,而 `r |= shift` 則是將兩次檢驗的位元結果相加。
再分別檢驗完 16 、 8 、 4 、 2 位元後,接著將這些檢驗結果與 `(x>1)` 相加即為從 msb開始計算的 leading set bit 的位置。
而因為結果需要向上取整,因此最後的結果需要加 1 ,如果 `x` 為 $2^n$ 則結果就會錯誤,因此在函示一開始需加上 `x--` 以避免此情況。
### 改進程式碼
在原本的函示中,因為 `x--` 在 `x` 為 0 時,會將 `x` 變為負數進而無法計算,因此只要將程式碼改為:
```diff
- x--;
+ x -= !(!x);
```
# 第二周測驗
## 測驗 1:
因為需要計算陣列 nums 中任兩個元素的 Hamming distance ,因此需要兩個迴圈將陣列 nums 中的元素相互比對並且透過 `__builtin_popcount(nums[i] ^ nums[j]` 計算 Hamming distance ,但因為兩層迴圈都是從頭開始走訪陣列 nums 因此會有重複計算的問題,例如:當 i=0 時, `nums[i] ^ nums[j]` 會計算 j=1,2,...,n, 而 `i=1` 時,會計算 j=0,2,...,n , 其中 `nums[0] ^ nums[1]` `nums[1] ^ nums[0]`重複計算了,因此當計算完成時, `total` 為兩倍的 Hamming distance ,在最後還須除 2 ,也就是 `total>>1`。
觀察每個 bit 的 Hamming distance :
| n'th bit | 4 | 3 | 2 | 1 | 0 |
| ---- | ---- | ---- | ---- | ---- | ---- |
| input 7 | 0 | 0 | 1 | 1 | 1 |
| input 5 | 0 | 0 | 1 | 0 | 1 |
| input 10 | 0 | 1 | 0 | 1 | 0 |
| input 17 | 1 | 0 | 0 | 0 | 1 |
每個 bit 的 Hamming distance 為陣列中取兩個元素,滿足兩元素中該 bit 為 [0,1] 或 [1,0] 的組合數量。例如:
1. 第 0 個 bit 觀察 :
只有 10 的第 0 個 bit 為 `0` -> Hamming Distance = (1)*(numsize - 1)
2. 第 1 個 bit 觀察 :
有兩個 bit 為 `0` -> Hamming Distance = (2)*(numsize - 2)
因此,我們可以統整所有元素中每個 bit 為 1 的數量,並計算每個 bit 的 Hamming distance ,最後再加總起來就是 total Hamming distance。
程式碼如下:
```c
int totalHammingDistance(int* nums, int numsSize)
{
int total = 0;
for (int i = 0;i < 32;i++)
int count = 0;
for (int j = 0; j < numsSize;j++)
if(nums[j] & (1<<i)){
count++;
}
total += count*(numsSize-count);
return total;
}
```
## 測驗 2:
觀察陣列 `move_masks` ,因為井字遊戲最多只有 9 個可能的選擇,因此可以知道 `move_masks` 中每一個元素代表井字遊戲的其中一個位置。將九宮格內的直線、橫線與斜線個數相加剛好為 8 條連線,又每條連線為九宮格中的三格連線所形成。
觀察元素內的數值可以發現,相比於直接將座標進行編號, `move_masks` 的做法是將九宮格中的連線做編號,每 4 個 bits 紀錄一條連線,則 `uint32_t` 恰好可以記錄 8 條連線。
在紀錄著一條連線的 4 bits 中,每位元代表連線中的其中一個格子,當遊戲的其中一方勝利時,代表 8 條連線中,有至少一條連線的三個格子被遊戲中的一方填滿,此時紀錄著這條連線的 4 bits 為 `0111` 即 `0x7`。
```c
static inline uint32_t is_win(uint32_t player_board)
{
return (player_board + 0x11111111) & 0x88888888;
}
```
這時只要將記錄著遊戲狀態的變數 `player_board` 加 `0x11111111` 即每 4 bits 加 1 ,並觀察是否有其中 4 bits 進位為 `1000` 即 `0x8` 就可知道是否達成勝利條件。
井字遊戲中,每一個格在都會影響到不只一條連線,因此 `move_masks` 的每個元素 set bit 個數至少為三以上,且每 4 bits 只會有一個 set bit。
觀察遊戲的整體流程, `boards` 負責記錄玩家的遊戲狀態,`available_moves` 代表目前可以下棋的位置,一開始皆為 9 個,而決定下棋位置方式為,透過 `xorshift32()` 隨機生成一數字,除 `n_moves` 並使用函式 `fastmod` 取餘數,最後取得的數值 `i` , `available_moves[i]` 即為下棋的位置。
因此我們知道函式 `fastmod` 中的 `mod7` 為取 7 的餘數, [Hacker's Delight](https://web.archive.org/web/20180517023231/http://www.hackersdelight.org/divcMore.pdf) 中,函式 `rems7` 透過 $8^k \equiv 1 (mod 7)$ 快速計算 $mod 7$ 具體程式如下:
```c
int remu7(int n) {
int r;
static char table[75] = {5,6, 0,1,2,3,4,5,6,
0,1,2,3,4,5,6, 0,1,2,3,4,5,6, 0,1,2,3,4,5,6,
0,1,2,3,4,5,6, 0,1,2,3,4,5,6, 0,1,2,3,4,5,6,
0,1,2,3,4,5,6, 0,1,2,3,4,5,6, 0,1,2,3,4,5,6, 0,1,2};
r = (n >> 15) + (n & 0x7FFF); // FFFF0000 to 17FFE.
r = (r >> 9) + (r & 0x001FF); // FFFFFF80 to 2BD.
r = (r>> 6)+(r&0x0003F); //-2to72(decimal).
return table[r];
}
```
因為 $2^{15} \equiv 1 (mod 7)$ ,所以我們可以將 n 右移 15 位元並且跟 n 從 lsb 開始計算 15 位元相加,其值取餘數後,與 n 取餘數的值相同。後面的 `(r >> 9)` 、 `(r>> 6)` 也是相同的原理。
[Hacker's Delight](https://web.archive.org/web/20180517023231/http://www.hackersdelight.org/divcMore.pdf) 中提另一種取餘數的方法,利用 $n \mod 7 = \lfloor\frac{8}{7}n\rfloor \mod 8$ 的方法取餘數:
```c
int remu7(unsigned n) {
n = (0x24924924*n + (n >> 1) + (n >> 4)) >> 29;
return n & ((int)(n - 7) >> 31);
}
```
透過將 $n*\lfloor\frac{2^{32}}{7}\rfloor$ 之後再往右移 29 位元,即可在省去高為元的同時,取得我們想要的答案。
而井字遊戲中的函式 `mod7` 則是結合兩者:
1. 使用 `x = (x >> 15) + (x & UINT32_C(0x7FFF));`縮小為數的計算。
2. 因為誤差的關係,採用 $n*\lceil\frac{2^{32}}{7}\rceil$ 也就是 `((x * UINT32_C(0x24924925)) >> 29)` 取得餘數。
## 測驗 3:
`treeint.c` 為一個測試程式,分別測試 BST 的插入、搜尋與刪除所花的時間,實作方法為使用結構體 `treeint_ops` 以函式指標的方式將 `treeint_xt.[ch]` 所提供的函式製作程 function hook ,並透過 `ops` 進行函式呼叫。其中,`xtree.[ch]` 負責處理 xtree 的平衡、新增與刪除,由資料型別 `xt_node` 作為樹的節點相連接。而 `treeint_xt.[ch]` 將 `xtree.c` 中的函式包裝成符合程式所需的資料型別 ,也就是定義了 entry 的資料型別 `treeint_st` 與比較函式 `treeint_xt_cmp` 。
`treeint_xt.[ch]` 提供的 5 個函式,分別透過呼叫 `xtree.[ch]` 中對應的函式完成對 BST 的操作。而 `xtree.[ch]` 提供了以下函式:
- `struct xt_tree *xt_create(cmp_t *cmp,
struct xt_node *(*create_node)(void *key),
void (*destroy_node)(struct xt_node *n))`:
作用為初始化 xtree ,並且定義了搜尋、建構或移除節點時所需的函式。
- `void xt_destroy(struct xt_tree *tree)`:
呼叫函式 `__xt_destroy` 使用遞迴得方式移除整棵樹。
- `int xt_insert(struct xt_tree *tree, void *key)`:
透過函式 `__xt_find` 尋找 `key` 在 xtree 中插入的位置,並呼叫 `__xt_insert` 將新節點插入 xtree 中。
- `int xt_remove(struct xt_tree *tree, void *key)`:
與 `xt_insert` 相似,透過函式 `__xt_find` 尋找 `key` 在 xtree 中的位置,呼叫 `__xt_remove` 將節點從 xtree 中移除。
- `struct xt_node *xt_find(struct xt_tree *tree, void *key)`:
透過呼叫 `__xt_find2` 以遞迴的方式尋找 `key` 在 xtree 中的位置並回傳節點指標。
xtree 與 AVL tree 、 Red Black tree 相似,三者皆為[自平衡樹](https://en.wikipedia.org/wiki/Self-balancing_binary_search_tree), xtree 的平衡機制是利用 `hint` 來評估是否需要做 rotate, `xt_node` 的 `hint` 的計算方式是,其左右節點 `hint` 最大值 +1 並且只有在被呼叫到 `xt_update` 時才會被更新。
其中:
- `xt_update`:
透過呼叫 `xt_balance` 判斷左右子節點 `hint` 的差值是否超過 1 ,如果是則 rotate 進行平衡,接著檢查 `hint` 在平衡後是否改變,如果是則往上對親節點進行平衡。
- `xt_rotate_right`:
將節點向右旋轉以平衡 xtree
```graphviz
digraph structs {
node[shape=circle];
node0 [label= "p"];
node1 [label= "n", color = red];
node2 [label= "l"];
node3 [label= "r"];
node4 [label= "ll"];
node5 [label= "lr"];
node0 -> node1;
node1 -> node2;
node1 -> node3;
node2 -> node4;
node2 -> node5;
node[shape=circle];
node7 [label= "p"];
node11 [label= "ll"];
node8 [label= "n", color = red];
node9 [label= "l"];
node12 [label= "lr"];
node10 [label= "r"];
node7 -> node9;
node8 -> node12;
node9 -> node8;
node9 -> node11;
node8 -> node10;
}
```
- `xt_rotate_left`:
將節點向左旋轉以平衡 xtree
```graphviz
digraph structs {
node[shape=circle];
node0 [label= "p"];
node1 [label= "n", color = red];
node2 [label= "l"];
node3 [label= "r"];
node4 [label= "rl"];
node5 [label= "rr"];
node0 -> node1;
node1 -> node2;
node1 -> node3;
node3 -> node4;
node3 -> node5;
node[shape=circle];
node7 [label= "p"];
node9 [label= "l"];
node8 [label= "n", color = red];
node11 [label= "rl"];
node12 [label= "rr"];
node10 [label= "r"];
node7 -> node10;
node10 -> node12;
node8 -> node11;
node8 -> node9;
node10 -> node8;
}
```
### 改進程式:
TODO