--- tags: linux2022 --- # 2022q1 Homework1 (quiz1) contributed by < `BensonYang1999` > > [第 1 周測驗題](https://hackmd.io/@sysprog/linux2022-quiz1) ## 測驗1 ### 延伸題目 #### 解釋上述程式碼運作原理 * 資料結構 * `map_t` 包含 `bits` 及 `ht` ,bits儲存hash table的大小( `1<<bits` ),`ht` 為指向 `hlist_head` 的指標 * `hlist_head` 中只有一個指標 `first` ,指向 `hlist_node` * `hlist_node` 包含 `pprev` 及 `next` , `pprev` 指向前一個node, `next` 指向後一個node;這裡比較特殊的地方 `pprev` 是用指標的指標,可以快速修改節點的連結。 * `hash_key` 包含 `key, data, node` ```graphviz digraph { layout = neato; splines = curved; overlap = false; compound = true; subgraph map_t { label = "map_t" node [shape=record] node_map [label="{map_t|bits|<ht>ht}" pos="0,-2.5!"] } subgraph hlist_head { label = "hlist_head" node [shape=record] node_head [label="{hlist_head.first|<f0>[0]|<f1>[1]|<f2>[2]|<f3>[3]|.\n.\n.\n | [1\<\< bits-1]}" pos="1.5,-3!"] } subgraph hlist_node { label = "hlist_node" node [shape=record] node_01 [label="{hlist_node|{<p01>**pprev | <n01>*next}}" pos="4,-2.5!"] node_02 [label="{hlist_node|{<p02>**pprev | <n02>*next}}" pos="6.5,-2.5!"] node_10 [label="{hlist_node|{<p10>**pprev | <n10>*next}}" pos="4, -4!"] } null1 [label="NULL" shape=box pos="8, -2.5!"] null2 [label="NULL" shape=box pos="6, -4!"] // link node_map:ht -> node_head node_head:f0 -> node_01 node_01:n01 -> node_02:w node_02:n02 -> null1 node_head:f1 -> node_10 node_10:n10 -> null2 { edge [color=gray] node_01:p01 -> node_head:f0 node_02:p02 -> node_01 node_10:p10 -> node_head:f1 } } ``` :::info 第一次使用 `graphviz`,對 arrow 的調整還不熟悉,會發生edge與node邊框重疊的問題,不確定要怎麼解決。 ::: :::danger 注意書寫規範:中英文間用一個半形空白字元區隔。 不要急著說「第一次使用」,你很快會接觸一堆工具,理工人說話要精準且資訊量充足。 :notes: jserv ::: * twoSum流程 1. 建立一個map,並設定大小為`10<<bits` 2. 進入迴圈,走訪陣列中的每個數字 1. 先將每個數值都透過`map_get`測試是否有匹配的數值,若匹配成功回傳正確的組合 2. 若找不當組合,則將此數字加入到hash table 3. 將所有創建的記憶體清除 * function說明 * map_init 目的:初始化`map`,建立適當大小的hash table 1. 利用malloc配置空間存放map ```c map_t *map = malloc(sizeof(map_t)); if (!map) return NULL; ``` 2. 配置hash table的記憶體空間,並將所有head的first指標先指向NULL ```c map->bits = bits; map->ht = malloc(sizeof(struct hlist_head) * MAP_HASH_SIZE(map->bits)); if (map->ht) { for (int i = 0; i < MAP_HASH_SIZE(map->bits); i++) (map->ht)[i].first = NULL; } else { free(map); map = NULL; } return map; ``` * find_key 目的:尋找`key`是否存在在hash table中 先找到符合的`head`後,造訪其中的所有`node`,尋找相同`key`的`node` * map_get 目地:利用`find_key`找到符合的節點後,回傳其原始數值的陣列代碼。 * map_add 目地:將原始數值利用hash轉換後,存入hash table,並紀錄陣列代碼。 詳細步驟: 1. 先確保要插入的值不存在在hash table中 (3~5) 2. 配置空間存放hash_key (7, 8) 3. 找到應當插入的hash table欄位 (10) 4. 找到新節點的記憶體位置n及原本存在欄位中的節點first (11) 5. 將新節點的next指向原本存在欄位中的節點first,因此本位置應填入`n->next = first;` (12) 6. 若原節點存在,須修改該節點的prev (13, 14) 7. 將head接到新節點上 (15) 8. 最後應將新節點的prev連接到head,因此本位置應填入`n->pprev = &h->first;` (16) ```c= void map_add(map_t *map, int key, void *data) { struct hash_key *kn = find_key(map, key); if (kn) return; kn = malloc(sizeof(struct hash_key)); kn->key = key, kn->data = data; struct hlist_head *h = &map->ht[hash(key, map->bits)]; struct hlist_node *n = &kn->node, *first = h->first; n->next = first; if (first) first->pprev = &n->next; h->first = n; n->pprev = &h->first; } ``` * map_deinit 目的:把整個map中所有的 `map, hash_key, node` 清空 #### 研讀 Linux 核心原始程式碼及對應的文件,解釋 hash table 的設計和實作手法,並留意到 `GOLDEN_RATIO_PRIME` ,探討其實作考量 * linux 核心利用 Seperate chaining 方式解決衝突 * 利用 number of bits 定義 hash table 的大小 * 利用 golden ratio constants 可有效達成高度隨機 --- ## 測驗2 思路:兩個判斷式都須考慮`下一個節點是否存在`以及`此節點與下一個節點是否擁有相同數值`,因此適當的程式碼為`head->next && head->val == head->next->val` ```c struct ListNode *deleteDuplicates(struct ListNode *head) { if (!head) return NULL; if (head->next && head->val == head->next->val) { /* Remove all duplicate numbers */ while (head->next && head->val == head->next->val) head = head->next; return deleteDuplicates(head->next); } head->next = deleteDuplicates(head->next); return head; } ``` ### 延伸問題 #### 嘗試避免遞迴,寫出同樣作用的程式碼 * 版本一(for leetcode 83):建立一個新的指標`node`指向`head`,利用判斷式確認是否有相同的值,當相同時將當前節的`next`接到`next->next`,代表忽略掉下一個節點,當不同時則直接將指標到下一個點 此版本特點在於會保留重複的數字,不會將其全部刪除 ```c= struct ListNode *deleteDuplicates(struct ListNode *head) { if (!head) return NULL; struct ListNode *node = head; while (node->next) { if (node->val == node->next->val) { node->next = node->next->next; } else { node = node->next; } } return head; } ``` * 版本二(for leetcode 82):利用指標`node`及指標的指標`dnode`,透過迴圈將所有重複的數字跳過,並利用`dnode`把list接到下一個數字 此版本特點在於會將重複的數字全部刪除,不會剩下一個 ```c= struct ListNode *deleteDuplicates(struct ListNode *head) { if (!head) return NULL; struct ListNode *node = head, **dnode = &head; while (node) { if (node->next && node->val == node->next->val) { while (node->next && node->val == node->next->val) node = node->next; *dnode = node->next; // 將dnode指向的指標(一開始為head)指向的下一個(不重複)的節點 node = node->next; } else { dnode = &((*dnode)->next); // 將dnode指向下一個節點 node = node->next; } } return head; } ``` #### 以類似 Linux 核心的 circular doubly-linked list 改寫,撰寫遞迴和迭代 (iterative) 的程式碼 --- ## 測驗3 ## 參考資料 * [graphviz position](https://stackoverflow.com/questions/5343899/how-to-force-node-position-x-and-y-in-graphviz)