# 2020q3 Homework1 (lab0) contributed by < `zzzxxx0001` > ## 開發環境 ```shell= $ uname -a Linux itlab-right 5.4.0-47-generic #51~18.04.1-Ubuntu SMP Sat Sep 5 14:35:50 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux $ gcc --version gcc (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0 Copyright (C) 2017 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. ``` ## 作業要求 * q_new: 建立新的「空」佇列; * q_free: 釋放佇列所佔用的記憶體; * q_insert_head: 在佇列開頭 (head) 加入 (insert) 給定的新元素 (以 LIFO 準則) * q_insert_tail: 在佇列尾端 (tail) 加入 (insert) 給定的新元素 (以 FIFO 準則) * q_remove_head: 在佇列開頭 (head) 移除 (remove) 給定的元素。 * q_size: 計算佇列中的元素數量。 * q_reverse: 以反向順序重新排列鏈結串列,該函式不該配置或釋放任何鏈結串列元素,換言之,它只能重排已經存在的元素 * q_sort: 以遞增順序來排序鏈結串列的元素 ## 開發過程 * **queue.h** * 根據題目q_insert_tail所需,達到時間複雜度$O(1)$的目標,必須新增tail指標,提供Queue快速尋訪。 * 為快速計算Queue中的node數量,達到時間複雜度$O(1)$的目標,加入`int size`,每成功新增一個節點size加1。 ```cpp typedef struct { list_ele_t *head; /* Linked list of elements */ list_ele_t *tail; int size; } queue_t; ``` * **q_new()** * 透過`malloc`分配記憶體空間給Queue,在初始化之前先確認是否成功分配,以免造成空指標的操作。 ```cpp queue_t *q_new() { queue_t *q = malloc(sizeof(queue_t)); if(q) { q->head = q->tail = NULL; q->size = 0 ; } return q; } ``` * **q_free** * 操作前先確認Queue是否存在,以免造成後續執行錯誤。 * 透過`*temp`指標指向欲刪除的head node。 * 透過while迴圈訪問所有節點,將節點空間依序釋放。 ```cpp void q_free(queue_t *q) { if(!q) return ; while(q->head) { list_ele_t *temp = q->head ; q->head = q->head->next ; temp->next = NULL ; free(temp->value) ; free(temp) ; } free(q); } ``` * **q_insert_head** * 操作前先確認Queue是否存在,以免造成後續執行錯誤。 * 透過`list_ele_t *newh = malloc(sizeof(list_ele_t))`分配記憶體給新的節點,並確認是否成功分配,以免後續操作錯誤。 * 透過`newh->value = malloc(sizeof(char)*( strlen(s)+1 ))`分配適當的記憶體空間,提供給`newh->value`複製`char *s`內容,預留多一格的長度==strlen(s)+1==置放字串終結字元('\0')。 * 透過`strncpy(newh->value, s, strlen(s)+1)`將字串含終結碼一併複製到`newh->value`,因此複製長度為**strlen(s)+1** * insert head分為兩個case: * 如果q->head不存在,則將head與tail指向newh * 如果q->head已存在,newh成為新的head,並將newh->next指向原本的head ```cpp bool q_insert_head(queue_t *q, char *s) { if(!q) return false ; list_ele_t *newh = malloc(sizeof(list_ele_t)); if(!newh) return false ; newh->value = malloc(sizeof(char)*( strlen(s)+1 )) ; if(!newh->value) { free(newh) ; return false ; } q->size ++ ; strncpy(newh->value, s, strlen(s)+1); if(!q->head) { newh->next = NULL ; q->head = q->tail = newh ; return true ; } newh->next = q->head; q->head = newh; return true; } ``` * **q_insert_tail** * 操作前先確認Queue是否存在,以免造成後續執行錯誤。 * 透過`list_ele_t *newh = malloc(sizeof(list_ele_t))`分配記憶體給新的節點,並確認是否成功分配,以免後續操作錯誤。 * 透過`newh->value = malloc(sizeof(char)*( strlen(s)+1 ))`分配適當的記憶體空間,提供給`newh->value`複製`char *s`內容,預留多一格的長度==strlen(s)+1==置放字串終結字元('\0')。 * insert tail分為兩個case: * 如果q->head不存在,則將head與tail指向newh。 * 如果q->head已存在,則將tail->next指向newh,並將newh設為新的tail。 ```cpp bool q_insert_tail(queue_t *q, char *s) { if(!q) return false ; list_ele_t *newh = malloc(sizeof(list_ele_t)) ; if(!newh) return false ; newh->value = malloc(sizeof(char)*(strlen(s) + 1)); if(!newh->value) { free(newh) ; return false ; } q->size ++ ; strncpy(newh->value, s, strlen(s)+1) ; newh->next = NULL ; if(!q->head) { q->head = q->tail = newh ; } else { q->tail->next = newh ; q->tail = newh ; } return true; } ``` * **q_remove_head** * 操作前先確認Queue或Queue->head是否存在,以免造成後續執行錯誤。 * 依循題目提示,將條件寫入if條件式。 >If sp is non-NULL and an element is removed, copy the removed string to *sp (up to a maximum of bufsize-1 characters, plus a null terminator.) * 透過`memset(sp, '\0', bufsize)`將陣列`sp`初始化。 * 因最後一格空間需保留給字串終結字元,因此實際複製字串長度為==bufsize-1==。 * 透過`rm_node`指標指向欲刪除的head node。 * head則由head->next進行取代。 * 將rm_node內所有資料記憶體釋放後,再將rm_node記憶體空間釋放。 ```cpp bool q_remove_head(queue_t *q, char *sp, size_t bufsize) { if(q && q->head) { if(strlen(q->head->value) >= bufsize && !sp) return false ; list_ele_t *rm_node = q->head ; memset(sp, '\0', bufsize) ; strncpy(sp, rm_node->value, bufsize-1) ; q->head = q->head->next ; rm_node->next = NULL ; free(rm_node->value) ; free(rm_node) ; q->size -- ; if(q->size == 0) q->tail = NULL ; return true ; } return false ; } ``` * **q_size** * 操作前先確認Queue是否存在,如果不存在則回傳0。 * 直接將`q->size`回傳,代表Queue中的節點數量。 ```cpp int q_size(queue_t *q) { if(!q) return 0 ; return q->size ; } ``` * **q_reverse** * 此部分參考[quiz_1](https://hackmd.io/@sysprog/sysprog2020-quiz1)中link list的`node_t *reverse(node_t *head)`。 * 操作前先確認Queue狀態,如果Queue不存在、Queue->head不存在或是Queue->size小於2都無法操作reverse,則直接return。 * reverse操作概念已在[quiz1_reverse](https://hackmd.io/y5g1z2z5Ta2Ru8itT39yPQ?view#reverse)中解釋。 * 預先將`q->tail`指向`q->head`,操作完reverse後,再將`q->head`指向目前的`head(cursor)`。 ```cpp void q_reverse(queue_t *q) { if(!q || !q->head || q->size<2) return ; list_ele_t *cursor = NULL ; q->tail = q->head ; while(q->head) { list_ele_t *next = q->head->next ; q->head->next = cursor ; cursor = q->head ; q->head = next ; } q->head = cursor ; } ``` * **q_sort** * Sort方始採merge sort,可達到時間複雜度$O(nlogn)$的理想目標。 * 演算法部分參考[Linked List Sort](https://npes87184.github.io/2015-09-12-linkedListSort/)。 ```cpp list_ele_t *merge(list_ele_t *l1, list_ele_t *l2) { list_ele_t *head = NULL; list_ele_t **temp = &head; while (l1 && l2) { if (strcmp(l1->value, l2->value) <= 0) { *temp = l1; l1 = l1->next; } else { *temp = l2; l2 = l2->next; } temp = &((*temp)->next); } if (l1) *temp = l1; if (l2) *temp = l2; return head; } list_ele_t *mergeSortList(list_ele_t *head) { if (!head || !head->next) return head; list_ele_t *fast = head->next; list_ele_t *slow = head; while (fast && fast->next) { slow = slow->next; fast = fast->next->next; } fast = slow->next; slow->next = NULL; list_ele_t *l1 = mergeSortList(head); list_ele_t *l2 = mergeSortList(fast); return merge(l1, l2); } void q_sort(queue_t *q) { if (!q || q->size == 1) return; q->head = mergeSortList(q->head); while (q->tail) q->tail = q->tail->next; } ``` ## 程式測試 * 透過 `make test` 驗證程式執行狀態是否合乎題意。 * 透過 `clang-format -i <filename>` 修正程式碼編排方式。 ``` $ make test CC qtest.o CC report.o CC console.o CC harness.o CC queue.o CC random.o CC dudect/constant.o CC dudect/fixture.o CC dudect/ttest.o LD qtest scripts/driver.py -c --- Trace Points +++ TESTING trace trace-01-ops: # Test of insert_head and remove_head --- trace-01-ops 6/6 +++ TESTING trace trace-02-ops: # Test of insert_head, insert_tail, and remove_head --- trace-02-ops 6/6 +++ TESTING trace trace-03-ops: # Test of insert_head, insert_tail, reverse, and remove_head --- trace-03-ops 6/6 +++ TESTING trace trace-04-ops: # Test of insert_head, insert_tail, size, and sort --- trace-04-ops 6/6 +++ TESTING trace trace-05-ops: # Test of insert_head, insert_tail, remove_head, reverse, size, and sort --- trace-05-ops 5/5 +++ TESTING trace trace-06-string: # Test of truncated strings --- trace-06-string 6/6 +++ TESTING trace trace-07-robust: # Test operations on NULL queue --- trace-07-robust 6/6 +++ TESTING trace trace-08-robust: # Test operations on empty queue --- trace-08-robust 6/6 +++ TESTING trace trace-09-robust: # Test remove_head with NULL argument --- trace-09-robust 6/6 +++ TESTING trace trace-10-malloc: # Test of malloc failure on new --- trace-10-malloc 6/6 +++ TESTING trace trace-11-malloc: # Test of malloc failure on insert_head --- trace-11-malloc 6/6 +++ TESTING trace trace-12-malloc: # Test of malloc failure on insert_tail --- trace-12-malloc 6/6 +++ TESTING trace trace-13-perf: # Test performance of insert_tail --- trace-13-perf 6/6 +++ TESTING trace trace-14-perf: # Test performance of size --- trace-14-perf 6/6 +++ TESTING trace trace-15-perf: # Test performance of insert_tail, size, reverse, and sort --- trace-15-perf 6/6 +++ TESTING trace trace-16-perf: # Test performance of sort with random and descending orders # 10000: all correct sorting algorithms are expected pass # 50000: sorting algorithms with O(n^2) time complexity are expected failed # 100000: sorting algorithms with O(nlogn) time complexity are expected pass --- trace-16-perf 6/6 +++ TESTING trace trace-17-complexity: # Test if q_insert_tail and q_size is constant time complexity Probably constant time Probably constant time --- trace-17-complexity 5/5 --- TOTAL 100/100 ```