---
# System prepended metadata

title: 2024q1 Homework1 (lab0)

---

# 2024q1 Homework1 (lab0)
contributed by < `Willsonbo` >

:::danger
注意空白！
唯有重視小處並步步為營，方可挑戰原始程式碼規模超越三千七百萬行的 Linux 核心。
:::

## 開發環境
```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.
$ 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):                  12
  On-line CPU(s) list:   0-11
Vendor ID:               GenuineIntel
  Model name:            Intel(R) Core(TM) i7-10710U CPU @ 1.10GHz
    CPU family:          6
    Model:               166
    Thread(s) per core:  2
    Core(s) per socket:  6
    Socket(s):           1
    Stepping:            0
    CPU max MHz:         4700.0000
    CPU min MHz:         400.0000
    BogoMIPS:            3199.92
  ```

:::danger
無論標題和內文中，中文和英文字元之間要有空白字元 (對排版和文字搜尋有利)。
:::

在一週的時間發現自己不足，不補三週的內容，也把以前的 fundamental of data structure in c 與 C++ How to Program : Late Objects Version ，才找回對鏈結串列<s>連結串列</s> 鏈結串列與其他演算法的概念<s>感覺</s>

:::danger
注意用語。
$\to$ [資訊科技詞彙翻譯](https://hackmd.io/@sysprog/it-vocabulary)
:::

你所不知道的 C 語言: linked list 和非連續記憶體中提及，指標的指標應用與如何寫出優雅的程式
首先是結構定義:
```c 
typedef struct list_entry {
    int value;//the value stored in noide
    struct list_entry *next;
} list_entry_t;
```

要從給定的鏈結串列中，移走 (remove) 符合特定數值的節點時，可撰寫以下程式碼:
```c
list_entry_t *remove(list_entry_t *head, int value)
{
    if (!head)//linked list without head
        return NULL;
    if (head->value == value)//the node with that value is pointed by head
        return head->next;

    list_entry_t *prev = head;
    list_entry_t *cur = head->next;
    while (cur) {//loop through the list to find the node with the value
        if (cur->value == value) {
            prev->next = cur->next;
            return head;
        }
        prev = cur;
        cur = cur->next;
    }
    return head;               
}
```

這段程式碼考慮到開頭的節點是否被移走，於是要有對應的程式碼來處理特例，最終回傳新的開頭節點。改進方式為，引入一個暫時節點，使其在給定的開頭節點之前，這樣就不用特別撰寫程式碼來處理特例：
```c
list_entry_t *remove(list_entry_t *head, int value)
{
    list_entry_t dummy = {.next = head};
    for (list_entry_t *prev = &dummy; prev->next; prev = prev->next) {
        if (prev->next->value == value) {
            prev->next = prev->next->next;
            break;
        }
    }
    return dummy.next;
}
```

> The first operand of the . operator shall have a qualified or unqualified structure or union
type, and the second operand shall name a member of that type.

參考規格書中針對 `.` 的敘述，此函式將 dummy 視為 list 的一員

不過這段程式碼依舊可改進，因為我們根本不用回傳新的開頭節點，相反的，可將函式原型改為 `void remove(list_entry_t **head, int value)`，藉由間接指標來更動傳入的鏈結串列開頭節點。

```c
void *remove(list_entry_t **head, int value)//**head is the pointer to the head
{
    for (list_entry_t *prev = &(**head); prev->next; prev = prev->next) {
        if (prev->next->value == value) {
            prev->next = prev->next->next;
            break;
        }
    }
}
```
在閱讀作業時了解到 `e.g.` 是範例與舉例的簡寫

## 指定的佇列操作
參考  <pao0626> 、<ShallowFeather>、<vax-r>

### `q_new`
>建立新的「空」佇列
* 問題：如何建立佇列、何謂空
* 方法：利用The Linux Kernel API中的void INIT_LIST_HEAD(struct list_head *list)建立新的list head
* 實作：建立空序列時先用malloc配置list_head大小的記憶體，INIT_LIST_HEAD來初始化


###  `q_free`
>釋放佇列所佔用的記憶體;

* 問題：鏈結串列佔用哪些記憶體、如何釋放
* 方法：釋放指向頭節點的指標和走訪並釋放各個節點的指標與內容
* 實作：利用list_for_each_entry_safe( The Linux Kernel API - List Management Functions)、q_release_element(queue.h)


###  `q_insert_head` 
>在佇列開頭 (head) 插入 (insert) 給定的新節點 (以 LIFO 準則)、`q_insert_tail`: 在佇列尾端 (tail) 插入 (insert) 給定的新節點 (以 FIFO 準則);
* 問題：如何確保可以插入並插入新節點
* 方法：確保head指標非空、確保節點本身與value記憶體成功分配，插入節點方法
* 實作：利用malloc分配記憶體給節點本身、strdup分配記憶體給value、並使用list_add、list_add_tail插入新節點
strdup：


### `q_remove_head`在佇列開頭 (head) 移去 (remove) 給定的節點、`q_remove_tail`: 在佇列尾端 (tail) 移去 (remove) 給定的節點;
* 問題：如何確保有節點與內容可以移除並將其移除，如何找到佇列開頭與結尾
* 方法：判段head指標與佇列本身是否為空、找到佇列頭的或尾端、將節點移除佇列、回傳被移除的佇列的值
* 實作：可以呼叫head指向的值或是用list_first_entry，尾端則可以透過list_last_entry、list_del可以移除佇列的節點
bufsize：


### `q_size`
>計算目前佇列的節點總量;
* 問題：如何計算節點數量
* 方法：確保佇列不為空且有函數走訪佇列的每個值
* 實作：判斷head是否為空指標並利用list_for_each走訪每個節點
list_for_each：

### `q_delete_mid`
>移走佇列中間的節點
* 問題：何謂中間?如何找到、是否有東西可以刪
* 方法：因為 queue 是用 circuler linked list 實作，因此可以藉由分別往兩個方向訪問，直到碰頭代表已找到中間點
* 實作：從head開始一邊向first:head->next一邊向second:head->pre開始訪問，直到first==second or first->next == second停止，再將該first 點刪除
    
    
### `q_delete_dup`
>在已經排序的狀況，移走佇列中具備重複內容的節點
* 問題：如何找到重複的節點、是否有東西可以刪、是否會重複訪問要定義何時停止
* 方法：用兩個指針訪問每個節點，其中一個放入當前的值，下一個用要被檢查的值，若檢查到一樣的則將其刪除
* 實作：list_for_each_entry_safe(cur, tmp, head, list) ，cur 表示正在訪問的節點，tmp表示下一個要訪問的節點，用list_for_each_entry_safe的好處是當cur被刪除時還是可以，安全的從tmp接續訪問
    
### `q_swap`
>交換佇列中鄰近的節點，詳見 LeetCode 24. Swap Nodes in Pairs
* 問題：以兩個節點為一組，兩兩交換，在訪問每一組時將他們交換
    Input: head = [1,2,3,4]->Output: [2,1,4,3]
    要如何在走訪節點時可以兩兩一組？要如何讓節點位置交換
* 方法：void list_move(struct list_head *list, struct list_head *head)可以讓list節點接續head節點
* 實作：用list_for_each走訪節點，並用list_move將當前接續下一個節點，接著第二次遞迴時，他會從根據順序走訪第一個節點的下一個節點，也就是第三個節點，以此接續直到到，下一個節點為head時結束
    list_move後為何用list_for_each還能走訪到被移動節點的下一個節點
    
### `q_reverse`
>以反向順序重新排列鏈結串列，該函式不該配置或釋放任何鏈結串列節點，換言之，它只能重排已經存在的節點
* 問題：將鏈結串列重新反方向排序
* 方法：從head->pre開始反向走訪節點直到head停止
* 實作：用list_for_each_reverse反向走訪與list_move調整節點位置
    
    
### `q_reverseK`
* 問題：如何將鏈結串列以每 k 個節點為一組進行反轉？
* 方法：預先配置一個新的鏈結串列，用於存儲反轉後的結果。每次從原始鏈結串列中取出 k 個節點，並進行反轉，然後將反轉後的結果添加到新鏈結串列的末尾。最後將新鏈結串列中的結果複製回原始鏈結串列。
* 實作：藉由list_cut_position
    
### `q_sort`
>以遞增順序來排序鏈結串列的節點，可參閱 Linked List Sort 得知實作手法

* 方法：利用 mergesort 演算法進行排序，將鍵結串列切割為較小的子串列，然後逐步合併這些子串列。
透過遞迴實現 mergesort 演算法，每次將鍵結串列切割成兩半，然後分別對兩半進行排序和合併。
* 實作：首先判斷鍵結串列是否為空或只有一個節點，若是則直接返回，使用快慢指針找到鍵結串列的中間節點，將其分為兩個子串列，遞迴調用 mergesort 函式對這兩個子串列進行排序，最後，將排好序的兩個子串列進行合併，得到最終的排序結果。
  
    
### `q_descend`
>參閱 LeetCode 2487. Remove Nodes From Linked List
    
   - 問題：如何在給定的鏈結串列中移除具有比其後面節點值更小的節點？
  
- 方法：反向遍歷鏈結串列，比較當前節點和其後面節點的值，如果當前節點的值比後面節點的值小，則刪除後面節點。
  
- 實作：
    ```c
    int q_descend(struct list_head *head)
    {
        // 確保 head 不為空
        if (!head)
            return 0;

        // 初始化節點計數
        int len = 0;

        // 從最後一個節點開始向前遍歷
        struct list_head *cur = head->prev;

        // 遍歷整個鏈結串列
        while (cur != head) {
            // 增加節點計數
            len++;

            // 如果下一個節點是 head，則跳出循環
            if (cur->prev == head)
                break;

            // 將節點轉換為元素結構
            element_t *c = container_of(cur, element_t, list),
                      *p = container_of(cur->prev, element_t, list);

            // 如果下一個節點的值大於當前節點的值，則刪除下一個節點
            if (strcmp(c->value, p->value) > 0) {
                list_del(&p->list);
                q_release_element(p);
                len--;
            } else {
                // 否則，繼續向前走訪
                cur = cur->prev;
            }
        }

        // 返回節點計數
        return len;
    }
    ```
    
    
    

 ### `q_merge`
>合併所有已排序的佇列，並合而為一個新的已排序佇列
    
* 問題：如何將所有已排序的佇列合併成一個新的已排序佇列？

* 方法：
    1. 檢查佇列是否為空，如果是，則返回。
    2. 取得第一個佇列。
    3. 如果只有一個佇列，則直接返回該佇列的大小。
    4. 從第二個佇列開始，將每個佇列的節點合併到第一個佇列中。
    5. 將合併後的佇列進行排序。
    6. 返回合併後的第一個佇列的大小。

* 實作： 
    ```c
    int q_merge(struct list_head *head)
    {
        if (!head || list_empty(head))
            return 0;

        // 取得第一個佇列
        queue_contex_t *fir = container_of(head->next, queue_contex_t, chain);

        // 如果只有一個佇列，直接返回大小
        if (head->next == head->prev)
            return fir->size;

        // 從第二個佇列開始合併
        for (struct list_head *cur = head->next->next; cur != head; cur = cur->next) {
            // 取得當前佇列
            queue_contex_t *que = container_of(cur, queue_contex_t, chain);

            // 將當前佇列合併到第一個佇列中
            list_splice_init(que->q, fir->q);

            // 將當前佇列大小歸零
            que->size = 0;
        }

        // 對第一個佇列進行排序
        q_sort(fir->q);

        // 更新第一個佇列的大小
        fir->size = q_size(fir->q);

        // 返回第一個佇列的大小
        return fir->size;
    }
    ```