contributed by < SHChang-Anderson >
int msb = (int) log2(N);
找到變數 N
的最高有效位 (most significant bit) 位置。
計算最高有效位的值存入變數 a
中。
進入迴圈,使用逐位元掃描的方法,從最高有效位開始,逐位檢查是否可以將對應的值加入到 result
中,而不會讓 result
的平方超過 N
。此方法的特色在於相較於逐一數值嘗試開平方根近似的方式,利用了二進制表示的特性,每次只需要檢查一個位元,因此能夠更有效地近似計算出整數平方根。
與版本一不同於不使用 log2
函式,而是使用迭代計算的方式找到最高位元。
這樣的優勢在於可以避免使用到浮點數運算,也可以在不支持 log2
的環境中運行。
這個版本的開方根利用 Digit-by-digit calculation 的概念實作開平方根。
首先若要對 () 做開平方根 ,可以假設 , 即為欲求得的平方根數值,接著,將 改寫為 2 的羃次和,即:
。
若將 做展開,透過矩陣觀察:
主對角線元素:
其餘元素:
將主對角線元素與其於元素分開討論可將原式整理成:
其中, 為對角線上的平方項,另外 為其餘的元素交叉相乘展開項。
接著將等式拆解為:
移項之後做觀察:
觀察括號內的數學式,可將 改寫為:
最終整理得到:
令
則所求 。
接著將計算式整理成:
若推展成一般式可得:
可以將 令為 ,則 。
若從從 一路嘗試計算到 每一輪透過 得到下一輪次的 並測試 是否成立,最終即可找到所求。
然而,每輪計算 的成本過高,若將 計算結果令為 ,則可推得 ,最終推得遞迴式: 。
這樣一來透過方程式 ,紀錄上一輪的 來計算 以這樣的方式避免較高的運算成本。
為了實現演算法設計,進一步將 拆成 與 ,得到:
可以藉由位元運算從 推出下一輪 ,再利用 計算出 最終推得 。
綜合以上方法使用演算法尋求 () ,從 的初始條件:
演算法中 z
對應到上述推導的 , 而 m
對應到上述推導的 。
由於初始的 , 將 z
設為 0:
int z = 0;
另一方面, ,因此可以利用以下程式碼計算 m
:
int m = 1UL << ((31 - __builtin_clz(x)) & ~1UL);
在迴圈中, int b = z + m;
, b
對應到推導中的 。 而由上述推導 可知,無論 結果為何,都需要將 ,因此 z >>= BBBB;
就是 的實作, BBBB
應替換為 1 。
另外,由 知道每一輪迴圈,需要將變數 m
除以 4, m >>= AAAA
應改為 m >>= 2
。
以上條件判斷中, if (x >= b)
表 又 則 ,因此將 加入 z
當中,因為 因此最終所求即 z
。
__builtin_clz
__builtin_clz(x)
函式回傳 x
的最高有效位前面連續的 0
位元的數量,那麼 31 - __builtin_clz(x)
就是最高有效位的位置。
同樣 fls(x) - 1
也可以找到最高有效位的位置,但 fls()
由索引值 1 開始計算,因此需要將 fls(x) - 1
,從而計算需要左移多少位元才能得到最接近且不大於 x
的 2 的冪次數。
因此可以將程式碼改寫為:
在 lib/math/int_sqrt.c,找到整數平方根運算的程式碼:
程式碼風格與實做原理與測驗一(版本三)類似,比較值得注意程式碼使用到 __fls(x)
來找到需要位移的位元數量,閱讀過去探討過關於 ffs 及 __ffs 加雙底線與否的不同 了解ffs
及 __ffs
(是否加雙底線) 的不同之處:參考 bitops
系列對外的介面: arch/arc/include/asm/bitops.h 中的註解得知:
由此可知使用 __fls(x)
來找到需要位移的位元數量,不需要 - 1 。
mod 10
和 div 10
連續操作本測驗針對正整數在相鄰描述進行 mod 10
和 div 10
操作的場景進行探討。若要優化這個計算情況,最直觀的方式是使用餘式定理。
根據餘式定理:
被除數 = (商 * 除數) + 餘數
對應到程式碼就是:
tmp = (tmp / 10) * 10 + (tmp % 10)
因此可以合併 mod 10 和 div 10 操作,改寫為以下程式碼:
若使用 bitwise operation 實作以上除法,會發現由於 10 存在因數 5 並非 2 的羃,因此可能會產生誤差。
測驗中題到了 tmp
不可能會大於 19 ,因此只需要考慮 19~0 的情況即可。其原因為:
tmp 是透過計算 (b[i] - '0') + (a[i] - '0') + carry
得到的。 b[i]
和 a[i]
分別是字符 '0' 到 '9' 之間的數字字符,對應的數值範圍是 0 到 9。
所以 (b[i] - '0') 和 (a[i] - '0') 的值範圍都是 0 到 9。
將兩個 0 到 9 之間的數相加,最大值為 9 + 9 = 18。
再加上最大可能的進位值 1,最大結果就是 18 + 1 = 19
接著,繼續針對方法繼續探究,針對此問題,提出了猜想:
假設:
以下證明上述猜想:
分別對左右不等式進行探討:
接著討論 ,若我們將 代入 可將不等式改寫為:
分別將 與 替換為 可表現為:
右不等式:
左不等式:
由上述證明可得知,若 tmp
不可能會大於 19 ,只須透過不等式:
即可得知,除數介於 至 之間皆可程式中達到相同效果。
找除數的方法使用 bitwise operation 找到介於 的除數,若欲處理得數字為 商式可以寫成 。
為一個可用的除數,由於 13 可以拆成 的羃相加,因此範例程式中透過 (tmp >> 3) + (tmp >> 1) + tmp
得到 再將此式乘上 8 (向左位移 3 bits) 即可得到 ,只要再將其除以 () 即可得到目標商式 。
uint32_t x = (in | 1) - (in >> 2);
: 這行程式碼初始化 x。表達式 (in | 1)
確保 in 是奇數(將最低位設置為 1),然後減去向右移位 2 的 in
(相當於將 in
除以 4),得到一個大約等於 () 的近似值。
uint32_t q = (x >> 4) + x;
相當於 亦等於 若換算為小數約為 ,因此目前 q
值接近 。
接著不斷透過持續的 bitwise 操作使的 q
值持續逼近 。
*div = (q >> CCCC);
為了使商值 *div
正確被指定,CCCC
應替換為 3
得到 。
最後 *mod = in - ((q & ~0x7) + (*div << DDDD));
應為透過餘式定理的餘數計算。根據餘式定理:
餘數 = 被除數 - 除數*商, ((q & ~0x7) + (*div << DDDD));
計算的就是除數*商的部份也就是 。 ((q & ~0x7)
的操作即 q & 0xFFFFFFF8
即將 q
最後三個位元清 0 ,這樣的作法使得目前 q
等價於 ,由於所求為 因此 (*div << DDDD))
DDDD
應替換為 1 相當於 以符合預期。
TODO:撰寫不依賴任何除法指令的 % 9 (modulo 9) 和 % 5 (modulo 5) 程式碼。
首先將 log
設為 -1。這樣做是為了在輸入值為 0 時,函式回傳 -1。
接著進入一個 while
迴圈,當 i
不為 0 時持續執行迴圈體內的操作。
在迴圈內, i
右移一位 (i >>= 1
)。這個操作相當於將 i
除以2。
每執行一次右移操作,就將 log
的值加1。
當 i
變為 0 時,迴圈終止。
最後,函式即可求得輸入值 i
的對數值並回傳。
這段程式碼每一次檢查一半的位元數量並進行 bitwise 位移,這種作法的優點是計算速度更快,因為它將對數值的計算分成了多個階段,每個階段只需要處理一小部分位元,而不是像前一種方法那樣逐位處理整個數值。
分段計算:這段程式碼將對數值的計算分成了多個階段,每個階段對應不同的位移量(16, 8, 4, 1)。這種做法可以加速計算過程,尤其是在處理大數值時。
這段程式碼將對數值的計算分成了多個階段,每個階段對應不同的位移量。可以觀察程式碼從最高 16 位元進行判別,接著是 8 位元 4 位元,直到最後一個位元為止。透過這樣的觀察我們可以知道 AAAA
、BBBB
、 CCCC
分別對映為 、 、 。
可以發現這樣的方法就是尋找 i
最高有效位的位置。
在 linux/log2.h 中可以找到 log2 的相關實作。
可以看到 log2 使用到測驗一提到的 fls
也就是透過fls(x) - 1
找到最高有效位的位置達成與 ilog2 以 2 為底的對數 (版本二) 相同的效果。
EWMA (指數加權移動平均) 是一種統計資料取平均的手法,其數學定義如下:
其中:
當 值越大時,EWMA 會給予較多的權重於最近的觀測值,因此計算出的平均曲線會較為敏感,能夠快速反映最新的數據變化。反之,若 值較小,則 EWMA 會給予較多權重於歷史數據,計算出的平均曲線會較為平滑,變化也相對較小。
閱讀並理解測驗四中對於 EWMA 實作。首先,先觀察結構體 ewma
:
結構體中使用 unsigned long
除存所有參數,使用 2 的羃來除存所有參數以及權重。
ewma_init
函式用於初始化結構體中的參數。在函式內部,我們觀察到對要初始化的參數進行了檢查,確保其值是 2 的冪次方。這樣做是為了後續使用位元操作來提高效能,代替乘除法的運算。接著,將這些參數轉換為對數形式,以便後續的處理。
值得注意的是,透過程式碼中對於 2 的冪次方的檢測,可以得知實作希望使用定點數進行加權平均的計算。此外,在函式的註解中提到,factor
參數被用作準備定點數運算所需的平移值。
ewma_add
是實際執行 EWMA 計算的函式,其中 internal
對應到 ;而 val
對應到 。我推測 (((avg->internal << EEEE) - avg->internal) + (val << FFFF)) >> avg->weight
即為上述數學定義: 的實作方式。
我注意到當 avg->internal
為 0 時,函式會執行 (val << avg->factor);
,也就是說當初始計算 EWMA 時尚未有任何資料,直接將目前時間點的觀測值加入。這時函式將 val
向左位移,因此我推測 (val << FFFF)
也應該將 val
向左位移,由此可知 FFFF
應該替換為 avg->factor
。
接著,我繼續探究 ((avg->internal << EEEE) - avg->internal)
程式碼部份。假設將 EEEE
暫時設置為變數 ,則由於 weight
以對數方式儲存 (((avg->internal << EEEE) - avg->internal) + (val << FFFF)) >> avg->weight
在數學上的意義為:。然而目標數學方程式應該為:,觀察可發現後者的 應為前式的 。因此, 應該與 等價,由此可推得 ,因此 FFFF
應該替換為 avg->weight
。
在 linux/average.h 可以找到 EWMA 實作程式碼。
linux/average.h 定義了 DECLARE_EWMA
巨集,這個巨集接受了三個參數, name(用於生成的 struct 和函式名稱)、 _precision (表示用於儲存小數部分的位元數)和 _weight_rcp (一個 2 的冪,決定了新舊值的加權)。
linux/average.h 定義了一個結構體 ewma_,包含一個 unsigned long 型態的成員,用於儲存 EWMA 值。
ewma_init() 函式用於初始化結構實例,將 internal 設為 0。並使用了 BUILD_BUG_ON 巨集,在編譯時檢查 _precision 和 _weight_rcp 參數是否符合要求。
ewma_read()
函式用於讀取 EWMA 值。由於 EWMA 實作使用了定點數運算,因此 internal 成員儲存了一個經過左移_precision 位的值。internal
成員儲存了經過放大的 EWMA 值(包含整數和小數部分),而 ewma_read()
的右移操作則是為了將它縮小回原始的整數 EWMA 值。
ewma_add() 函式是 EWMA 計算關鍵,用於將新值納入 EWMA 計算。它首先讀取 internal 值,根據 _weight_rcp
的值決定歷史資料與目前資料的加權。
ath11k/core.h 找到 DECLARE_EWMA(avg_rssi, 10, 8)
定義了 EWMA 結構體,由 linux/average.h 可得知 fixed-precision values
為 10 而 。
ath11k: 高通 IEEE 802.11ax 裝置的 Linux 驅動程式:
根據 Wireless Wiki,ath11k 是針對高通的 IEEE 802.11ax 無線網路裝置所設計的 Linux 驅動程式。它能夠支援在 SoC 類型裝置中的 AHB 匯流排和 PCI 接口。ath11k 基於 mac80211,這是 Linux 核心中用於無線網路裝置的通用框架。
avg_rssi
和 EWMA :
我參考了 Received Signal Strength Indicator 一文來理解 RSSI。根據該資料,RSSI 是衡量設備從接收端接收訊號能力的指標,用於評估無線通訊中訊號的強度和品質。在 ath11k 的程式碼中,有一個名為 avg_rssi
的變數,被用來儲存指數加權移動平均值 (EWMA)。透過 EWMA,能夠平滑訊號強度的變化,提供更穩定的接收訊號強度指標 (RSSI)。
ceil_ilog2
程式碼理解ceil_log2
這個程式碼實現了一個函式,用於計算給定的 32 位元無號整數 的最小次方指數值向上進位的結果。也就是說,對於傳入的參數 ,回傳最小的整數 n,滿足 。
可以注意到函式的最開始將 x 減 1,這樣可以確保當 x 是 2 的冪次時,計算出的指數正確。
接著根據以上程式碼的操作,我們可以觀察到類似於測驗三中的使用以 2 為底的對數 (版本二) 的二分搜尋法來找到最高位元位置。然而,與測驗三程式碼不同的是,這裡使用變數 r
來記錄位移量,而 (x > 0xFFFF) << 4;
的操作等價於以下程式碼:
在這裡,(x > 0xFFFF)
的結果是一個布林值。如果 x
的高 16 位元不為 0,則 (x > 0xFFFF)
的結果為 True,亦即等於 1。因此,位移量 r
被設定為 1 << 4
,即 ,達到與測驗三 log2 程式碼相同的效果。
值得注意的是,在程式碼中除了執行位移操作外,還將目前的位移量與變數 r
做了 運算,即 r |= shift;
。由於位移量都是 2 的冪次方,因此這樣的 運算等同於對位移量進行加法操作(result +=
)。因此,在進行位移後,程式碼將持續累加位移量,以找到最高位元位置。
最終函式回傳,總位移量 + 1,然而若對照測驗三 log2 程式碼可發現,少了一個條件判斷:
因此 | x > GGG
即判斷 是否 ,也因此 GGG
應填入 以符合預期。
試想上述程式碼若傳入參數 時,程式碼的第一行, x--
會將 x
值改變為 0xFFFFFFFF
,這樣一來,會使得接下來的迴圈以及條件判斷不符合預期,因此需要對此做修正。
簡單的修正方法即為加入 if
條件判斷,避開 時做減法,然而這樣的方式並不符合 branchless 。
我嘗試將程式碼做以下更動:
加入 x = x - (x > 0);
後使得在 時才會產生布林值 1 ,達到減 1 的效果,並仍是 branchless 。
population count 簡稱 popcount 或叫 sideways sum,是計算數值的二進位表示中,有多少位元是 1。
閱讀到關鍵程式碼:
不了解為何 n = (v >> 1) & 0x77777777
即可將數值分為四個位元一個單位做減法,並透過 v -= n
即可求得 。因此我將數學式列出,最一開始,傳入值 v 即為 而將 v >> 1
可得 。
而我們可以得知 (v >> 1) & 0x77777777;
結果為:
寫成數學式為:
若持續重複執行 n = (n >> 1) & 0x77777777; v -= n;
可以分別得到:
因此,若以四項為單位做相減即可得到:
並對應到以每 4 個位元 (nibble) 為一個單位計算 1 的個數。
接著透過一系列位移以及 bitmask 操作可得所求的 popcount 值。
Hamming Distance 是指這兩個數字對應位元的不同位置的個數。例如:數字 3 (二進制為 )和數字 5 (二進制為 )的漢明距離為 2,因為它們在第一位和第三位不同。
在程式碼中,使用了位元運算子 XOR( ) 來找出兩個數字在哪些位置不同。對於任何兩個位元,只有當它們不同時, XOR 的結果才會是 1。因此, 的結果會是一個數字,其二進制表示中 1 的位置就是 x 和 y 不同的位置。
接著程式使用 __builtin_popcount
函式來計算 中有多少個 1,也就是 x 和 y 有多少個不同的位元。這個函數的實作方式是直接對輸入的數字計算其二進制表示中 1 的個數,效率相當快。因此,這一行程式碼實際上就是在快速計算出 x 和 y 的漢明距離。
以上程式碼用於計算 nums
陣列中所有數字之間的漢明距離總和。需特別注意的是,程式中重複考慮了兩個數字之間的距離,因此最終結果為總距離的兩倍。為了將結果除以 2,使用右移操作符將總和向右移動1位。因此,AAAA
應填入 1。
從位元展現的樣貌,觀察 Total 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 |
首先,我們觀察第 0 個位元位置。在這個位置上,數字 7、5、17 都是 1,而數字 10 是 0。進一步探究 Hamming Distance:
從上圖可理解為:每個 1 位元可以與 1 個 0 位元產生距離為 1 的 Hamming Distance。因此,由於有 3 個 1 位元,總 Hamming Distance 為 。
接下來,我們觀察第 1 個位元位置:
在第 1 個位元位置上,數字 7 和 10 都是 1,而數字 5 和 17 是 0。觀察上圖可理解為:每個 1 位元可以與 2 個 0 位元產生距離為 1 的 Hamming Distance。因此,由於有 2 個 1 位元,總 Hamming Distance 為 。
總結來說,可以計算每個位置上的 1 位元數量,並將每個位置的 1 位元數量乘以 0 位元數量,以求得 Total Hamming Distance。
根據以上方法實作改進後的程式碼:
撰寫程式驗證其正確性:
commit 3e675c3
使用 perf
分析其效能差異,針對 10000 筆數字進行 Total Hamming Distance 計算。
改進前:
改進後:
可以發現改進後的程式碼大幅減少了 clock cycles 數量,同時也縮減了執行時間。
為了在不使用除法的情況下計算某數除以另一個數的餘數,使用了模同餘的概念。
當除數為 3 時,我們可以觀察到 和 。 根據 的性質,我們可以進行以下推導:
當我們將 以二進位表示時,可以寫為 。
根據前述推導,我們得知當 為偶數時,同餘為 1;當 為奇數時,同餘為 -1。因此,我們可以得到以下表達式:
接著,我們使用以下定理進行化簡:
因此,n = popcount(n & 0x55555555) - popcount(n & 0xAAAAAAAA)
可以寫為 n = popcount(n ^ 0xAAAAAAAA) - 16
。
然而,以上計算結果的範圍會落在 -16 至 16 之間。考慮到希望餘數為正數的情況,我們需要加上一個 3 的倍數以確保餘數在同餘情況下為正數。
至於為何要加上 39 ? 參閱 《Hacker's Delight》中的說明:
We want to apply this transformation again, until n is in the range 0 to 2, if possible. But it is best to avoid producing a negative value of n, because the sign bit would not be treated properly on the next round. A negative value can be avoided by adding a sufficiently large multiple of 3 to n. Bonzini’s code, shown in Figure 10–21, increases the constant by 39. This is larger than necessary to make n nonnegative, but it causes n to range from –3 to 2 (rather than –3 to 3) after the second round of reduction. This simplifies the code on the return statement, which is adding 3 if n is negative. The function executes in 11 instructions, counting two to load the large constant.
在文中指出,將常數增加了 39。這個值比僅使非負數所需的常數值更大,可使得在第二輪計算後,值落在 -3 到 2 的範圍內(而非 -3 到 3),也因此簡化了程式碼,只需在 n 為負數時加 3 即可。
另一種方法是直接將 0 到 32 的所有數字除以 3 得到的餘數事先儲存在一個 lookup table 中,這樣就可以直接透過查表的方式找到對應的餘數。程式碼如下所示:
此程式實現的是一個井字遊戲的變體,該變體將遊戲目標從傳統的在3x3棋盤上實現三個連續的棋子,擴展到了在任何八條可能的直線上達到三個連續的棋子。這使得玩家的策略更加多樣化,因為一步棋可能會影響多條直線。
程式中設計了 available_moves[]
陣列,此陣列即為 3x3 井字遊戲上的九個位置選擇,因此我們可以看到 play_random_game
函式中,每做出一個選擇將其選擇從陣列中移除:
實作方式就是將選擇的走法用最後一個可用的走法來替換,然後將最後一個可用的走法移到了被選擇的走法所在的位置。
在 board |= move_masks[move];
這行程式碼中,將選定的走法 move
對應的連線狀態更新到了玩家的棋盤狀態中。在這個井字遊戲變體中,棋盤狀態不是按照傳統的九宮格形式表示,而是以8條可能的連線來表示。這意味著玩家的棋盤狀態存儲了對應於這8條連線的狀態,而不僅僅是傳統的棋子擺放狀態。
對於 board |= move_masks[move];
這行程式碼,move_masks[move]
取得了選定走法 move
對應的連線狀態,然後將它與玩家的棋盤狀態 board
做位元或運算,從而將選定走法的影響更新到了玩家的棋盤狀態中。由於每個 move_masks
元素都包含了對應位置下棋可能影響的所有連線,因此這個操作有效地更新了玩家棋盤狀態中的所有可能連線。
move_masks
陣列中的每個元素代表了在將棋子放置到特定位置後,對於所有可能連線狀態的影響。每個元素的二進位表示描述了在該位置放置棋子後,連線狀態發生變化的情況。
以 0x40040040
為例,用圖示來進行說明:
考慮 0x40040040
九宮格棋盤中為左上角 (0) 的選擇:
1 | 2 | 3 | |
---|---|---|---|
1 | 0 | 1 | 2 |
2 | 3 | 4 | 5 |
3 | 6 | 7 | 8 |
此位置將有三種可能連線:
1 | 2 | 3 | |
---|---|---|---|
1 | 0 | 1 | 2 |
2 | 3 | 4 | 5 |
3 | 6 | 7 | 8 |
對應到 board
二進位表示即:
0111 0000 0000 0000 0000 0000 0000 0000
1 | 2 | 3 | |
---|---|---|---|
1 | 0 | 1 | 2 |
2 | 3 | 4 | 5 |
3 | 6 | 7 | 8 |
對應到 board
二進位表示即:
0000 0000 0000 0111 0000 0000 0000 0000
1 | 2 | 3 | |
---|---|---|---|
1 | 0 | 1 | 2 |
2 | 3 | 4 | 5 |
3 | 6 | 7 | 8 |
對應到 board
二進位表示即:
0000 0000 0000 0000 0000 0000 0111 0000
0x40040040
以二進位可表示為:
0100 0000 0000 0100 0000 0000 0100 0000 玩家棋盤狀態與其做 運算可更新棋子擺上棋盤 0 位置後對連線狀態的影響。
勝利的條件判斷即 player_board
以四個位元為單位出現 0111 即判斷該玩家獲勝,可以看到程式碼將 (player_board + BBBB)
與 0x88888888
做 運算,由此可知,當出現 0111 時需要將棋結果轉為 1000 ,而將 0111 + 1 即可達成此效果,因此 BBBB
應填入 0x11111111
。
TODO
treeint.c
為二元樹測試程式,用來測量在不同的操作下,如插入、查找和刪除,二元樹的性能表現。
treeint_ops
結構,該結構包含指向各種樹操作函式的指標。xt_ops
為 treeint_ops
的實例 ,並將其函式指標設定為特定的實作函式。xtree.[ch]
二元搜尋樹的實現,它採用了一些特定的策略來保持樹的平衡。
二元樹的結構定義包含 xt_tree
以及 xt_node
,值得注意的是在 xt_node
中加入了 hint
作為平衡參數。
程式使用不同函式實現二元樹的不同功能:
xt_create 函數創建一個空的樹。
xt_destroy 和 __xt_destroy 函數用來遞迴釋放樹中所有節點的記憶體。
xt_rotate_left 和 xt_rotate_right 函數用於節點的左旋和右旋操作,這是平衡樹的關鍵操作之一。
xt_update 函數根據節點的平衡因子進行相應的旋轉操作,以維持樹的平衡。
__xt_find 和 __xt_find2 函數實現了在樹中查找特定鍵值的節點。
__xt_insert 和 xt_insert 函數實現了向樹中插入新節點的功能。
樹的刪除操作:
刪除操作相對複雜,尋找替代節點(右子樹的最小節點或左子樹的最大節點)並進行替換。
函式首先檢查被刪除的節點 del
是否有右子節點 (xt_right(del))
。
若存在,它會找到右子樹中最小的節點(xt_first(xt_right(del)))
,這個最小節點將會取代要被刪除的節點。
如果被刪除的節點是根節點(del == *root)
,則將根節點更新為這個最小節點 (*root = least)
。
接著,函式呼叫 xt_replace_right
找到的最小節點來替換被刪除的節點。因此 AAAA
應該替換為 least
。
最後,對替換後的新樹結構進行 xt_update
,以維持樹的平衡。由於替換操作後,least
節點被移動到了 del
的位置,而 least
的原位置現在由它的右子節點所取代,針對原位置的節點進行更新,因此BBBB
應替換為 xt_right(least)
。
同樣的,若刪除的節點有左子節點, CCCC
應該被替換為 most
,表示將 most
節點放到 del
節點的位置。
如果被刪除的節點 del
有左子節點,會找到這個左子樹中的最大節點 most
來替代 del
。而 DDDD
應替換為 xt_left(most)
。
最後,當欲刪除的節點沒有子節點時分為兩種情況進行處理:
del
正好是根節點,直接將根節點指標設置為 NULL
,樹將變為空。del
不是根節點,那麼首先找到 del
的親代節點 parent
,接著判斷 del
是其親代節點的左子節點還是右子節點。如果 del
是左子節點,則將 parent
的左子節點指針設置為 NULL
。如果 del
是右子節點,則將 parent
的右子節點指針設置為 NULL
。此舉使得節點從二元樹中斷開。xt_update(EEEE, FFFF)
來更新樹的平衡。 EEEE
應傳入 root
。由於刪除 del
後,需要重新平衡的是 del
的親代節點 parent
。因此 FFFF
應該是 parent
節點。