---
# System prepended metadata

title: 2025q1 Homework2 (quiz1+2)

---

# 2025q1 Homework2 (quiz1+2)
contributed by <eric895888>

{%hackmd NrmQUGbRQWemgwPfhzXj6g %}
## 開發環境
```shell
$ gcc --version 
gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0
Copyright (C) 2021 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

$ ldd --version
ldd (Ubuntu GLIBC 2.35-0ubuntu3.9) 2.35
Copyright (C) 2024 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Written by Roland McGrath and Ulrich Drepper.

$ lscpu
Architecture:             x86_64
  CPU op-mode(s):         32-bit, 64-bit
  Address sizes:          39 bits physical, 48 bits virtual
  Byte Order:             Little Endian
CPU(s):                   8
  On-line CPU(s) list:    0-7
Vendor ID:                GenuineIntel
  Model name:             11th Gen Intel(R) Core(TM) i5-11300H @ 3.10GHz
    CPU family:           6
    Model:                140
    Thread(s) per core:   2
    Core(s) per socket:   4
    Socket(s):            1
    Stepping:             1
    CPU max MHz:          4400.0000
    CPU min MHz:          400.0000
    BogoMIPS:             6220.80
```
# 第一週測驗題
### 測驗 1
測驗1實作鏈結串列的插入與基本操作，`list_insert_before()` 用於在指定節點前插入新節點，並透過指標操作維持鏈結串列的結構，`test_list()` 測試不同插入情境，包括在串列開頭與結尾插入節點，並使用 `my_assert()` 確保串列的長度與順序正確，`list_reset()` 重新初始化測試資料，t`est_suite()` 執行所有測試，而 `main()` 呼叫 `test_suite()` 並輸出測試結果。
而 `list_insert_before()` 目的是插入 item 節點於 before 之前，若 before == NULL，則插入到鏈結串列的末端，使用 list_item_t **p 來遍歷鏈結串列，使用指標的指標方式確保能直接修改指向的位置，如果 before 是 head，則 head 直接更新為 item。

AAAA 填入&l->head是遍歷的起點。
BBBB 填入before是搜尋的目標。
CCCC 填入&(*p)->next是遍歷方式。
DDDD 填入item->next讓 item 的 next 指向 before。  
```c
static void list_insert_before(list_t *l,list_item_t *before, list_item_t *item)
{
    if (!l || !before || !item) 
        return;  

    list_item_t **p;

    for (p = &l->head; *p != before; p = &(*p)->next)
        ;

    *p = item;
    item->next = before;
}

```

