# 2021q1 Homework1 (lab0)
contributed by < `xralphack` >
### 作業要求
#### fork [lab0-c](https://github.com/sysprog21/lab0-c), 依據指示著手修改 queue.[ch] 和連帶的檔案
- [ ] 詳細閱讀 C Programming Lab
- [x] q_new
- [x] q_free
- [x] q_insert_head
- [x] q_insert_tail
- [x] q_remove_head
- [x] q_size
- [x] q_reverse
- [x] q_sort
### 開發紀錄
#### q_new
```
queue_t *q_new()
{
queue_t *q = malloc(sizeof(queue_t));
if (q == NULL) {
return NULL;
}
q->head = NULL;
q->tail = NULL;
q->size = 0;
return q;
}
```
使用 sizeof 取得 queue_t 結構所需的記憶體大小, malloc 可以配置記憶體並取得該記憶體位置, 透過指標設定初始值, tail 與 size 可使部份函式的時間複雜度縮減到 O(1)
#### q_free
```
{
if (q == NULL) {
return;
}
list_ele_t *nextt = q->head;
while (nextt) {
free(nextt->value);
list_ele_t *tmp = nextt->next;
free(nextt);
nextt = tmp;
}
free(q);
}
```
從 head 開始往後找, 找到一個節點後, 暫存指向下一個節點的指標, 釋放該節點所使用的字串空間和本身的空間
#### q_insert_head
```
bool q_insert_head(queue_t *q, char *s)
{
list_ele_t *newh;
char *copied_string;
if (q == NULL) {
return false;
}
newh = malloc(sizeof(list_ele_t));
if (newh == NULL) {
return false;
}
copied_string = malloc(strlen(s) + 1);
if (copied_string == NULL) {
free(newh);
return false;
}
strlcpy(copied_string, s, (strlen(s) + 1) * sizeof(char));
newh->next = q->head;
newh->value = copied_string;
q->head = newh;
if (q->tail == NULL) {
q->tail = newh;
}
q->size++;
return true;
}
```
先配置好新節點所需的空間與其使用的字串空間, 將 head 改為指向新節點, 如果 tail 不存在, 表示新節點也是最後一個節點, 也要將 tail 改為指向新節點, 並且調整 size
這段程式配置兩次記憶體, 如果第二次失敗, 記得要釋放掉第一次的空間
執行 `git commit -a` 會出現以下錯誤
```
Dangerous function detected in /home/user/linux2021/lab0-c/queue.c
strcpy ...
```
參考 [Common vulnerabilities guide for C programmers](https://security.web.cern.ch/security/recommendations/en/codetools/c.shtml) 用 strlcpy 替換調 strcpy
```
#ifndef strlcpy
#define strlcpy(dst, src, sz) snprintf((dst), (sz), "%s", (src))
#endif
```
#### q_insert_tail
```
bool q_insert_tail(queue_t *q, char *s)
{
list_ele_t *newt;
char *copied_string;
if (q == NULL) {
return false;
}
newt = malloc(sizeof(list_ele_t));
if (newt == NULL) {
return false;
}
copied_string = malloc(strlen(s) + 1);
if (copied_string == NULL) {
free(newt);
return false;
}
strlcpy(copied_string, s, (strlen(s) + 1) * sizeof(char));
newt->next = NULL;
newt->value = copied_string;
if (q->tail) {
q->tail->next = newt;
}
q->tail = newt;
if (q->head == NULL) {
q->head = newt;
}
q->size++;
return true;
}
```
跟 q_insert_head 的概念類似, head 為 `NULL` 指標時插入新節點, 表示該節點也是第一個節點, 因此需要修改 head
#### q_remove_head
```
bool q_remove_head(queue_t *q, char *sp, size_t bufsize)
{
list_ele_t *nextt = NULL;
if (q == NULL || q->head == NULL) {
return false;
}
char *string = q->head->value;
if (sp != NULL) {
int i;
for (i = 0; i < bufsize - 1; i++) {
*(sp + i) = *string;
string++;
}
*(sp + i) = '\0';
}
free(q->head->value);
nextt = q->head->next;
free(q->head);
q->head = nextt;
if (q->head == NULL) {
q->tail = NULL;
}
q->size--;
return true;
}
```
#### q_size
```
int q_size(queue_t *q)
{
if (q == NULL || q->head == NULL) {
return 0;
}
return q->size;
}
```
#### q_reverse
```
void q_reverse(queue_t *q)
{
if (q == NULL || q->head == NULL) {
return;
}
int i;
list_ele_t *prevt = NULL;
list_ele_t *thist = q->head;
for (i = 0; i < q->size; i++) {
if (i == 0) {
q->tail = thist;
} else if (i == q->size - 1) {
q->head = thist;
}
list_ele_t *tmp = thist->next;
thist->next = prevt;
prevt = thist;
thist = tmp;
}
}
```
### q_sort
```
void q_bubble_sort(queue_t *q)
{
for (int i = q->size; i > 0; i--) {
list_ele_t *thist = q->head;
for (int j = 0; j < i - 1; j++) {
if (strcmp(thist->value, thist->next->value) > 0) {
char *tmp = thist->value;
thist->value = thist->next->value;
thist->next->value = tmp;
}
thist = thist->next;
}
}
}
void q_sort(queue_t *q)
{
if (q == NULL || q->head == NULL) {
return;
}
q_bubble_sort(q);
}
```
執行 make test
```
+++ TESTING trace trace-15-perf:
# Test performance of insert_tail, size, reverse, and sort
ERROR: Time limit exceeded. Either you are in an infinite loop, or your code is too inefficient
ERROR: Not sorted in ascending order
--- trace-15-perf 0/6
+++ TESTING trace trace-16-perf:
```
執行 ./qtest 執行 sort 沒有發現無窮迴圈, 目前判斷是使用 bubble sort 效率太差
~~因為太不了解指標, 嘗試要寫 merge sort 還沒有成功~~
```
void q_sort(queue_t *q)
{
if (q == NULL || q->head == NULL) {
return;
}
q->head = merge_sort(q->head);
// 從 tail 指向的節點開始找,每當有下一個就改成是新的最後一個,直到找不到
// 這個寫法效率不佳,應該要在 merge_sort 裡面改好 q->head, q->tail
while (q->tail->next) {
q->tail = q->tail->next;
}
}
list_ele_t *merge_sort(list_ele_t *node)
{
// 當 queue 有兩個元素時才有 sort 的必要
if (!node || !node->next) {
return node;
}
// 讓 fast 端移動的速度快一個節點,直到到達尾端
// 以下程式碼可以把一段元素分成兩段
list_ele_t *slow = node;
list_ele_t *fast = node->next;
while (fast && fast->next) {
slow = slow->next;
fast = fast->next->next;
}
fast = slow->next;
slow->next = NULL;
// 分成兩段後,使用遞迴分成兩段,直到每段剩下一個元素
list_ele_t *node1 = merge_sort(node);
list_ele_t *node2 = merge_sort(fast);
// 把各個段再合併起來
// 因為是遞迴到每段剩下一個元素再合併回去
// 合併時等於是合併已排序過的兩段
return merge(node1, node2);
}
```
之前不太了解有些演算法題目會改一個前提: 已知有兩個排序過的陣列,請你合併並且排序
這次寫了這個題目後了解到在 Divide-and-conquer algorithm 中,因為問題會被切成更小的子問題,子問題解了之後就會得到類似已排序過的結果
```
list_ele_t *merge(list_ele_t *head1, list_ele_t *head2)
{
// 因為是要遞增,所以要知道比較小的一段當作頭,並且在最後回傳
list_ele_t *head = strcmp(head1->value, head2->value) <= 0 ? head1 : head2;
// 紀錄上一個較小值
list_ele_t *cursor = NULL;
while (head1 && head2) {
// 將兩段的元素做比較
// cursor 指向較小的元素
// 如果 cursor 已經有前一個較小的元素,設定 next 讓他們串起來
// 比較小的元素所在的那一段的指標要往下一個移動
if (strcmp(head1->value, head2->value) <= 0) { // 1 <= 2
if (!cursor) {
cursor = head1;
} else {
cursor->next = head1;
cursor = cursor->next;
}
head1 = head1->next;
} else {
if (!cursor) {
cursor = head2;
} else {
cursor->next = head2;
cursor = cursor->next;
}
head2 = head2->next;
}
}
// 比較完之後,檢查沒有被比較完的
if (head1) {
cursor->next = head1;
} else if (head2) {
cursor->next = head2;
}
return head;
}
```