# 2025q1 Homework1 (lab0) contributed by < [`EJ7289`](https://github.com/EJ7289) > {%hackmd NrmQUGbRQWemgwPfhzXj6g %} ## 開發環境 ``` shell $ gcc --version gcc (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0 $ lscpu Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Byte Order: Little Endian Address sizes: 39 bits physical, 48 bits virtual CPU(s): 4 On-line CPU(s) list: 0-3 Thread(s) per core: 2 Core(s) per socket: 2 Socket(s): 1 NUMA node(s): 1 Vendor ID: GenuineIntel CPU family: 6 Model: 142 Model name: Intel(R) Core(TM) i7-7500U CPU @ 2.70GHz Stepping: 9 CPU(s) scaling MHz: 14% CPU max MHz: 3500.0000 CPU min MHz: 400.0000 BogoMIPS: 5799.77 Virtualization: VT-x L1d cache: 64 KiB (2 instances) L1i cache: 64 KiB (2 instances) L2 cache: 512 KiB (2 instances) L3 cache: 4 MiB (1 instance) NUMA node0 CPU(s): 0-3 ``` ## 針對佇列操作的程式碼實作 ### q_new 在 C 語言中, `malloc` 用來分配指定大小的記憶體空間,並且回傳指標,配置的空間尚未初始化。 > ***C99 [7.20.3.3] The malloc function** > `#include <stdlib.h>` > `void * malloc(size_t size);` > **Description** > The **malloc** function allocates space for an object whose size is specified by **size** and whose value is indeterminate. > **Return** > The **malloc** function returns either a null pointer or a pointer to the allocated space.* 若記憶體配置失敗,須回傳空指標,但是 C 語言中的空指標用 NULL 表示。 補充:C++11 中的空指標用 nullptr 表示。 ``` c struct list_head *q_new(){ struct list_head *h = (struct list_head *)malloc(sizeof(struct list_head)); if (!h) { return NULL; } h->next = h; h->prev = h; return h; } ``` ### q_free 注意事項: - list_head 為**雙向循環鏈結串列**, head 一直指向 next 也不會是 NULL - 釋放記憶體,須將 elem 以及 elem->value 的記憶體都釋放 - ==為什麼不能不能只釋放 elem 就好?== - ==為什麼另外釋放 elem->value 不釋放 elem->list ?== - 最後也需要將 head 釋放 以下程式會陷入無窮迴圈: ``` c void q_free(struct list_head *head) { while(head) { struct list_head *next = head->next; free(head); head = next; } } ``` > 放 github 的程式 `free` 函數的引數為指標。 > ***C99 [7.20.3.2] The free function** > `#include <stdlib.h>` > `void free(void *ptr);`* ### q_insert_head/q_insert_tail - [ ] 原本的程式1: ``` c bool q_insert_head(struct list_head *head, char *s) { element_t *newq = malloc(sizeof(element_t)); if (!newq) { return false; } newq->value = s; newq->list.next = head; newq->list.prev = head->prev; return true; } ``` 以上程式的問題有: 1. 若 s 是**區域變數**,會在離開函數時被釋放,導致外界無法取得。 2. 該佇列為**雙向循環**鏈結串列,因此需要原本的鏈結串列指向新建的。 修改方法: 1. 為了讓外界能讀取 s 的值,使用 `strdup()` 複製字串。 `strdup()` 會用 malloc 獲得新的字串,並且可用 free 釋放字串。 2. 雙向循環的鏈結串列在新增節點時,需要打斷2條鏈和新增4條鏈。被打斷的鏈會使用到,因此需要注意新增4條鏈的順序。 - [ ] 改寫為程式2: ``` c bool q_insert_tail(struct list_head *head, char *s) { // Create a new queue element_t *newq = malloc(sizeof(element_t)); if (!newq) { return false; } // Duplicate s in global memory and storage in new queue newq->value = strdup(s); if (!newq->value) { free(newq); return false; } // Insert s at the tail of queue newq->list.next = head; newq->list.prev = head->prev; head->prev->next = &newq->list; head->prev = &newq->list; return true; } ``` - [ ] 程式3:使用 API 簡化程式 > 放 github 的程式 ### q_remove_head/q_remove_tail 在 queue.h 說明: 1. `q_remove_head(struct list_head *head, char *sp, size_t bufsize)` - head: header of queue - sp: 插入要移除的字串 - bufsize: 字串 *sp 可以容納的做大空間 原本程式碼: ``` c element_t *q_remove_head(struct list_head *head, char *sp, size_t bufsize) { if (!head || list_empty(head)) { return NULL; } // find the element of node element_t *h = list_entry(head, element_t, list); sp = h->value; list_del_init(head); return h; } ``` 以上程式的問題有: 1. head 為 dummy 而不是起始 node,真正的起始 node 為 head->next - 改為 `list_entry(head->next, element_t, list)` - 改為 `list_del_init(head->next)` 2. `sp = h->value` 無法回傳字串 <font color="#0000FF">- h 為區域變數</font> - 若用 `*sp = *h->value` 只能回傳一個字元,無法回傳字串 - 使用 `strncpy` 複製 sp ==如果不複製為怎樣?== :::spoiler **char 的使用** - `char` **只能**作為字元,例如 `char a = 'a'` ==char的字元宣告必須用**單引號**== - `char` **不能**作為字串的宣告,例如 ~~`char a = "good"`~~ - `char *` 則作為字串的指標宣告 - `char *a = "a"` ==char的指標宣告必須用**雙引號**== - `char *b = "good"` ``` c char *a = "a"; char *b = "banana"; printf("%c\n", a); // error printf("%c\n", *a); // correct, a printf("%c\n", b); // error printf("%c\n", *b); // correct, b printf("%s\n", b); // correct, banana printf("%s\n", *b); // error ``` ::: - [ ] 程式2: ``` c element_t *q_remove_head(struct list_head *head, char *sp, size_t bufsize) { if (!head || list_empty(head)) { return NULL; } // find the element of node element_t *h = list_entry(head, element_t, list); if (sp) { sp = strncpy() } sp = h->value; list_del_init(head); return h; } ``` ### q_size 解題思維: 1. 利用 count 在迴圈內計算 2. head 為 dummy 並非直接指向第一個節點 > insert github's commit ### q_delete_mid 解題思維: 1. 排除 head 為 NULL 或 empty 的情況 2. 用快慢指標找到鏈結串列的中間點 3. 移除鏈結串列的中間點 (利用 `list_del()`) 5. 釋放中間點 (須同時釋放 `elem` 和 `elem->value`) > insert github's commit ### q_delete_dup 目的:將**相鄰**且**相同**的數值刪除 <!-- 情境分析: | prev | curr | next | step 1 | step 2 | | ---- | ---- | ---- | ------ | ------ | | 0 | 1 | 2 | | | | 1 | 1 | 2 | | 0 | 1 | 1 | | 1 | 1 | 1 | --> 解法: 1. 比較 curr 與 next 是否相同 2. 若相同,將布林變數改成 true ;若不相同,將布林變數改成 false 3. 布林變數為 false (不動作);布林變數為 true 和布林變數為 true 變成 false (刪除前一個數值 prev ) - [ ] 原本程式 1 ``` c bool q_delete_dup(struct list_head *head) { // https://leetcode.com/problems/remove-duplicates-from-sorted-list-ii/ if (!head || list_empty(head)) return false; struct list_head *curr = head->next; struct list_head *next = curr->next; bool del = false; while(curr!=head) { element_t *elem = list_entry(curr, element_t, list); element_t *safe = list_entry(next, element_t, list); if (elem->value == safe->value) { del = true; list_for_each_entry_safe(elem, safe, head, list); free(elem->value); free(elem); } else if ((elem->value != safe->value) && (del == true)) { del = false; list_for_each_entry_safe(elem, safe, head, list); free(elem->value); free(elem); } curr = next; next = next->next; } return true; } ``` 程式 1 存在的問題: 1. elem->value 指向的是 **value 的地址**,因此要用 `strcmp()` 2. `list_for_each_entry_safe()` 的使用方式不正確 在 `list.h` 中說明`list_for_each_entry_safe()` 可用來代替一個 for 迴圈。 ``` c #define list_for_each_entry_safe(entry, safe, head, member) \ for (entry = list_entry((head)->next, typeof(*entry), member), \ safe = list_entry(entry->member.next, typeof(*entry), member); \ &entry->member != (head); entry = safe, \ safe = list_entry(safe->member.next, typeof(*entry), member)) ``` 最基本的定義方式`#define MACRO value` => `#define MAX 1000` 3. 須使用 `list_del()` 將指定數值從鏈結串列中移除 4. 需要初始化 `entry=NULL` 和 `safe=NULL` - [ ] 程式 2 > github code ### q_swap 利用 XOR 操作 swap 的行為,不適用在 `struct` 和 `char*` ,因此該題不適用。 ``` c XORswap(int *a, int *b) { *a ^= *b; *b ^= *a; *a ^= *b; } ``` 因此,需要另外存取的空間 tmp 。 解題方法:不改 node ,指改變 `*value` 。(如下方程式所示) ``` c for (; curr != head && curr->next != head; curr = curr->next->next, next = curr->next) { char *tmp = list_entry(curr, element_t, list)->value; list_entry(curr, element_t, list)->value = list_entry(next, element_t, list)->value; list_entry(next, element_t, list)->value = tmp; } ``` > github code ### q_reverse 解題方法: 1. `curr->next = prev` 的概念 但該題目是雙向循環鏈結串列,用該方法修改連接的串列會更複雜一些。 => 方法修改為頭尾互換(如圖所示) => 不改變 node 指改變 `*value` ``` mermaid flowchart LR id0(0) <--> id1(1) <--> id2(2) <--> id3(3) <--> id4(4) <--> id5(5) ``` ``` mermaid flowchart LR id0(5) <--> id1(1) <--> id2(2) <--> id3(3) <--> id4(4) <--> id5(0) style id0 color:#f00; style id5 color:#f00; ``` ``` mermaid flowchart LR id0(5) <--> id1(4) <--> id2(2) <--> id3(3) <--> id4(1) <--> id5(0) style id1 color:#f00; style id4 color:#f00; ``` ### q_reverseK 解題方法: 1. 判斷是否能切成 K 2. K 個切成一個小 queue > github code 在 **C99 [6.8.5.3]** 中,解釋到 for 迴圈的使用如下, `for ( init-clause ; condition ; iteration-expression ) statement` 其中: - init-clause:變數初始化(可為變數宣告或運算式) - condition:循環條件(當條件為 false(0)時停止迴圈) - iteration-expression:每次迴圈執行完後的運算式 - statement:迴圈主體 範例: ``` c for (int a=0, b=0; a<3, b<3; a++, b++, printf("a = %d\n", a)) { printf("b = %d\n\n", b); } ``` 輸出: ``` c b = 0 a = 1 b = 1 a = 2 b = 2 a = 3 ``` ### q_sort - [ ] 原程式 ``` c struct list_head *qsort_split(struct list_head *head, bool descend) { if (!head || list_empty(head) || list_is_singular(head)) return head; struct list_head *base = head->next; struct list_head *curr = head->next->next; struct list_head *left; struct list_head *right; INIT_LIST_HEAD(left); INIT_LIST_HEAD(right); for (; curr != head; curr = curr->next) { if (strcmp(list_entry(base, element_t, list)->value, list_entry(curr, element_t, list)->value) >= 0) { list_add_tail(curr, left); } else { list_add_tail(curr, right); } } if (descend == true) { struct list_head *tmp = left; left = right; right = tmp; } list_add_tail(base, left); list_splice(qsort_split(left, descend), qsort_split(right, descend)); return left; } ``` ==想建立一個新的 dummy 為什麼不能這樣寫?== ``` c struct list_head *dummy; INIT_LIST_HEAD(dummy); ``` ``` c struct list_head *dummy = NULL; INIT_LIST_HEAD(dummy); ``` 為什麼必須這樣寫? ``` c struct list_head dummy; INIT_LIST_HEAD(&dummy); ``` ### q_ascend / q_descend > github code ### q_merge - 使用 `queue_context_t` 取出各個 queue - 輸出 merge queue 時,需要以 `head` 作為 head ? ## 參考資料 1. C 語言動態記憶體的配置:https://blog.gtwang.org/programming/c-memory-functions-malloc-free/#google_vignette 2.