# 2025q1 Homework2 (quiz1+2)
contributed by <`I-Ying-Tsai`>
## quiz1
### 測驗 1
這題要求我們利用 `list_insert_before` 涵式來測試好品味的 `linked list` 結構
```c
#include <stddef.h>
typedef struct list_item {
int value;
struct list_item *next;
} list_item_t;
typedef struct {
struct list_item *head;
} list_t;
```
根據 `list_insert_before` 涵式的要求,可以得到:
AAAA : `&l->head` , 因為涵式接受的是 `Node** head` <所以我們需要傳入的是 `head` 的地址,而 `head` 又是第一個節點的地址,這也是指標的指標的應用。
BBBB : `before` , 因為在 `list_insert_before` 函式中,我們希望在 `entry` 之前插入新節點 `new_node`,因此我們需要確保 `new_node` 指向 `entry`,而前一個節點(`prev`)指向 `new_node`。
CCCC : `&(*p)->next` , 因為我們希望透過 `pointer to pointer (Node** p)` 來修改鏈結串列的結構,特別是當 `entry` 不是 `head` 的情況下,我們需要找到 `entry` 的前一個節點,並調整它的 `next` 指標,讓 `new_node` 正確插入。
DDDD : `item->next` , 因為 `*p = item;` 讓 `prev->next` 指向 `item。 item->next = before;` 讓 `item` 連接到 `entry`。
#### 延伸問題 : 解釋上方程式碼運作原理
這段程式碼的目的是 在單向鏈結串列中,找到 `entry` ,並在其之前插入 `item`。
#### 延伸文題 : 在現有的程式碼基礎上,撰寫合併排序操作
```c
#include <stdio.h>
#include <stdlib.h>
typedef struct Node {
int data;
struct Node* next;
} Node;
// 找到鏈結串列的中間點並拆分
void split_list(Node* source, Node** front, Node** back) {
Node* slow = source;
Node* fast = source->next;
while (fast && fast->next) {
slow = slow->next;
fast = fast->next->next;
}
*front = source;
*back = slow->next;
slow->next = NULL;
}
// 合併兩個排序後的鏈結串列
Node* sorted_merge(Node* a, Node* b) {
if (!a) return b;
if (!b) return a;
Node* result = NULL;
if (a->data <= b->data) {
result = a;
result->next = sorted_merge(a->next, b);
} else {
result = b;
result->next = sorted_merge(a, b->next);
}
return result;
}
// 合併排序主函式
void merge_sort(Node** head_ref) {
Node* head = *head_ref;
if (!head || !head->next) return;
Node *a, *b;
split_list(head, &a, &b);
merge_sort(&a);
merge_sort(&b);
*head_ref = sorted_merge(a, b);
}
// 插入新節點到鏈結串列
void push(Node** head_ref, int new_data) {
Node* new_node = (Node*)malloc(sizeof(Node));
new_node->data = new_data;
new_node->next = *head_ref;
*head_ref = new_node;
}
// 列印鏈結串列
void print_list(Node* head) {
while (head) {
printf("%d -> ", head->data);
head = head->next;
}
printf("NULL\n");
}
// 測試
int main() {
Node* head = NULL;
push(&head, 4);
push(&head, 2);
push(&head, 7);
push(&head, 1);
push(&head, 9);
push(&head, 3);
printf("原始鏈結串列:\n");
print_list(head);
merge_sort(&head);
printf("排序後的鏈結串列:\n");
print_list(head);
return 0;
}
```
----
### 測驗 2
`block_t` :
```c
typedef struct block {
size_t size;
struct block *l, *r;
} block_t;
```
`size_t size;` :
記錄當前記憶體區塊的大小。
`struct block *l;` :
指向 左子節點(left child),即 小於當前區塊大小的記憶體區塊。
`struct block *r;` :
指向 右子節點(right child),即 大於當前區塊大小的記憶體區塊。
---
`find_free_tree()` :
```c
block_t **find_free_tree(block_t **root, block_t *target);
```
搜尋 `target` 在 `free tree` 中的位置,回傳指向該區塊的指標。這個函式的作用是幫助 `remove_free_tree()` 定位到 `target` 在 二元搜尋樹(`BST`) 中的節點,以便進行刪除或合併。
---
`find_predecessor_free_tree()` :
```c
block_t *find_predecessor_free_tree(block_t **root, block_t *node);
```
尋找小於 `node->size` 的最大節點。這個函式的結果用於 刪除 `target` 節點時,找到適合替換 `target` 的節點。
---
`remove_free_tree()`
```c
void remove_free_tree(block_t **root, block_t *target)
```
從二元搜尋樹(`BST`)中移除 `target`,並確保 `BST` 結構保持完整。
刪除一個節點時,可能發生三種情況:
1.`target` 沒有子節點 → 直接刪除。
2.`target` 只有一個子節點 → 讓 `target` 的父節點直接指向 `target` 的子節點。
3.`target` 有兩個子節點 → 找到 `target` 的 `predecessor` 來替代 `target`,保持 `BST` 屬性。
---
EEEE : `(*pred_ptr)->r` , 因為它會走訪 `target` 節點的 左子樹,找到其中的最右節點。
FFFF : `pred_ptr` , 因為它代表如何向右子樹移動,即應該指向 `pred_ptr` 的 右子節點,直到找到最右節點。
#### 延伸閱讀 : 解釋上方程式碼運作的原理,並嘗試補完程式碼,使其可獨立執行,應提供相關的測試程式碼
這段程式碼的目的是 管理樹狀結構的記憶體區塊,其中 `block_t` 作為 二元搜尋樹(`BST`)節點,用來管理空閒記憶體區塊(`free block`)。主要功能包括:
`find_free_tree()`:在樹中尋找 指定大小的記憶體區塊。
`find_predecessor_free_tree()`:尋找 指定節點的 `predecessor`。
`remove_free_tree()`:從 `BST` 中 移除記憶體區塊,並維持 `BST` 屬性。
補完程式碼:
```c
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef struct block {
size_t size;
struct block *l, *r;
} block_t;
// 在二元搜尋樹中尋找適合大小的 free block
block_t **find_free_tree(block_t **root, block_t *target) {
block_t **current = root;
while (*current) {
if ((*current)->size == target->size) {
return current;
} else if ((*current)->size > target->size) {
current = &(*current)->l;
} else {
current = &(*current)->r;
}
}
return NULL;
}
// 找到左子樹的最右節點
block_t *find_predecessor_free_tree(block_t **root, block_t *node) {
if (!node || !node->l) return NULL;
block_t *pred = node->l;
while (pred->r) {
pred = pred->r;
}
return pred;
}
// 從 free tree 中移除一個節點
void remove_free_tree(block_t **root, block_t *target) {
block_t **node_ptr = find_free_tree(root, target);
if (!node_ptr || !*node_ptr) return;
// 如果節點有兩個子節點
if ((*node_ptr)->l && (*node_ptr)->r) {
block_t **pred_ptr = &(*node_ptr)->l;
while ((*pred_ptr)->r) // EEEE
pred_ptr = &(*pred_ptr)->r; // FFFF
block_t *expected_pred = find_predecessor_free_tree(root, *node_ptr);
assert(expected_pred == *pred_ptr);
if (*pred_ptr == (*node_ptr)->l) {
block_t *old_right = (*node_ptr)->r;
*node_ptr = *pred_ptr;
(*node_ptr)->r = old_right;
} else {
block_t *old_left = (*node_ptr)->l;
block_t *old_right = (*node_ptr)->r;
block_t *pred_node = *pred_ptr;
remove_free_tree(&old_left, *pred_ptr);
*node_ptr = pred_node;
(*node_ptr)->l = old_left;
(*node_ptr)->r = old_right;
}
}
// 只有一個子節點
else if ((*node_ptr)->l || (*node_ptr)->r) {
block_t *child = ((*node_ptr)->l) ? (*node_ptr)->l : (*node_ptr)->r;
*node_ptr = child;
}
// 沒有子節點
else {
*node_ptr = NULL;
}
target->l = NULL;
target->r = NULL;
}
// 插入新節點到 free tree
void insert_free_tree(block_t **root, block_t *node) {
if (!*root) {
*root = node;
node->l = node->r = NULL;
return;
}
if (node->size < (*root)->size) {
insert_free_tree(&(*root)->l, node);
} else {
insert_free_tree(&(*root)->r, node);
}
}
// 輔助函式:中序走訪列印二元搜尋樹
void inorder_traversal(block_t *root) {
if (root) {
inorder_traversal(root->l);
printf("%zu ", root->size);
inorder_traversal(root->r);
}
}
```
---
#### 延伸閱讀 : 參閱 memory-allocators,針對你補完 (及後續改進) 的記憶體配置器,進行效能評比,並解讀其效能表現
```shell
i-ying-tsai@i-ying-tsai-G5-KF5:~/quiz1/memory-allocators/build$ cmake ..
-- The CXX compiler identification is GNU 13.3.0
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- The C compiler identification is GNU 13.3.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Configuring done (0.4s)
-- Generating done (0.0s)
-- Build files have been written to: /home/i-ying-tsai/quiz1/memory-allocators/build
i-ying-tsai@i-ying-tsai-G5-KF5:~/quiz1/memory-allocators/build$ make
[ 25%] Building C object CMakeFiles/quiz2.dir/quiz2.c.o
[ 50%] Building CXX object CMakeFiles/quiz2.dir/src/StackAllocator.cpp.o
/home/i-ying-tsai/quiz1/memory-allocators/src/StackAllocator.cpp: In member function ‘virtual void* StackAllocator::Allocate(std::size_t, std::size_t)’:
/home/i-ying-tsai/quiz1/memory-allocators/src/StackAllocator.cpp:39:39: warning: narrowing conversion of ‘padding’ from ‘std::size_t’ {aka ‘long unsigned int’} to ‘char’ [-Wnarrowing]
39 | AllocationHeader allocationHeader{padding};
| ^~~~~~~
[ 75%] Building CXX object CMakeFiles/quiz2.dir/src/PoolAllocator.cpp.o
[100%] Linking CXX executable quiz2
[100%] Built target quiz2
i-ying-tsai@i-ying-tsai-G5-KF5:~/quiz1/memory-allocators/build$ ./quiz2
原始 free tree: 5 10 15 20 30
移除 size 10
更新後 free tree: 5 15 20 30
移除 size 20
更新後 free tree: 5 15 30
```
---
### 測驗 3
`node_t` :
```c
typedef struct __node {
long value;
struct list_head list;
} node_t;
```
`value`:儲存節點的值(數值)。
`list`:使用 `Linux Kernel list_head` 來管理鏈結串列。
---
`list_entry` :
```c
#define list_entry(ptr, type, member) \
((type *)((char *)(ptr) - (unsigned long)(&((type *)0)->member)))
```
`ptr`:指向 `struct list_head` 的指標(即 `next` 或 `prev`)。
`type`:包含 `struct list_head` 的結構體類型(如 `node_t`)。
`member`:`struct list_head` 在 `type` 結構中的成員名稱(如 `list`)。
它會從 `list_head` 的指標 `ptr`,計算出 `type` 結構體的起始位置。
---
GGGG :` head->prev` , 因為需要確保雙向鍊結串列可以閉合。
HHHH : `list_entry(pivot,node_t,list)->value` , 因為它要取出 `pivot` 節點的 `value`。
IIII : `list_entry(n,node_t,list)->value` , 因為它要取出 `n` 節點的 `value`。
JJJJ : `pivot` , 因為當快速排序拆分鏈結串列時,pivot 作為 新的中間點,用來重新組合。
KKKK : `right` , 因為這個快速排序是從 `right` 部分開始排序,所以 `right` 需要先被放到 `begin[i + 2]`。
---
#### 延伸閱讀 : 解釋上述程式碼運作原理
---
#### 延伸閱讀 : 研讀〈A comparative study of linked list sorting algorithms〉,分析針對雙向鏈結串列的排序演算法考量,並運用 Linux 核心風格的 List API 予以實作
---
## quiz 2
### 測驗 1