# 2024q1 Homework4 (quiz3+4)
contributed by < `LindaTing0106` >
## 第三週測驗題
### 測驗 1
i_sqrt 版本一利用 log2 函式得出 N 的最高有效位元在 msb 。 隨後再迴圈內檢查該 N 為二進位表示時,將在逼近的數字加上 $2^a$ 後將其平方後,不會超過原數字。
藉由此種方式得出 N 的平方根。
- [ ] 版本一
```c
#include <math.h>
int i_sqrt(int N)
{
int msb = (int) log2(N);
int a = 1 << msb;
int result = 0;
while (a != 0) {
if ((result + a) * (result + a) <= N)
result += a;
a >>= 1;
}
return result;
}
```
- [ ] 版本二
將 log2 函式利用迴圈方式將其取代。
```c
int i_sqrt(int N)
{
int msb = 0;
int n = N;
while (n > 1) {
n >>= 1;
msb++;
}
int a = 1 << msb;
int result = 0;
while (a != 0) {
if ((result + a) * (result + a) <= N)
result += a;
a >>= 1;
}
return result;
}
```
i_sqrt 版本三則是利用 Digit-by-digit calculation 的方式,其中先將要開平方根的數 $N$ 用二進位方式表示。
假設 $N = (25)^2$,使其變為 $N = (11001)^2$。
若將其表示位十進位則得 $N = (2^4 + 2^3 +2^1)^2$ ,則要算出 $x$ 就要將其展開,則為 $(a_4 + a_3 + a_2 + a_1 + a_0)^2$,在此 $a_i = 2^i$ or $0$ ,將其利用矩陣展開後可以得其規律。
$a_n^2 + (a_{n-1} +2a_n)a_{n-1} + (a_{n-2}+2(a_{n-1}+a_n))a_{n-2} +...+ (2\sum_{i=1}^{n}a_i+a_0)a_0$
故此處可以假設 $P_m =a_n +a_{n-1}+...+a_{m}$
則 $a_n^2 + (a_{n-1} +2P_n)a_{n-1} + (a_{n-2}+2P_{n-1})a_{n-2} +...+ (2P_{1}+a_0)a_0$ 。
則 $P_m = P_{m+1} + a_m$ 。 $P_m$ 為 $N$ 的平方根,故我們想求出 $P_m$ 的話就需要去知道每個 $a_m$ 為零或不為零,但如果每一輪都將 $N - P_m^2$ 的話將會導致運算成本過高。
- $X_m = N - P_m^2 = X_{m-1} - Y_m$
- $X_{m-1} = N - P_{m+1}^2$
- $Y_m = P_{m}^2 - P_{m+1}^2 = P_{m+1}^2 +2P_{m+1}a_m +a_m^2 - P_{m+1}^2 = 2P_{m+1}a_m +a_m^2$
在這裡將 $Y_m$ 拆解為 $c_m$ 和 $d_m$ ,其中
- $c_m = 2P_{m+1}a_{m} = P_{m+1}2^{m+1}$
- $d_m = a_{m}^2 = (2^{m})^2$
- $Y_m =
\begin{cases}
c_m + d_m , if a_m = 2^m \\
0 , if a_m = 0 \\
\end{cases}$
以此類推
- $c_{m-1} = 2P_m a_{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 , if a_m = 2^m\\
c_m/2 , if a_m = 0 \\
\end{cases}$
- $d_{m-1} = a_{m-1}^2 = (2^{m-1})^2 = \dfrac{(2^m)^2}{2^2} = \dfrac{d_m}{4}$
則在程式進行時,會由 $2^n$ 往下查看 $N$ 中有無 $2^i$ 。
- $X_{n-1} = N$
- $c_n = 0$
- $P_0 = a_n + a_{n-1} +...+ a_0 = c_{-1}$
故可以看出,求出 $c_{-1}$ 則等於求出 $N$ 的平方根為何。
- [ ] 版本三
```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 >>= 2) {
int b = z + m;
z >>= 1;
if (x >= b)
x -= b, z += m;
}
return z;
}
```
確認 x 不為 0 或是其他小於 0 的數。
```c
if (x <= 1) /* Assume x is always positive */
return x;
```
int m 為 $d_m$ ,故第一次進來迴圈時, int m = $d_n = (2^n)^2$。 因為 $(2^n)^2 = 2^{n*2}$ 故 $n*2$ 必不可能為奇數,因此要 `& ~1UL` 。
因為 $d_{m-1} = \dfrac{d_m}{4}$ 故每次迴圈往右位移 2 。
```c
for (int m = 1UL << ((31 - __builtin_clz(x)) & ~1UL); m; m >>= 2) {
```
$Y_m =
\begin{cases}
c_m + d_m , if a_m = 2^m \\
0 , if a_m = 0 \\
\end{cases}$
並且每次得出 $c_{m-1} =
\begin{cases}
c_m/2 +d_m , if a_m = 2^m\\
c_m/2 , if a_m = 0 \\
\end{cases}$ ,
且若 $X_m >= Y_m$ 則減去 $Y_m$ 得 $X_{m-1}$。
```c
int b = z + m;
z >>= 1;
if (x >= b)
x -= b, z += m;
```
#### ffs 取代 `__builtin_clz` 實作
在每一次迴圈中,使用 ffs 得到 temp 中最低有效位元 var 後,將其右移 var ,一直重複此操作,最終可以得到其最高有效位元的位置。但還需要將其減 1 才會與 `(31 - __builtin_clz(x))` 相等。
```c
int msb = 0;
for (int temp = x; temp; ){
int var = ffs(temp);
msb += var;
temp >>= var;
}
for (int m = 1UL << ((msb - 1) & ~1UL); m; m >>= 2)
```
在迴圈內使用 mask 取代原先條件句。
```c
int mask = -(x >= b);
x -= mask & b;
z += mask & m;
```
> [commit aa272b4](https://github.com/LindaTing0106/linux2024-homework/commit/aa272b4edabcf3f2627b9fa3425278602d6d5d84)
### 測驗 2
一般在計算商數和餘數時,我們都會直接使用 / 、 % 對其進行運算,如此造成運算成本較高的後果。
```c
static void str_add(char *b, char *a, char *res, size_t size)
{
int carry = 0;
for (int i = 0; i < size; i++) {
int tmp = (b[i] - '0') + (a[i] - '0') + carry;
carry = tmp / 10;
tmp = tmp % 10;
res[i] = tmp + '0';
}
}
```
因此採用 bitwise operation 來實作除法,但可以發現當我們希望將 n 除以 10 時,無法直接使用 shift 指令完成,因為 10 並不能直接使用 $2 ^ i$ 的方式來表示。因此我們需要利用 n / x 來完成除以 10 的動作。
但我們應該要先證明其除以 x 後,應該在小數點後第一位時的精確度都是正確的。
故設
- $n = ab$
- $l = cd$
- 且 $n \ge l$
$\dfrac{ab}{10} \le \dfrac{n}{x} \le \dfrac{ab.9}{10}$ ,確保 n 除以 x 後其跟 n 除以 10 直到小數點第一位時的精確度都是正確的。
$\dfrac{ab}{10} \le \dfrac{n}{x} \le \dfrac{ab.9}{10} \Rightarrow a.b \le \dfrac{n}{x} \le a.b9 \Rightarrow \dfrac{n}{a.b9} \le {x} \le \dfrac{n}{a.b}$
,由於 $n = ab$ ,故 $\dfrac{n}{a.b9} \le x \le 10$。
$\dfrac{cd}{10} \le \dfrac{l}{x} \le \dfrac{cd.9}{10} \Rightarrow \dfrac{cd}{10} \le l * \dfrac{a.b9}{n} \le \dfrac{cd.9}{10}
\Rightarrow \dfrac{cd}{10} \le cd * \dfrac{a.b9}{ab} \le \dfrac{cd.9}{10}$
而 $\dfrac{a.b9}{ab} =\dfrac{a.b + 0.09}{ab}$ ,故
$cd * \dfrac{a.b9}{ab} = cd * (\dfrac{a.b}{ab} + \dfrac{0.09}{ab}) = cd * \dfrac{1}{10} + cd * \dfrac{0.09}{ab} = c.d + cd * \dfrac{0.09}{ab}$
故 $\dfrac{cd}{10} \le c.d + cd * \dfrac{0.09}{ab}$ , 而因為 $cd < ab$ ,故 $c.d + cd * \dfrac{0.09}{ab} \le \dfrac{cd.9}{10}$。
故可以保證 $n / x$ 其在小數點後第一位的精確度。
因為被除數的大小不會超過 19 ,所以我們接著直接用 19 來做討論。
故從上面式子,我們可以帶入 $n = 19$ :
$\dfrac{19}{10}
\le \dfrac{19}{x} \le \dfrac{19.9}{10}
\Rightarrow \dfrac{19}{1.99}
\le x \le \dfrac{19}{1.9}
\Rightarrow 9.54
\le x \le 10$
故我們只要找到一 $x$ 其在此範圍內,便可保證將某數除以 $x$ 時,其到小數點後第一位的精確度都會是正確的。
在這邊老師用了 $\dfrac{128}{13} \approx 9.85$ 這個數字來當作 x 。
故將 n 除以 $\dfrac{128}{13}$ ,即可取代除以 10 ,而 n 除以 $\dfrac{128}{13}$ 同時也等同於 n * $\dfrac{13}{128}$ 。因此我們要想辦法用 bitwise operation 操作出 $\dfrac{13}{128}$ 。
```c
((((tmp >> 3) + (tmp >> 1) + tmp)
```
得出 $\dfrac{tmp}{8} + \dfrac{tmp}{2} + tmp = \dfrac{13tmp}{8}$ 。
```c
((((tmp >> 3) + (tmp >> 1) + tmp) << 3
```
往左位移 3 讓 $\dfrac{13tmp}{8} * 8 = 13tmp$
為了避免在向右位移時導致前面 3 個位元丟失,先用 mask 的方式將位元暫存下來。
```c
d0 = q & 0b1;
d1 = q & 0b11;
d2 = q & 0b111;
```
```c
q = ((((tmp >> 3) + (tmp >> 1) + tmp) << 3) + d0 + d1 + d2)
```
:::info
但這裡不太懂的是 d2 應該是為了避免 (tmp >> 3) 丟失的位元, d0 則是應該是為了避免 (tmp >> 1) 丟失的位元,那為什麼需要加 d1 呢?
:::
接著再將 $13tmp / 128$ ,故往右位移 7 。
```c
q = ((((tmp >> 3) + (tmp >> 1) + tmp) << 3) + d0 + d1 + d2) >> 7
```
最後為了要算出 mod 10 ,將 tmp 減 ( div 10 的結果乘以 10 )。
```c
r = tmp - (((q << 2) + q) << 1);
```
最終可將程式包裝為
```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));
}
```
在這邊我們會先把 $in | 1$ ,之後再將其減掉 $in >> 2$ ,如此我們可以得到 $x = \dfrac{in3}{4}$ 。
```c
uint32_t x = (in | 1) - (in >> 2);
```
:::info
in | 1 是在 in 為偶數時,會使 in + 1 ,其用意為?
:::
$q = \dfrac{in3}{64} + \dfrac{in48}{64} = \dfrac{in102}{128}$ 。
而 $\dfrac{102}{128} \approx 0.8 = \dfrac{8}{10}$ 。
```c
uint32_t q = (x >> 4) + x;
```
此處用來使 $\dfrac{102}{128}$ 更接近 $0.8$。
```c
x = q;
q = (q >> 8) + x;
q = (q >> 8) + x;
q = (q >> 8) + x;
q = (q >> 8) + x;
```
:::info
如果不進行逼近的話,會導致結果有錯嗎?
:::
將 $\dfrac{8in}{10}$ 變為 $\dfrac{in}{10}$ 。
```c
*div = (q >> 3);
```
最後為了要算出 mod 10 ,將 in 減 ( div 10 的結果乘以 10 )。
由於 (q & ~0x7) = q & ~0..0111 = q & 1..1000 。也就等於將 *div << 3 。
故為要湊出 *div $* 10$ 。故將 *div << 3 + *div << 1 。
```c
*mod = in - ((q & ~0x7) + (*div << 1));
```
### 測驗 3
ilog2 版本一是用來計算以 2 為底的對數,原先的版本為利用將二進位的數字往右位移 1 ,則表示其可以除以 2 一次,重複以上算式,直到數字被除至 0 。
```c
int ilog2(int i)
{
int log = -1;
while (i) {
i >>= 1;
log++;
}
return log;
}
```
但此函式的缺點為,此數字的最高 set bit 的位置在 n 處,該迴圈就應該要執行 n 次。
故將其改寫為 ilog2 版本二,
若有一數為 $2^{32}$ ,則在原本的程式中需要執行 32 次,但在改寫完的程式中只須執行 2 次。因此可省下原本需要的時間。
```c
while (i >= 2^16) {
result += 16;
i >>= 16;
}
while (i >= 2^8) {
result += 8;
i >>= 8;
}
while (i >= 2^4) {
result += 4;
i >>= 4;
}
while (i >= 2) {
result += 1;
i >>= 1;
}
```
亦可寫為 ilog2 版本三, builtin_clz 用以得出一二進位數字其最高有效位元前,共有多少個 0 ,在利用 31 減去此數後,則可以得到其為 $2^i$ 。而需要傳入 v | 1 則是為了避免 v 為 0 時,由於 builtin_clz 對於 0 未定義導致錯誤的情況發生。
```c
int ilog32(uint32_t v)
{
return (31 - __builtin_clz(v | 1));
}
```
### 測驗 4
指數加權移動平均為一種在取平均的手段,其特點為可以調整歷史資料加權降低的程度。其公式為 $S_t = \begin{cases}
Y_0 , t = 0 \\
\alpha Y_t +(1 - \alpha)S_{t - 1}, t > 0 \\
\end{cases}$ ,其中 $\alpha$ 表示歷史資料加權降低的程度, $Y_t$ 表示在 $t$ 時獲得的新資料, $S_t$ 表示在 $t$ 時算出的指數加權移動平均。將以上式子展開後可得
$$
S_t = \alpha Y_t + (1- \alpha) \alpha Y_{t - 1} +(1- \alpha)^2 \alpha Y_{t - 2} + ... + (1 - \alpha)^i \alpha Y_{t - i} + ... + (1 - \alpha)^t \alpha Y_0
$$
在程式中,利用 factor 去縮放新傳進來的資料, weight 則是表示歷史資料加權降低程度。
### 測驗 5
實作出 ceil_ilog2 ,在最一開始處先將 x - 1 ,此目的為最終可以直接無條件進位,下面 `r = (x > 0xFFFF) << 4;` 則是若 $x > 2^{16}$ 則將 $1 << 4$ 並傳給 r ,而後再讓 x 去向右位移 r ,最後是為了判斷 x 是否是一個大於 0 的數字,若 x 大於 0 則其 ceil_ilog2 必定要加一。
#### 改進程式碼,使其得以處理 x = 0 的狀況,並仍是 branchless
利用 x 減去 (x > 0) ,使當 x = 0 的情況下不會減到 1 , x 也就不會變為 0xFFFF_FFFF ,最後在原本加 1 處改為加 (原本的 x >= 1) ,讓 x 為 0 時不會加 1 。
>[commit 1fd5a21](https://github.com/LindaTing0106/linux2024-homework/commit/1fd5a217f79c5174b529c6fb71245abfaed87c55)
---
## 第四週測驗題
### 測驗 1
在 popcount_naive 函式中的缺點為,如果 v 中有多少個 set bit ,則程式就需要執行多少次。故引入了 popcount_branchless 的寫法,利用 $x - \dfrac{x}{2} - \dfrac{x}{4} - \dfrac{x}{8}$ 可以知道在 x[3:0] 範圍內 1 的數量。
推導為:設 $x = 2^3b_3 + 2^2b_2 + 2^1b_1 +2^0b_0$ ,其中 $b_i = 0$ or $1$ 。
則 $x - \dfrac{x}{2} - \dfrac{x}{4} - \dfrac{x}{8}$ = $2^3b_3 + 2^2b_2 + 2^1b_1 +2^0b_0 - 2^2b_3 + 2^1b_2 + 2^0b_1 - 2^1b_3 + 2^0b_2 - 2^0b_3$ 。
整理後為 $(2^3 - 2^2 - 2^1 - 2^0)b_3 + (2^2 - 2^1 - 2^0)b_2 + (2^1 - 2^0)b_1 +(2^0)b_0$。
故可以發現在 $b^3$ 為 1 的時候 $(2^3 - 2^2 - 2^1 - 2^0)b_3$ 也會為 1 ,以此類推。
故可以得知確實可以利用 $x - \dfrac{x}{2} - \dfrac{x}{4} - \dfrac{x}{8}$ 得知 4 bits 範圍內的 1 的 bits 數。
```c
unsigned n;
n = (v >> 1) & 0x77777777;
v -= n;
n = (n >> 1) & 0x77777777;
v -= n;
n = (n >> 1) & 0x77777777;
v -= n;
```
利用此段程式碼我們可以得到此圖。
```
b_31 b_30 b_29 b_28 ... b7 b6 b5 b4 b3 b2 b1 b0 // v
0 b_31 b_30 b_29 ... 0 b7 b6 b5 0 b3 b2 b1 // (v >> 1) & 0x77777777
0 0 b_31 b_30 ... 0 0 b7 b6 0 0 b3 b2 // (n >> 1) & 0x77777777
0 0 0 b_31 ... 0 0 0 b7 0 0 0 b3 // (n >> 1) & 0x77777777
```
也可以得到此算式 $v - \dfrac{v}{2} - \dfrac{v}{4} - \dfrac{v}{8}$ 。
- 假設 `v = 32'b0100_1111_0000_0100_0111_0001_0001_1100`
- 則運算完的結果會為 `v = 32'b0001_0100_0000_0001_0011_0001_0001_0010` ,但可以看出我們應該要將每個 nibble 的位置調整好,相加後才是 1 的總數。
```c
v = (v + (v >> 4)) & 0x0F0F0F0F
```
假設 $B_n$ 代表第 n 個 nibble (4 位元) 中的數值
```c
B7 B6 B5 B4 B3 B2 B1 B0 // v
0 B7 B6 B5 B4 B3 B2 B1 // (v >> 4)
```
```c
B7 (B7+B6) (B6+B5) (B5+B4) (B4+B3) (B3+B2) (B2+B1) (B1+B0) // (v + (v >> 4))
```
```c
0 (B7+B6) 0 (B5+B4) 0 (B3+B2) 0 (B1+B0) // (v + (v >> 4)) & 0x0F0F0F0F
```
- 則繼續使用剛才的例子 `v = 32'b0001_0100_0000_0001_0011_0001_0001_0010`
- 經過此式子後`v = 32'b0000_0101_0000_0001_0000_0100_0000_0011` 。
```c
v *= 0x01010101
```
此處的 $A_i$ 為上面剛計算完的 $B_{i+1}+B_i$
```c
0 A6 0 A4 0 A2 0 A0
x 0 1 0 1 0 1 0 1
---------------------------------------------------
0 A6 0 A4 0 A2 0 A0
0 A6 0 A4 0 A2 0 A0 0
0 A6 0 A4 0 A2 0 A0 0
0 A6 0 A4 0 A2 0 A0 0
---------------------------------------------------
↑_______________________A6+A4+A2+A0
```
- `v = 32'b0000_0101_0000_0001_0000_0100_0000_0011`
```c
0 0101 0 0001 0 0100 0 0011
x 0 1 0 1 0 1 0 1
-------------------------------------------------------
0 0101 0 0001 0 0100 0 0011
0 0101 0 0001 0 0100 0 0011
0 0101 0 0001 0 0100 0 0011
0 0101 0 0001 0 0100 0 0011
-------------------------------------------------------
↑_______________________每個 nibble 加總起來的地方
```
- `v = 32'b0000_1101_0000_xxxx_0000_xxxx_0000_xxxx`
可以看到我們所求的數字就位於 v[31:24] 處,故最後將其向右為移即可結束。
```c
return v >> 24
```
- `return 32'b..._0000_1101`。
```c
unsigned popcount_branchless(unsigned v)
{
unsigned n;
n = (v >> 1) & 0x77777777;
v -= n;
n = (n >> 1) & 0x77777777;
v -= n;
n = (n >> 1) & 0x77777777;
v -= n;
v = (v + (v >> 4)) & 0x0F0F0F0F;
v *= 0x01010101;
return v >> 24;
}
```
在與算出 hammingDistance 有關的題目中,可以利用 popcount 計算兩數有幾個不相同的值,而最後回傳回去時需要右移 1 ,是因為此處會重複運算到一次,所以需要除以 2 也就是右移 1 。
```c
int totalHammingDistance(int* nums, int numsSize)
{
int total = 0;;
for (int i = 0;i < numsSize;i++)
for (int j = 0; j < numsSize;j++)
total += __builtin_popcount(nums[i] ^ nums[j]);
return total >> 1;
}
```
#### 使用位元方式找尋 Total Hamming Distance
用位元的位置去看,在 x 位元時, nums 中有幾個 1 ,從此處也可以得出 0 的數量,把 1 數量乘上 0 的數量,即是當前位元位置的 Hamming Distance ,最後把所有位置的 Hamming Distance 都加總起來後即是 Total Hamming Distance 。
>[commit 840a409](https://github.com/LindaTing0106/linux2024-homework/commit/840a4099e8f891888c1b4fccdfb30fd97ab1d4e2)
### 測驗 2
由於 $2^k =
\begin{cases}
1(mod3), k even \\
-1(mod3), k odd\\
\end{cases}$ , 故 $n = b_{n-1}b_{n-2}...b_{2}b_{1}b_{0}$ ,而 $b_i = 1$ ,若 i 為奇數,則 n mod 3 的結果會 -1 。
故 $n (mod 3) = \sum_{0}^{n-1} b_i *(-1)^i (mod3)$ 。
可以看出我們只要將 $b_i$ 在偶數的位元數減去 $b_i$ 在奇數的位元數即可得到 n 。
故得出此式 `n = popcount(n & 0x55555555) - popcount(n & 0xAAAAAAAA)` 。
在 `tic-tac-toe` 程式碼中,進行一個 3x3 大小的圈圈叉叉,由於大小為 3x3 故總操作數只會有 9 次。此時的操作為每次從陣列中選出一個可以操作的位置,選擇完後將本次選擇的位置,從陣列中移除。並將其給 `board | move_masks[move]` ,隨後去檢查該名玩家是否勝利。
```c
static const uint32_t move_masks[9] = {
0x40040040, 0x20004000, 0x10000404,
0x04020000, 0x02002022, 0x01000200,
0x00410001, 0x00201000, 0x00100110,
};
```
若左邊有一條直線達成連線則 `player_board = 0x44470041` 。
若中間有一條直線達成連線則 `player_board = 0x22207022` 。
若右邊有一條直線達成連線則 `player_board = 0x11100714` 。
若上面有一條橫線達成連線則 `player_board = 0x70044444` 。
若中間有一條橫線達成連線則 `player_board = 0x07022222` 。
若右邊有一條橫線達成連線則 `player_board = 0x00711111` 。
若左上有一條斜線達成連線則 `player_board = 0x42142172` 。
若右上有一條橫線達成連線則 `player_board = 0x12412427` 。
可以看出只要 player_board 在 16 進位的表示下,只要有其中一個位元為 7 則表示勝利。
故在此段程式碼中將每個位元都加 1 ,再將每個位元都 & 8 ,如果回傳不為 0 則表示此為玩家勝利。
```c
static inline uint32_t is_win(uint32_t player_board)
{
return (player_board + 0x11111111) & 0x88888888;
}
```
### 測驗 3
在 Makefile 中可以看到當執行 make check 命令時,其會去對 treeint 檔進行編譯,可以從其中看到 insert, find, remove 等操作的執行時間。而 treeint 呼叫的函式是在 treeint_xt 定義。
- `treeint_xt_destroy` 函式中會呼叫 xtree 中的 `xt_destroy`
- 當 `xt_root` 存在時,會再進入 `__xt_destroy`,使用遞迴方式將樹的左右子樹刪除,最終再刪除自己本身。
- `treeint_xt_insert` 函式中會呼叫 xtree 中的 xt_insert ,在函式中會呼叫 `__xt_find`,去尋找應該 insert 至何處,如果找到此值已經在樹中了則不用新增,反之找到該新增位置後,就將節點插入進去。
- `treeint_xt_remove` 函式中會呼叫 xtree 中的 xt_remove , 在函式中會呼叫 `xt_find` ,但 `xt_find` 不同於 `__xt_find`, xt_find 將不會回傳位置,其目的只是在於確認欲刪除的節點確實存在。
- 如果欲刪除的節點有右子樹:將要被刪除的節點用 xt_replace_right 替換為右子樹中第一個遇到的節點,也就是右子樹中最左邊的節點。
- 如果欲刪除的節點有左子樹:將要被刪除的節點用 xt_replace_left 替換為左子樹中最後一個遇到的節點,也就是左子樹中最右邊的節點。
* `xt_replace_right`
* `xt_replace_left`