# 2020q3 Homework1 (quiz1) contributed by < ``yceugene`` > ###### tags: `linux2020` `sysprog2020` `quiz` [題目連結](https://hackmd.io/@sysprog/sysprog2020-quiz1) ## :memo: 目錄 [TOC] ## 題目 * 單向 linked list 結構定義: ``` typedef struct __node { int value; struct __node *next; } node_t; ``` * 考慮以下函數: * add_entry: 新增節點 * find_entry: 回傳包含指定數值的節點之記憶體位址 * remove_entry: 移除指定節點 * swap_pair: 交換一對相鄰的節點 * reverse: 將給定的 linked list 其內節點予以反向 ## 1. Linked psCOMMANDLIST ### add_entry(): ```c= void add_entry(node_t **head, int new_value) { node_t **indirect = head; node_t *new_node = malloc(sizeof(node_t)); new_node->value = new_value; new_node->next = NULL; assert(new_node); while (*indirect) indirect = &(*indirect)->next; *indirect = new_node; } ``` * 動作說明: * 傳入參數 **head, 用來傳入 node list 的根. 在初始狀況時, *head (*indirect) 為 NULL, 則用來傳回第一個 node (root) 的位址. * 接著使用 malloc() 來取得 node 的記憶體, 將 value 設定, 因為新加入的 node, 會在 list 的最尾端, 因此一併指定其 next 為 NULL. * assert(new_node) 用來確保 new_node 非 NULL. 在此建議將 assert(new_node) 上移緊接著 malloc() 之後, 因為 new_node 一旦為 NULL, 之後的 new_node->value ... 會造成 code dump. 在真正的 application 時, assert() 可能不夠, 必須加入完整的 exception handling, 來確保一旦 malloc() 失敗, new_node 為 NULL時, 系統不會 hang up. * 在初始狀態時, *head (即 *indirect) 為 NULL, 因此 while 迴圈不會執行, 只要 malloc() 成功傳會 new_node 非 NULL, 就會由 *indirect (即 *head) 傳回 new_node 當成第一個 node (root) * 非初始狀態時, *head (即 *indirect) 非NULL, while 迴圈啟動尋找 next, 直到找到最後一個 node 為 NULL,在將 new_node 接上最後一個 node的 next 並結束執行. 如此 new_node 就變成最後一個 node 了. ### find_entry(): ```c= node_t *find_entry(node_t *head, int value) { node_t *current = head; for (; current && current->value != value; current = current->next) /* interate */; return current; } ``` * 動作說明: * 在 for 迴圈中尋找符合 value的 node,找到後, 將該node (即 current) 傳回. * 如果 value 在 linked list 裡頭找不到, 則會傳回 NULL. 在呼叫此函式時, 可以加入適當的防呆動作, 確保程式在找不到 value 時, 依然可以正常動作! ### remove_entry(): ```c= void remove_entry(node_t **head, node_t *entry) { node_t **indirect = head; while ((*indirect) != entry) indirect = &(*indirect)->next; *indirect = entry->next; free(entry); } ``` * 動作說明: * 在 for 迴圈尋找目標 node (這裡是 entry). 找到之後, 將 entry->next 指向前一個 node, 再將 entry 移除 (free()). * 當 entry 在 linked list 裡找不到時, 會發生 code dump, 因此建議在找不到 entry 時, 應該及時提醒並適當返回 (如下:) ```c= void remove_entry(node_t **head, node_t *entry) { node_t **indirect = head; while ((*indirect) != entry) { indirect = &(*indirect)->next; if( *indirect == NULL ) // protect printf("entry(%p) not found!", entry); return; } *indirect = entry->next; free(entry); } ``` ### swap_pair(): ```c= node_t *swap_pair(node_t *head) { for (node_t **node = &head; *node && (*node)->next; node = &(*node)->next->next) { node_t *tmp = *node; *node = (*node)->next; tmp->next = (*node)->next; (*node)->next = tmp; } return head; } ``` * 動作說明: * swap_pair() 在 for loop 裡, 每次會處理兩個 node, 依序更新其 next 指標, 並將最後的 next 指向原 next 的下一個 next, 來繼續下一個 swap. * 當 linked list 為奇數個時, 最後一個 node 會保留不動作. ### reverse(): ```c= node_t *reverse(node_t *head) { node_t *cursor = NULL; while (head) { node_t *next = head->next; head->next = cursor; cursor = head; head = next; } return cursor; } ``` * 動作說明: * cursor 其初始值為 NULL, 會被指定給 head->next 當作reverse()完成後的最後一個 node. * next 會指向下個 node, 並在迴圈結束前, 指給當前的 node (head), 以進行下一個迴圈. * 當 head 為 NULL 時, cursor 指向原 list 的最後一個 node. 在 reverse() 後, 會被返回 當成新 list 的第一個 node (root). ### execution: ``` 72 @0x5620d678d6b0 next:0x5620d678d6d0 101 @0x5620d678d6d0 next:0x5620d678d6f0 108 @0x5620d678d6f0 next:0x5620d678d710 109 @0x5620d678d710 next:0x5620d678d730 110 @0x5620d678d730 next:0x5620d678d750 111 @0x5620d678d750 next:(nil) 72 @0x5620d678d6b0 next:0x5620d678d6f0 108 @0x5620d678d6f0 next:0x5620d678d710 109 @0x5620d678d710 next:0x5620d678d730 110 @0x5620d678d730 next:(nil) 108 @0x5620d678d6f0 next:0x5620d678d6b0 72 @0x5620d678d6b0 next:0x5620d678d730 110 @0x5620d678d730 next:0x5620d678d710 109 @0x5620d678d710 next:(nil) 109 @0x5620d678d710 next:0x5620d678d730 110 @0x5620d678d730 next:0x5620d678d6b0 72 @0x5620d678d6b0 next:0x5620d678d6f0 108 @0x5620d678d6f0 next:(nil) ``` > 為提供多一點的訊息來說明動作, 以下將print_list()修改: ```c= void print_list(node_t *head) { for (node_t *current = head; current; current = current->next) printf("%d \t@%p next:%p\n", current->value, current, current->next); printf("\n"); } (show value, node and next node address instead of value only!) ``` ## extension 1 - 修改 swap_pair() & reverse() ### swap_pair(): ```c= void swap_pair(node_t **_head) { node_t *head = *_head; for (node_t **node = &head; *node && (*node)->next; node = &(*node)->next->next) { node_t *tmp = *node; *node = (*node)->next; tmp->next = (*node)->next; (*node)->next = tmp; } *_head = head; } ``` * 動作說明:(此修改希望盡可能用最少的修改來完成.) * 修改參數成 **_head, 並將返回資料型態改為 void. * 新增變數 node_t *head = *_head. * 用 *_head = head 來取代 return 將 head 傳回. ### reverse(): ```c= void reverse(node_t **_head) { node_t *cursor = NULL; node_t *head = *_head; while (head) { node_t *next = head->next; head->next = cursor; cursor = head; head = next; } *_head = cursor; } ``` * 動作說明:(此修改希望盡可能用最少的修改來完成.) * 同 swap_pair(), 修改參數成 **_head, 並將返回資料型態改為 void. * 新增變數 node_t *head = *_head. * 用 *_head = head 來取代 return 將 head 傳回. ## extension 2 - 使用 recursive 來修改 reverse() ### rev_recursive(): ```c= void rev_recursive(node_t *pPrevNode, node_t *pNode, node_t **root) { if(!pNode) { *root = pPrevNode; return; } rev_recursive(pNode, pNode->next, root); pNode->next = pPrevNode; MDebug(pNode); } ``` ### reverse(): ```c= void reverse(node_t **_head) { node_t *cursor = NULL; rev_recursive(cursor, *_head, _head); } ``` * 動作說明: (recursive reverse 主要有三個動作) * rev_recursive() 會呼叫自己,因此通常不需要while loop. 並且 recursive 時,必須找到停止點, 即 pNode 為 NULL 時. 此時, 必須將此最後一個 node 傳回當成新 list 的 root (第一個 node)並結束遞迴. * 三個參數, *pPrevNode, *pNode 及 **root, 分別代表前一個 node, 當前 node 及 root. root 用以取回新的 head. * 在遞迴中, 依序將 pNode->next 指向 pPrevNode 來完成動作! > :warning: 進一步研究能否減少使用參數!