### 合併排序 
這段程式碼使用合併排序（Merge Sort）來對單向鏈結串列進行排序，首先，`findMid()` 使用快慢指標找到鏈結串列的中點，將其拆分為兩半，接著，`mergeSort()` 採用遞迴方式對兩個子串列分別進行排序，直到每個子串列只剩下一個節點；然後根據 [你所不知道的 C 語言: linked list 和非連續記憶體](https://hackmd.io/@sysprog/c-linked-list#%E6%A1%88%E4%BE%8B%E6%8E%A2%E8%A8%8E-LeetCode-21-Merge-Two-Sorted-Lists) 做出`mergeTwoLists()` 合併兩個已排序的子串列，確保結果仍然是排序好的，最後，`mergeSortList()` 負責處理 list_t 結構，呼叫 `mergeSort()` 排序並更新 head，從而完成整個鏈結串列的排序。
```c
list_item_t *mergeTwoLists(list_item_t *left, list_item_t *right) {
    list_item_t *head;
    list_item_t **ptr = &head;
    while (left && right) {
        if (left->value < right->value) {
            *ptr = left;
            left = left->next;
        } else {
            *ptr = right;
            right = right->next;
        }
        ptr = &(*ptr)->next;
    }
    *ptr = left ? left : right;
    return head;
}

//Fast-Slow Pointer
void findMid(list_item_t *head, list_item_t **left, list_item_t **right) {
    if (!head || !head->next) {
        *left = head;
        *right = NULL;
        return;
    }

    list_item_t *slow = head, *fast = head->next;
    while (fast && fast->next) {
        slow = slow->next;
        fast = fast->next->next;
    }

    *left = head;
    *right = slow->next;
    slow->next = NULL;
}

list_item_t *mergeSort(list_item_t *head)
{
    if(!head || !head->next)
        return head;
    list_item_t *left, *right;

    findMid(&head,left,right);

    left = mergeSort();
    right = mergeSort();
    return sortedMerge(left,right);
    
}
```
### 測驗 2
測驗2的程式碼實作了一個二元搜尋樹（BST）的節點刪除函式 `remove_free_tree()`，用來從 block_t 型態的 BST 中刪除指定的 target 節點。首先，`find_free_tree(root, target)` 找到指向該節點的指標 node_ptr，接著根據 target 節點的子節點情況進行刪除操作，如果 target 有 兩個子節點，則使用中序（in-order predecessor）的走訪方式，也就是左子樹中最大（最右）節點 pred_ptr，然後以 pred_ptr 替換 target，並將 target 的右子樹連接到 pred_ptr。如果 pred_ptr 不是 target 的直接左子節點，則從原來位置刪除 pred_ptr，並將其作為新節點插入。如果 target 只有一個子節點，則直接讓 target 的父節點指向 target 唯一的子節點，繼續維持 BST 結構。如果 target 沒有子節點，則將其設為 NULL，從樹中刪除。最後，target->l 和 target->r 被設為 NULL。
    
`remove_free_tree()` 中如果要被刪除的節點有兩個子樹，使用中序的走訪方式找到左子樹中最右邊的節點。
EEEE 填入 (*pred_ptr)->r 。
FFFF 填入 &(*pred_ptr)->r 則是用來往右走訪的操作。
```c
block_t **pred_ptr = &(*node_ptr)->l;
while ((*pred_ptr)->r)
    pred_ptr = &(*pred_ptr)->r;
```

#### 測驗 2 測試程式碼
```c
/* 在 BST 中尋找節點的指標 */
block_t **find_free_tree(block_t **root, block_t *target) {
    while (*root) {
        if ((*root)->size == target->size)
            return root;
        else if (target->size < (*root)->size)
            root = &((*root)->l);
        else
            root = &((*root)->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;
}

/* 插入節點到 BST */
void insert_free_tree(block_t **root, size_t size) {
    block_t *new_node = (block_t *)malloc(sizeof(block_t));
    new_node->size = size;
    new_node->l = new_node->r = NULL;

    if (!*root) {
        *root = new_node;
        return;
    }

    block_t *parent = NULL, *current = *root;
    while (current) {
        parent = current;
        if (size < current->size)
            current = current->l;
        else
            current = current->r;
    }

    if (size < parent->size)
        parent->l = new_node;
    else
        parent->r = new_node;
}

/* 印出 BST（中序） */
void print_tree(block_t *root) {
    if (!root) 
        return;
    print_tree(root->l);
    printf("%zu ", root->size);
    print_tree(root->r);
}
int main() {
    block_t *root = NULL;

    insert_free_tree(&root, 50);
    insert_free_tree(&root, 30);
    insert_free_tree(&root, 70);
    insert_free_tree(&root, 20);
    insert_free_tree(&root, 40);
    insert_free_tree(&root, 60);
    insert_free_tree(&root, 80);

    printf("BST 初始化中序走訪: ");
    print_tree(root);
    printf("\n");

    block_t target;
    
    target.size = 50;
    printf("刪除節點: %zu\n", target.size);
    remove_free_tree(&root, &target);
    print_tree(root);
    printf("\n");

    target.size = 30;
    printf("刪除節點: %zu\n", target.size);
    remove_free_tree(&root, &target);
    print_tree(root);
    printf("\n");

    target.size = 70;
    printf("刪除節點: %zu\n", target.size);
    remove_free_tree(&root, &target);
    print_tree(root);
    printf("\n");

    return 0;
}
```
### 測驗 3
測驗3的程式碼實作了一個基於單向鏈結串列的快速排序法，不過並不是常見的遞迴方式而是改使用迭代的方式去實作，先建立一個list，並插入隨機順序的 100000 個數字，然後對鏈結串列進行 quick_sort，最後驗證排序結果 (assert(list_is_ordered(list)))，並列印排序後的數據。
    quick_sort 使用 begin 陣列來管理子鏈表的拆分，並在每層迭代選擇第一個節點作為 pivot，將其後的元素依據數值大小分成 left (小於等於 pivot) 和 right (大於 pivot)處理兩部分，最終將結果合併。
    
GGGG 填入head->prev = prev;指向最後一個節點。
HHHH 填入list_entry(pivot,node_t,list)->value。
IIII 填入list_entry(n,node_t,list)->value。
JJJJ 填入pivot。
KKKK 填入right。

# 第二週測驗題
### 測驗 1
程式碼使用 struct listitem 定義雙向鏈結串列節點，並透過 testlist 初始化鏈結串列。首先，程式產生隨機數，`getnum()`透過三個變數計算亂數，`get_unsigned16()` 產生 16-bit 無號整數，而 `random_shuffle_array()` 對 values 陣列進行洗牌，接著，程式遍歷 values 陣列，將其數值存入動態配置的 struct listitem，並插入 testlist 串列中，排序部分使用 `list_quicksort()` 進行快速排序，選擇 pivot 節點後，將較小與較大的節點分別移動到 list_less 和 list_greater，遞迴處理後重新組合串列，排序完成後，程式先使用 `qsort()` 排序 values 陣列作為正確結果，然後檢查 testlist 內的節點是否與 values 內容一致，確保排序正確。最後，程式逐一釋放記憶體，並確認 testlist 已清空 (list_empty(&testlist)) 以確保資源管理正確。
    
AAAA 填入 list_first_entry() 它的作用是獲取鏈結串列的第一個元素。
BBBB 填入 list_del()這是 quicksort 分割（partition）過程的步驟，因為 pivot 不能留在 head 裡，要另外存放，方便後續排序。
CCCC 填入list_move_tail因為需要將大於 pivot 的元素移到 list_greater。
DDDD 填入 list_add()，因為需要把 pivot 放回原本的鏈結串列。
EEEE 填入 list_splice()，因為需要把 pivot 左邊較小的元素併回 head。
FFFF 填入list_splice_tail()，因為需要把 pivot 右邊較大的元素併回 head。

### 測驗 2
`clz2()`若 upper 等於 0，則下一次遞迴呼叫應檢查 lower 部分，並記錄目前已累計的 leading zero，若 upper 不等於 0，則下一次遞迴呼叫應繼續檢查 upper 部分，如果 x == 0 且 c == 0，則回傳 32，表示 32-bit 數字完全沒有 1，全都是 0。

```c
static const int mask[] = {0, 8, 12, GGGG};
static const int magic[] = {HHHH, 1, 0, IIII};

unsigned clz2(uint32_t x, int c)
{
    if (!x && !c)
        return 32;

    uint32_t upper = (x >> (16 >> c));
    uint32_t lower = (x & (0xFFFF >> mask[c]));
    if (c == JJJJ)
        return upper ? magic[upper] : KKKK + magic[lower];
    return upper ? clz2(upper, c + 1) : (16 >> (c)) + clz2(lower, c + LLLL);
}
```
GGGG 填入14，因為要lower要保留的位數是2。
HHHH 填入2
IIII 填入0
JJJJ 填入3
KKKK 填入2
LLLL 填入1
MMMM 填入~1
NNNN 填入1
PPPP 填入2
    
### 測驗 3
這段程式碼實作 twoSum 演算法，用來尋找陣列 nums 中兩個數字相加等於 target 的索引，其中 map_add 用來插入鍵值對，而 map_get 用來查找特定鍵的值，在 twoSum 函式中，程式遍歷 nums 陣列，對於每個數字 nums[i]，計算 target - nums[i] 是否已存在於 hash table 中，若找到則回傳兩個索引，否則將 nums[i] 及其索引存入 hash table 。
AAAA 填入 map->bits
BBBB 填入	map->bits
CCCC 填入	first->pprev
DDDD 填入	n->pprev
EEEE 填入	n->pprev