# 2024 Homework4 (quiz3+4) ## 第三周測驗 [2024q1 第 3 週測驗題](https://hackmd.io/@sysprog/linux2024-quiz3) ### 測驗 `1` : Using bitwise operations to compute square root 先看原程式碼 ```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; } ``` 編譯方式 ``` $ gcc -o i_sqrt i_sqrt.c -lm ``` > 因為呼叫 log2,所以需要連結 libm (math library),即 -lm 選項 假設 N = $36_{10}$ =$100100_2$,`msb = 5` => a = $100000_2$ 接著進入迴圈中 | 迴圈次數 | a | result |是否進入迴圈| | -------- | -------- | -------- |- | | 1 | $100000_2$ | 0 |y | | 2 | $10000_2$ | 0 |y | | 3 | $1000_2$ | 0 |y | | 4 | $100_2$ | $100_2$ |y | | 5 | $10_2$ | $110_2$ |y | | 6 | $1_2$ | 0 |n | 在此 `bitwise` 操作中, `(result + a)` 的平方逐一比較 `N` 值,當值較小時就疊加當前的值,即 `result += a` ,直到為 N 的開方根值。 > 此題一開始用十進位去看比較容易明白, `a >>= 1` 看作 `a / 2`。 編譯方式:`$ gcc -o i_sqrt i_sqrt.c -lm` 版本二使用右移操作直到 n 值為 0 ,藉此計算 `msb` ```clike int msb = 0; int n = N; while (n > 1) { n >>= 1; msb++; } ``` 版本三使用 [Digit-by-digit calculation](https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Digit-by-digit_calculation) 方法進行開方根。將平方數拆成 2 的冪相加 令 $x=N^2=(a_n+a_{n-1}+...+a_0)^2$ ,其中 N 即為所求的開方根 $P_m=a_n+a_{n-1}+...+a_m$ $P_0=a_n+a_{n-1}+...+a_0=N$ 即為所求 推論出: $P_m=P_{m+1}+a_m$ 後續將求出所有 $a_m=2^m$ or $0$,從 $m = n$ 一直到 $m = 0$,而求得 $a_m$ 只須比較$P_m^2 \le N^2$,有兩種情況: \begin{cases} P_m=P_{m+1}+2^m \ \ ;\ P_m^2 \le N^2\\ P_m=P_{m+1}\ \ \ \ \ \ \ \ \ \ \ \ ;\ otherwise \end{cases} 由於每輪逐一比較的運算成本過高,提出了另個想法 將$N^2-P_m^2$ 的值表示位$X_m$ \begin{cases} X_m=N^2-P_m^2--(1) \\ X_{m+1}=N^2-P_{m+1}^2--(2) \end{cases} 將(1)式減(2)式可得 $X_m-X_{m+1}=-P_m^2+P_{m+1}^2--(3)$ 其中 $P_m$ 平方為 $P_m^2=P_{m+1}^2+2a_mP_{m+1}+a_m^2=P_{m+1}^2+(2P_{m+1}+a_m)a_m$ 令 $Y_m= P_m^2-P_{m+1}^2=(2P_{m+1}+a_m)a_m$ 最後將 $Y_m$ 代回到(3)式可得: $X_m=X_{m+1}-Y_m$ 將 $Y_m$ 拆解 $c_m,d_m$ $c_m=P_{m+1}2^{m+1}$ $d_m=(2^m)^2$ 得: \begin{cases} Y_m=c_m+d_m\ ;if\ a_m=2^m\\ Y_m=0\ \ \ \ \ \ \ \ \ \ \ \ \ \ ;if\ a_m=0 \end{cases} 藉由位元運算從$c_m,d_m$推出下一輪 $c_{m-1}=P_m2^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}=d_m/4$ **Linux 核心原始程式碼** [linux/block/blk-wbt.c](https://github.com/torvalds/linux/blob/39cd87c4eb2b893354f3b850f916353f2658ae6f/block/blk-wbt.c#L9) ```c static void rwb_arm_timer(struct rq_wb *rwb) { struct rq_depth *rqd = &rwb->rq_depth; if (rqd->scale_step > 0) { /* * We should speed this up, using some variant of a fast * integer inverse square root calculation. Since we only do * this for every window expiration, it's not a huge deal, * though. */ rwb->cur_win_nsec = div_u64(rwb->win_nsec << 4, int_sqrt((rqd->scale_step + 1) << 8)); } else { /* * For step < 0, we don't want to increase/decrease the * window size. */ rwb->cur_win_nsec = rwb->win_nsec; } blk_stat_activate_nsecs(rwb->cb, rwb->cur_win_nsec); } ``` 尚未完成 ### 測驗 `2` : Using bitwise operations to perform mod 10 and div 10 參考《[Hacker's Delight](https://web.archive.org/web/20180517023231/http://www.hackersdelight.org/divcMore.pdf)》,採用 bitwise operation 來實作除法 $n\ \dfrac{a}{2^N}$ 此題選用 a=13 N=7 => $n\ \dfrac{13}{2^7}$ ,其中 $\dfrac{13}{2^7}$ 為 $0.10156$ 。 完整程式碼 ```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); ``` 將 $tmp\times \dfrac{13}{2^7}$ 改寫成 $tmp\times 13 \div 16 \div 8$,其中 $tmp\times \dfrac{13}{8}\times 8=13tmp$ 可從下方程式碼得到 ```c (((tmp >> 3) + (tmp >> 1) + tmp) << 3) ``` 由於 `tmp` 右移三位會遺失原本最低三位的 bit ,因此將分別用 `d0` 、 `d1` 、 `d2` 來儲存原先的 bit ,再將遺失的 bit 補回來,最後在除以128,即為 $\dfrac{13}{128} tmp$,得到一個近似 $tmp\div 10$ 的結果 ( `q` )。 ```c q = ((((tmp >> 3) + (tmp >> 1) + tmp) << 3) + d0 + d1 + d2) >> 7; ``` :::info 上述程式碼中不太理解為何補足遺失的最低三位 bit 是加上 `+ d0 + d1 + d2` ,在我實作中發現就算缺少這段程式碼也不會影響其結果,其大致的觀念我能理解是為了增加其精度,但又為何不直接加上 `+ d2` 就好。 ::: 下方程式碼根據餘數定理:$f-g\times Q=r$,可以得到 $r$ (餘數) - $f$ : 被除式 - $g$ : 除式 - $Q$ : 商 - $r$ : 餘數 ```c r = tmp - (((q << 2) + q) << 1); ``` 其中`(((q << 2) + q) << 1)` 等於 $(q\times 4+q)\times2=q\times 10$ 將程式碼包裝成 ```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 >> 3); *mod = in - ((q & ~0x7) + (*div << 1)); } ``` `uint32_t x = (in | 1) - (in >> 2)` 得到 $\dfrac{3}{4}in$ `uint32_t q = (x >> 4) + x;` 得到 $q=\dfrac{3}{4}in\div 16+\dfrac{3}{4}in=\dfrac{51}{64}in$ ,其中 $\dfrac{51}{64}in$約為 $0.796875in$ 近似 $\dfrac{8}{10}$ 再經由 bitwise 操作使 0.796875 更接近 0.8 ```c q = (q >> 8) + x; // q = 0.799987793 q = (q >> 8) + x; // q = 0.799999952 q = (q >> 8) + x; // q = 0.799999998 q = (q >> 8) + x; // q = 0.8 ``` 最後 `*div = (q >> 3);` 即為 $*div=\dfrac{8}{10}in\div 8=\dfrac{1}{10}in$ `((q & ~0x7) + (*div << 1));` 為 $\dfrac{8}{10}in+\dfrac{1}{10}in\times 2=\dfrac{in}{10}\times10$,其中 $\dfrac{in}{10}$ 為商。 在由餘式定理:$f-g\times Q=r$ 求得 `*mod` 值為何 **練習撰寫不依賴任何除法指令的 % 9 (modulo 9) 和 % 5 (modulo 5) 程式碼** 尚未進行 ### 測驗 `3` : Using bitwise operations to calculate the logarithm base 2 用 `ilog2` 計算以 2 為底的對數,且其輸入和輸出皆為整數。 稍微改寫一下原程式碼方便識讀,舉例 $i = 4_{10} = 100_2$ | while ( i >= 2 ) | 原 i | 後 i | log | | -------- | -------- | -------- | - | | 第 1 次迴圈 | 100 | 10 | 1 | | 第 2 次迴圈 | 10 | 1 | 2 | 輸出 `log = 2` ```diff int ilog2(int i) { - int log = -1; + int log = 0; - while (i){ + while (i >= 2) { i >>= 1; log++; } return log; } ``` 改寫後的程式碼,將 `i` 用一定數量的位元去進行判斷,有效提升數字較大時的計算速度,其原因為原程式碼每個位元逐一去判斷,反覆進行多次的迴圈,改後後判斷分成 4 個階段,若 `i` 大於 $2^{16}、2^8、2^4、2^1$則直接進行位元操作。 ```c static size_t ilog2(size_t i) { size_t result = 0; while (i >= 65536) { result += 16; i >>= 16; } while (i >= 256) { result += 8; i >>= 8; } while (i >= 16) { result += 4; i >>= 4; } while (i >= 2) { result += 1; i >>= 1; } return result; } ``` **在 Linux 核心原始程式碼找出 log2 的相關程式碼 (或類似的形式),並解說應用案例** 看到 log2 為底的形式就想到 `clz` 、 `ffs` 、 `fls` 等相關的操作都有類似的手法。 在 [linux/arch/arc/include/asm/bitops.h](https://github.com/torvalds/linux/blob/026e680b0a08a62b1d948e5a8ca78700bfac0e6e/arch/arc/include/asm/bitops.h#L28) 中找到 fls 的實作,其作用為返回最高有效 bit 位(由最低位往左數),當判斷式為 0 進行位移操作,其手法相似 log2 ,不同於位元操作向左進行。 該順序不同於 `log2` , `constant_fls` 從 1 起始,當沒有有效位返回 0 。 ```c static inline int constant_fls(unsigned int x) { int r = 32; if (!x) return 0; if (!(x & 0xffff0000u)) { x <<= 16; r -= 16; } if (!(x & 0xff000000u)) { x <<= 8; r -= 8; } if (!(x & 0xf0000000u)) { x <<= 4; r -= 4; } if (!(x & 0xc0000000u)) { x <<= 2; r -= 2; } if (!(x & 0x80000000u)) r -= 1; return r; } ``` ### 測驗 `4` : Using bitwise to calculate EWMA > 參考 [stevendd543](https://hackmd.io/@stevennnnnn/linux2024-homework4#%E7%AC%AC%E5%9B%9B%E9%A1%8C)、[2024q1 第 3 週測驗題](https://hackmd.io/@sysprog/linux2024-quiz3#%E6%B8%AC%E9%A9%97-4) **Exponentially Weighted Moving Average** 賦予資料指數衰減的權重,主要目的使越舊的資料權重越低,反之越新的資料對整體平均值的影響越大,以下為此統計手法的公式: $$ S_t = \begin{cases} Y_0 \quad\quad &t=0 \\ \alpha Y_t + (1-\alpha) \cdot S_{t-1} \quad\quad &t>0 \end{cases} $$ - $\alpha$ 表示歷史資料加權降低的程度,介在 0 ~ 1 之間,越高的 $\alpha$ 會使歷史資料減少的越快 - $Y_t$表示時間 t 時的資料點 - $S_{t}$ 表示時間 t 時的 EWMA ```c void ewma_init(struct ewma *avg, unsigned long factor, unsigned long weight) { if (!is_power_of_2(weight) || !is_power_of_2(factor)) assert(0 && "weight and factor have to be a power of two!"); avg->weight = ilog2(weight); avg->factor = ilog2(factor); avg->internal = 0; } ``` `@internal` : 看作 $t$ 當前的位置 `@factor` : 縮放因子,必須是 2 的冪次 `@weight` : 資料的權重,在這 $\alpha=2^{avg->weight}$ 透過右移來達到$\alpha=2^{-(avg->weight)}=\dfrac{1}{2^{avg->weight}}$,必須是 2 的冪次 ```c struct ewma *ewma_add(struct ewma *avg, unsigned long val) { avg->internal = avg->internal ? (((avg->internal << weight) - avg->internal) + (val << avg->factor)) >> avg->weight : (val << avg->factor); return avg; } ``` 一開始 $t=0$ ,因此 $S_t=Y_0$ 對應到 `avg->internal = (val << avg->factor);` ,接著 `avg->internal = (((avg->internal << avg->weight) - avg->internal) + (val << avg->factor)) >> avg->weight` 的理解較為困難,看起來與題意的公式不同,在 [stevendd543](https://hackmd.io/@stevennnnnn/linux2024-homework4#%E7%AC%AC%E5%9B%9B%E9%A1%8C) 筆記中的舉例有非常清楚解釋: 令 $\alpha\alpha^{-1}=1$,將公式改寫成 $\alpha^{-1}S_t=\alpha^{-1}(\alpha Y_t+(1-\alpha)S_{t-1})$ $\ \ \ \ \ \ \ \ \ \ =Y_t+(\alpha^{-1}-1)S_{t-1}$ 其中 $(\alpha^{-1}-1)S_{t-1}$ 等效於 `((avg->internal << avg->weight) - avg->internal)`,$Y_t$ 等效於 `(val << avg->factor)` ,最後將整個公式 `>> avg->weight` 即為 $\alpha^{-1}S_t\times \alpha=S_t$ (當時 $t$ 的EWMA)。 ```c unsigned long ewma_read(const struct ewma *avg) { return avg->internal >> avg->factor; } ``` 將計算的平均值除以縮放因子而得到真正的平均值。 **在 Linux 核心原始程式碼找出 EWMA 的相關程式碼 (或類似的形式),至少該涵蓋無線網路裝置驅動程式** ### 測驗 `5` : Test 3 Extension 延伸測驗 3,已知輸入必為大於 0 的數值 (即 x > 0),以下程式碼可計算 [log2] ,也就是 [ceil](https://man7.org/linux/man-pages/man3/ceil.3.html) 和 log2 的組合並轉型為整數。 `x--` 目的為避免 `x` 為 2 的冪,接著使用類似測驗 `3` 的作法,在指定的位元寬度中比較,若條件成立就會進行位元操作,下方程式碼寫的更加複雜因此實際帶入數值去進一步解釋。 ```c int ceil_ilog2(uint32_t x) { uint32_t r, shift; x--; r = (x > 0xFFFF) << 4; x >>= r; shift = (x > 0xFF) << 3; x >>= shift; r |= shift; shift = (x > 0xF) << 2; x >>= shift; r |= shift; shift = (x > 0x3) << 1; x >>= shift; return (r | shift | x > 1) + 1; } ``` case `1` x = 0000 0000 0000 0000 0000 1000 0000 0000 ``` x-- = 0000 0000 0000 0000 0000 0111 1111 1111 r = (x > 0xFFFF) << 4 = 0 << 4 = 0 x = x >> r = 0000 0000 0000 0000 0000 0111 1111 1111 shift = (x > 0xFF) << 3 = 1 << 3 = 8 x >>= shift = 0000 0000 0000 0000 0000 0000 0000 0111 r |= shift = 8 shift = (x > 0xF) << 2 = 0 << 2 = 0 x >>= shift = 0000 0000 0000 0000 0000 0000 0000 0111 r |= shift = 8 shift = (x > 0x3) << 1 = 1 << 1 = 2 x >>= shift = 0000 0000 0000 0000 0000 0000 0000 0001 return (r | shift | x > 1) + 1 = return (11) ``` - 透過 `shift = (x > 0xFF) << 3` 決定 `x` 要右移的大小 - `r |= shift` 等效 `r += shift` ,用來記錄由第 0 bit 往左數到 msb 的有效位 - 最後結果把先前 `x--` 的值加回來 **改進程式碼,使其得以處理 x = 0 的狀況,並仍是 branchless** ```diff int ceil_ilog2(uint32_t x) { uint32_t r, shift; + bool mark = x; + x -= !!x - x--; r = (x > 0xFFFF) << 4; x >>= r; shift = (x > 0xFF) << 3; x >>= shift; r |= shift; shift = (x > 0xF) << 2; x >>= shift; r |= shift; shift = (x > 0x3) << 1; x >>= shift; - return (r | shift | x > 1) + 1; + return (r | shift | x > 1) + mark; } ``` 當 `x = 0` 時 `ceil_ilog2` 應要回傳 0 ,因此設立一個 `mark` 來紀錄最後是否 +1 。 **Linux 核心原始程式碼** 尚未完成 ## 第四周測驗 [2024q1 第 4 週測驗題](https://hackmd.io/@sysprog/linux2024-quiz4#%E6%B8%AC%E9%A9%97-1) ### 測驗 `1` : Total Hamming Distance 閱讀 [population count](https://en.wikichip.org/wiki/population_count#google_vignette) 了解計算二進位表示式中有多少個位元為 `1` 、 了解 [Hamming distance](https://en.wikipedia.org/wiki/Hamming_weight) 所代表的意思。 更進階的操作參照[測驗1](https://hackmd.io/@sysprog/linux2024-quiz4#%E6%B8%AC%E9%A9%97-1),大致內容為每4個位元為一個單位,求出其中的 set bits 個數,32位元中總共有 8 組,將每組算得的 set bits 個數加總在一起,裡面的推導過程需詳讀就能得知最後為何 `v >> 24` 即為 popcount 。 考題針對 [LeetCode 477. Total Hamming Distance](https://leetcode.com/problems/total-hamming-distance/description/) ```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; } ``` Leetcode 範例描述到 ``` Input: nums = [4,14,2] The answer will be: HammingDistance(4, 14) + HammingDistance(4, 2) + HammingDistance(14, 2) = 2 + 2 + 2 = 6. ``` 在考題程式碼中為兩兩成對的去計算 popcount ,因此會出現HammingDistance(4, 14)與HammingDistance(14, 4)這種情況,因此最後回傳要除以 2 (即 `return total >> 1`)。 **不依賴 popcount,嘗試以上述歸納的規則,針對 [LeetCode 477. Total Hamming Distance](https://leetcode.com/problems/total-hamming-distance/description/) 撰寫出更高效的程式碼** 參考 solution 中的解答 ```c int totalHammingDistance(int* nums, int numsSize){ int ans = 0; for (int i = 0; i < 32; ++i) { int c = 0; for (int j = 0; j < numsSize; ++j) { c += (nums[j] >> i) & 1; } ans += c * (numsSize - c); } return ans; } ``` 使用 bitwise 逐一比較二進制中每個位元是否為 1 ,其判斷式 `(nums[j] >> i) & 1` 每當比較完一位元,便計算其 HammingDistance 的值 ( `ans += c * (numsSize - c)` ) | n 'th bit | 3 | 2 | 1 | 0 | | -------- | -------- | -------- | - | - | | input 4 | 0 | 1 | 0 | 0 | | input 14 |1 | 1 | 1 | 0 | | input 2 | 0 | 0 | 1 | 0 | 1. 第 0 個 bit 觀察: 全部都是 0 => Hamming Distance = 0 2. 第 1 個 bit 觀察: 1 有兩個 => Hamming Distance = (2)* (numsSize - 2) 3. 第 3 個 bit 觀察: 1 有一個 => Hamming Distance = (1)* (numsSize - 1) ### 測驗`2` : Remainder by Summing digits 了解[ [ 離散數學 ] 同餘(Mod)](https://ithelp.ithome.com.tw/articles/10205727)的性質及定義 詳讀 [Hacker's Delight](https://web.archive.org/web/20180517023231/http://www.hackersdelight.org/divcMore.pdf) 中提及 Remainder by Summing digits 以除 3 為例, $1\equiv 1\pmod 3$ , $2\equiv -1\pmod 3$ $2^k\equiv$ \begin{cases} 1\equiv 1\pmod 3, & \text{if $k$ is even} \\ 2\equiv -1\pmod 3, & \text{if $k$ is odd} \end{cases} n 以二進制表示成 $n=b_{n-1}b_{n-2}...b_1b_0$ \begin{aligned} n &= {\sum\limits_{i=0}^{n-1}} b_i2^{i} 由以上推論出\ n \equiv {\sum\limits_{i=0}^{n-1}} b_i(-1)^i\pmod 3 \end{aligned} 將上式結合 popcount 寫成程式 ```c n = popcount(n & 0x55555555) - popcount(n & 0xAAAAAAAA) ``` > 0x55 = 0b01010101 > 0xAA = 0b10101010 再由定理 $popcount(x \And \overline{m}) - popcount(x \And m) = popcount(x \bigoplus m) - popcount(m)$ > 定理證明在 [Hacker's Delight](https://web.archive.org/web/20180517023231/http://www.hackersdelight.org/divcMore.pdf) 裡有提到,頁碼:P13 化簡成 ```c n = popcount(n ^ 0xAAAAAAAA) - 16 ``` ```c int mod3(unsigned n) { n = popcount(n ^ 0xAAAAAAAA) + 23; //n = popcount(n ^ 0xAAAAAAAA) - 16 + 39; n = popcount(n ^ 0x2A) - 3; return n + ((n >> 31) & 3); //避免 n 為負數值 } ``` 為了避免 `n` 變成負數,因此加上一個足夠大的 3 的倍數,範例中為加 39 ,在第二輪應用使 `n` 介於 -3~2 之間而非 -3~3 之間。 `return n + ((n >> 31) & 3)`主要用作避免 n 為負數值,若為 n 為正則不受影響。 ``` 舉例 若 n = -1 -1 -> 0b1111 1111 1111 1111 1111 1111 1111 1111 # n 0b11 # (n >> 31) & 3 0b10 # n + ((n >> 31) & 3) ``` lookup table ```c int mod3(unsigned n) { static char table[33] = {2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1 }; n = popcount(n ^ 0xAAAAAAAA); return table[n]; } ``` 使用查表的方式去尋找對應的餘數。 **tictactoe.c** **將上述手法應用於第三次作業中,追求更有效的賽局判定機制。** ### 測驗 `3` : XTree [AVL Tree](https://josephjsf2.github.io/data/structure/and/algorithm/2019/06/22/avl-tree.html) 是一種Binary Search Tree (BST) 實作方法,透過高度計算及 balance factor 判斷是否需要旋轉來平衡。 [red-black tree](https://clu.gitbook.io/data-structure-note/1.4.3-red-black-tree) 遵循以下規則來達成平衡: 1. 節點只有黑或紅 2. 根節點必為黑 3. 若節點是紅色的,則其子節點必為黑色,反之不必為真(黑色節點的下個節點可為黑或紅) 4. 所有從該節點走到其子樹下的末端節點 (leaf) 的上之黑色節點數量必定 此題 XTree 的平衡機制與 AVL tree 相似,利用 hint 來評估是否需要做 rotate,然而 xt_node 的 hint 的計算方式是,其左右節點 hint 最大值 +1 並且只有在被呼叫到 xt_update 時才會被更新。 ```c #define xt_root(r) (r->root) #define xt_left(n) (n->left) #define xt_right(n) (n->right) #define xt_rparent(n) (xt_right(n)->parent) #define xt_lparent(n) (xt_left(n)->parent) #define xt_parent(n) (n->parent) ``` **`xt_create`** : 初始化 `XTree` **`xt_destroy`** 利用遞迴 ```c if (xt_root(tree)) __xt_destroy(tree, xt_root(tree)); ``` 將樹裡的每個節點釋放,在 `__xt_destroy()` 中 ```c if (xt_left(n)) __xt_destroy(tree, xt_left(n)); if (xt_right(n)) __xt_destroy(tree, xt_right(n)); ``` 若 `xt_left` (左子樹)或 `xt_right` (右子樹)存在則繼續往下走直到盡頭 `tree->destroy_node(n)` 來釋放。 **`xt_first`** 用遞迴的方式尋找最左邊的節點 ```c if (!xt_left(n)) return n; return xt_first(xt_left(n)); ``` **`xt_last`** 與 `xt_first` 一樣,但變成尋找最右變的節點 **`xt_rotate_left`** ```c struct xt_node *l = xt_left(n), *p = xt_parent(n); xt_parent(l) = xt_parent(n); xt_left(n) = xt_right(l); xt_parent(n) = l; xt_right(l) = n; ``` 這段程式碼為下圖操作 ```graphviz digraph Tree { graph[ordering=out] p -> n n -> l n -> r l -> A l -> B label=before } ``` ```graphviz digraph Tree { graph[ordering=out] p -> l l -> A l -> n n -> B n -> r label=after } ``` ```c if (p && xt_left(p) == n) xt_left(p) = l; else if (p) xt_right(p) = l; ``` 假設 p 的右子樹不存在 ```graphviz digraph Tree { graph[ordering=out] null [label="" shape=plaintext] null1 [label="" shape=plaintext] p -> l p -> null l -> null1 l -> n label=before } ``` ```graphviz digraph Tree { graph[ordering=out] null [label="" shape=plaintext] null1 [label="" shape=plaintext] p -> null p -> l l -> null1 l -> n label=after } ``` ```c if (xt_left(n)) xt_lparent(n) = n; ``` 重新定義 n 節點的 parent **`xt_rotate_right`** 操作手法與 `xt_rotate_left` 相似,因此僅用圖示 ```graphviz digraph Tree { graph[ordering=out] p -> n n -> l n -> r r -> A r -> B label=before } ``` ```graphviz digraph Tree { graph[ordering=out] p -> r r -> n n -> l n -> A r -> B label=after } ``` **`xt_balance`** 與 AVL Tree 中 Balance Factor of T, BF(T) 的平衡判斷相似,這裡的 hint 為左子樹高 - 右子樹高 **`xt_max_hint`** 選擇左子樹與右子樹的 hint 的最大值 `+1` 即為節點 n 的 hint 值(更新 hint) **`xt_update`** 若樹不平衡 `xt_balance < -1` 進行 `xt_rotate_right` , `xt_balance > 1` 進行 `xt_rotate_left` **`xt_insert`** 插入新節點在樹中 **`xt_remove`** 當移除一節點 `del` 時,將該右子樹的最左來填補,即 `least` 來替換掉原本的 `del` ,並檢查是否平衡 `xt_update`,反之則用左子樹的最右節點來點補,若被刪除的節點為 `leaf` 將不用進行交換。 ```c static void __xt_remove(struct xt_node **root, struct xt_node *del) { if (xt_right(del)) { struct xt_node *least = xt_first(xt_right(del)); if (del == *root) *root = least; xt_replace_right(del, least); xt_update(root, xt_right(least)); return; } if (xt_left(del)) { struct xt_node *most = xt_last(xt_left(del)); if (del == *root) *root = most; xt_replace_left(del, most); xt_update(root, xt_left(most)); return; } if (del == *root) { *root = 0; return; } /* empty node */ struct xt_node *parent = xt_parent(del); if (xt_left(parent) == del) xt_left(parent) = 0; else xt_right(parent) = 0; xt_update(root, parent); } ```