contributed by <leonnig
>
static inline void list_insert_before(list_t *l, list__item_t *before, list_item_t *item)
{
list_item_t **p;
for (p = AAAA; *p != BBBB; p = CCCC)
;
*p = item;
DDDD = before;
}
此 function 的功能是將item
插入到 list 中 before
指向的節點的位置。
如果要達成這個功能,勢必要從頭 traverse 過 list 以找到該插入的位置,可以從程式碼中發現,宣告了一個p
pointer to pointer 去做 traverse,p
一開始要指向的要是一個指標,並且是從開頭的位置,所以AAAA
要是整個串列的起點,也就是 &l->head
。
而中止條件*p != BBBB
,可以看出對p
做 value of,又功能的要求是要插入到before
的位置,因此可以判斷當*p
指向的不為before
時都要繼續前進,BBBB
= before
。
每次前進時,指標的指標p
會指向下一個節點中的next
的位置,這樣在搜尋時可以利用 address of 直接取得要插入的目標位置,CCCC
= &(*p)->next
。
找到插入的位置後,*p = item
就是將before
的前一點的next
指向item
,最後必須將item
與後面的list連接起來,所以DDDD
= item->next
。
其實整段程式碼就是在對 list 的一些操作做測試。
先來說明以下 function 功能
my_assert
#define my_assert(test, message) \
do { \
if (!(test)) \
return message; \
} while (0)
這段程式在判斷給定的 test 是否通過,若未通過則回傳一段訊息通知。
list_reset
static list_t *list_reset(void)
{
for (size_t i = 0; i < N; i++) {
items[i].value = i;
items[i].next = NULL;
}
l.head = NULL;
return &l;
}
這段程式將一條 list 內的連接都斷開,也就是將list_item_t
的next
都設為 NULL,head也設為 NULL。
而在 test_list 則是最主要做測試的部分,其中又分為3段測試。
首先對 list 做 reset,然後去測試看 list 是否有被正常 reset 成功。
my_assert(list_size(&l) == 0, "Initial list size is expected to be zero.");
並且從 l->head
插入 N 個數,測試從開頭插入的操作。
並且插入方式如下:
for (size_t i = 0; i < N; i++)
list_insert_before(&l, l.head, &items[i]);
可以發現他的第二個 argement 給的是l.head
,代表說他的迴圈會馬上停在第一個位置,而item
插入的位置就都會在最前面,也就是數字越大的節點會在越前面。
然後再去檢查插入完成後的 list 大小是否為 N,並且逐一檢查每個節點的 vlaue 是否有符合順序。
size_t k = N - 1;
list_item_t *cur = l.head;
while (cur) {
my_assert(cur->value == k, "Unexpected list item value");
k--;
cur = cur->next;
}
再來是測試從 list 尾部插入的操作
終止條件改成了NULL
,也就是會往後找到最後一個節點的next
為止,將item
插入。
所以數字越大的節點會出現在越後方。
for (size_t i = 0; i < N; i++)
list_insert_before(&l, NULL, &items[i]);
然後執行跟前面一樣的測試。
最後則是做 list 的 reset,完成後再做從 list 尾部的插入。
補完 list_size
功能
static int list_size(list_t *l)
{
if (!l->head)
return 0;
int cnt = 0;
list_item_t *cur = l->head;
while (cur) {
cur = cur->next;
cnt++;
}
return cnt;
}
首先撰寫 merge 的操作
list_item_t *mergeTwo(list_item_t *L1, list_item_t *L2)
{
list_item_t *head;
list_item_t **ptr = &head;
for (; L1 && L2; ptr = &(*ptr)->next) {
if (L1->value < L2->value) {
*ptr = L1;
L1 = L1->next;
} else {
*ptr = L2;
L2 = L2->next;
}
}
*ptr = L1 ? L1 : L2;
return head;
}
再來遞迴做 divide
list_item_t *mergesort(list_item_t *head)
{
if (!head->next || !head)
return head;
list_item_t *slow = head;
for (const list_item_t *fast = slow->next; fast && fast->next;
fast = fast->next->next) {
slow = slow->next;
}
list_item_t *new_l = malloc(sizeof(list_item_t));
new_l = slow->next;
slow->next = NULL;
list_item_t *left = mergesort(head), *right = mergesort(new_l);
mergeTwo(left, right);
}
---=[ List tests
Before sorting: 99 92 61 11 58 54 16 83 94 11
After sorting: 11 11 16 54 58 61 83 92 94 99
ALL TESTS PASSED
Tests run: 2
/* Find the in-order predecessor:
* This is the rightmost node in the left subtree.
*/
block_t **pred_ptr = &(*node_ptr)->l;
while (EEEE)
pred_ptr = FFFF;
根據註解,這段程式碼的目的是要找出二元搜尋樹中的 in-order predecessor ,而註解有說明了是要左子樹中最右邊的節點。
所以會一直往右子樹的節點去搜尋,直到某個節點的右子樹為 NULL
所以EEEE
= (*pred_ptr)->r
而這就代表該節點為左子樹中最右邊的節點
FFFF
= &(*pred_ptr)->r
根據程式中的敘述可以得知,目的是想要實作一個memory allocator
,並且以二元搜尋樹的資料結構去存放那些可用、閒置的記憶體區塊。
首先來分析一下remove_free_tree
,根據敘述,我認為此函式的功能主要是移除那些準備要被使用或配置的記憶體區塊,
使用find_free_tree
先找出要拿來使用的區塊
block_t **node_ptr = find_free_tree(root, target);
node_ptr
為指標的指標,是指向存放target
的指標變數,也就是某個父節點的l
或r
。
而根據找的目標節點狀態不同,也會有不同的處理方式。
1. 當要移除的節點有左子節點及右子節點時:
這時不能直接刪除,需要找到一個替代的節點,就是目標節點的左子樹中最右邊的節點。
block_t **pred_ptr = &(*node_ptr)->l;
while ((*pred_ptr)->r)
pred_ptr = &(*pred_ptr)->r;
而這時又分為兩種情況
當替代的節點就是欲移除的節點的左子節點時:
先用old_right
指標指向欲移除節點的右子節點,在對node_ptr
做 dereference 得到指向目標的指標,在將其指向替代節點,再將新的目標節點的右子節點指標指向old_right
,維持二元搜尋樹的結構。
替代的節點位於左子樹的深處:
必須把替代節點先移除原本的位置,所以這邊需要呼叫remove_free_tree(&old_left, *pred_ptr)
移除替代節點,再將其接回正確的位置。 這邊利用pred_node
指標來保存被移除的替代節點。
/* Remove the predecessor from its original location. */
remove_free_tree(&old_left, *pred_ptr);
/* Replace the target node with the predecessor. */
*node_ptr = pred_node;
(*node_ptr)->l = old_left;
(*node_ptr)->r = old_right;
assert(*node_ptr != (*node_ptr)->l);
assert(*node_ptr != (*node_ptr)->r);
2. 當要移除的節點只有左子節點或右子節點時(只有一個子節點):
判斷移除節點的子節點是左還是右,將那個子節點連結回去原本的二元樹。
block_t *child = ((*node_ptr)->l) ? (*node_ptr)->l : (*node_ptr)->r;
*node_ptr = child;
3. 當要移除的節點沒有任何子節點時:
直接將指向target
的指標設為 NULL ,段開連接。
將target
移除後,要將目標節點指向右子樹和指向左子樹的指標設為 NULL,避免 dangling reference。
剩餘待補上..
Todo