2024q1 Homework2 (quiz1+2)
contributed by<Terry7Wei7>
第一週測驗題
測驗一
運作原理
選擇樞軸(Pivot): 一開始,從待排序的數組中選擇一個元素作為樞軸。這個枢軸的選擇可以是隨機的、固定的,或者使用一些特殊的選擇方法。在這個實現中,它選擇了待排序數組的第一個元素。
分割(Partition): 將數組中的元素分為兩部分,使得樞軸左邊的元素都小於或等於樞軸,右邊的元素都大於或等於樞軸。這是通過左右兩個指針(L和R)的移動實現的,左指針找到一個大於樞軸的元素,右指針找到一個小於樞軸的元素,然後這兩個元素進行交換,重複這個過程直到左右指針相遇。
遞迴: 接下來,分別對樞軸左右兩邊的子數組遞迴地應用相同的排序過程。這就是快速排序的分治策略,將原始問題分解為兩個較小的子問題,然後遞迴解決它們。
結束條件: 遞迴過程中,如果子數組的大小變得足夠小(通常是只有一個元素或沒有元素),則遞迴結束。這時子數組視為已經排序。
繼續遞迴: 遞迴完成後,將所有子數組的結果合併起來,即可得到完全排序的數組。
這個實現中使用了一個額外的數組(beg 和 end)來跟踪每個子數組的起始和結束位置,而不是使用遞迴調用。這樣可以避免函數調用的開銷,並且沒有使用堆疊,這是一個非常基本但高效的實現方式。總的來說,快速排序是一種高效的排序算法,平均時間複雜度為O(n log n)。
改進方法
將節點添加到左邊或右邊時,需要確定左右子鏈表的尾節點。
程式碼
使用 Linux 核心風格的 List API 改寫快速排序
隨機選擇 pivot。
使用三值中值分割法(median-of-three partitioning),即在每次分割前,從子序列的頭、尾和中間選擇三個元素,並選擇其中值作為 pivot,以確保 pivot 能夠接近序列的中間值,從而更好地分割序列。
程式碼
測驗二
運作原理
改進方法
第二週測驗題
測驗一
原理解釋
struct hlist_node:這是一個雜湊表節點結構,它包含指向下一個節點的指標 next,以及指向前一個節點的指標 pprev。
struct hlist_head:這是一個雜湊表頭結構,它包含指向第一個節點的指標 first。
INIT_HLIST_HEAD 函數用於初始化雜湊表頭結構,將 first 指標設置為 NULL,表示鏈表為空。
hlist_add_head 函數用於將節點添加到雜湊表中。它將新節點插入到雜湊表的頭部,即 first 指標指向新節點,並更新新節點的 next 和 pprev 指標,以及原來頭部節點的 pprev 指標。
find 函數用於在給定雜湊表中查找特定值的節點索引。它使用雜湊函數計算值的雜湊值,並遍歷對應雜湊槽中的節點,直到找到值相等的節點,然後返回該節點的索引。如果未找到,則返回 -1。
dfs 函數是一個遞歸函數,用於根據前序遍歷和中序遍歷序列建立二元樹。它首先創建當前根節點,然後根據根節點在中序遍歷序列中的位置,遞歸建立左子樹和右子樹,並將其連接到根節點上。
node_add 函數用於創建並添加一個新節點到雜湊表中。它首先分配內存以存儲新節點,然後計算節點值的雜湊值,並將新節點添加到雜湊表的對應槽中。
buildTree 函數是入口函數,用於初始化雜湊表並調用深度優先搜索函數 dfs 來建立二元樹。
實作測試程式
使用指標而不是索引:在哈希表中查找值時,可以將哈希表的頭指標直接傳遞給 find 函數,而不是傳遞整個哈希表。這樣可以減少參數傳遞的開銷。
釋放節點內存:在構建樹的過程中,應該在適當的時候釋放節點的內存,避免內存泄漏。
錯誤處理:在節點添加到哈希表時,應該檢查內存分配是否成功,並在失敗時進行適當的錯誤處理。
簡化節點結構:可以簡化 order_node 結構,不需要存儲值的索引,因為在構建樹的過程中索引已經不再需要
程式碼
#include <stdio.h>
#include <stdlib.h>
#define container_of(ptr, type, member) \
((type *) ((char *) (ptr) - (size_t) & (((type *) 0)->member)))
#define list_entry(ptr, type, member) container_of(ptr, type, member)
#define hlist_for_each(pos, head) \
for (pos = (head)->first; pos; pos = pos->next)
struct hlist_node {
struct hlist_node *next, **pprev;
};
struct hlist_head {
struct hlist_node *first;
};
static inline void INIT_HLIST_HEAD(struct hlist_head *h)
{
h->first = NULL;
}
static inline void hlist_add_head(struct hlist_node *n, struct hlist_head *h)
{
if (h->first)
h->first->pprev = &n->next;
n->next = h->first;
n->pprev = &h->first;
h->first = n;
}
struct TreeNode {
int val;
struct TreeNode *left, *right;
};
struct order_node {
struct hlist_node node;
int val;
};
static struct order_node *find(int num, struct hlist_head *heads, int size)
{
struct hlist_node *p;
int hash = (num < 0 ? -num : num) % size;
hlist_for_each(p, &heads[hash]) {
struct order_node *on = container_of(p, struct order_node, node);
if (num == on->val)
return on;
}
return NULL;
}
static struct TreeNode *dfs(int *preorder,
int pre_low,
int pre_high,
int *inorder,
int in_low,
int in_high,
struct hlist_head *in_heads,
int size)
{
if (in_low > in_high || pre_low > pre_high)
return NULL;
struct TreeNode *tn = malloc(sizeof(*tn));
tn->val = preorder[pre_low];
struct order_node *on = find(preorder[pre_low], in_heads, size);
int idx = on->val;
tn->left = dfs(preorder, pre_low + 1, pre_low + (idx - in_low), inorder,
in_low, idx - 1, in_heads, size);
tn->right = dfs(preorder, pre_high - (in_high - idx - 1), pre_high, inorder,
idx + 1, in_high, in_heads, size);
return tn;
}
static inline void node_add(int val,
struct hlist_head *heads,
int size)
{
struct order_node *on = malloc(sizeof(*on));
if (!on) {
perror("Memory allocation failed");
exit(EXIT_FAILURE);
}
on->val = val;
int hash = (val < 0 ? -val : val) % size;
hlist_add_head(&on->node, &heads[hash]);
}
static struct TreeNode *buildTree(int *preorder,
int preorderSize,
int *inorder,
int inorderSize)
{
struct hlist_head *in_heads = malloc(inorderSize * sizeof(*in_heads));
if (!in_heads) {
perror("Memory allocation failed");
exit(EXIT_FAILURE);
}
for (int i = 0; i < inorderSize; i++)
INIT_HLIST_HEAD(&in_heads[i]);
for (int i = 0; i < inorderSize; i++)
node_add(inorder[i], in_heads, inorderSize);
struct TreeNode *root = dfs(preorder, 0, preorderSize - 1, inorder, 0,
inorderSize - 1, in_heads, inorderSize);
free(in_heads);
return root;
}
void inorderTraversal(struct TreeNode *root)
{
if (root == NULL)
return;
inorderTraversal(root->left);
printf("%d ", root->val);
inorderTraversal(root->right);
}
int main()
{
int preorder[] = {3, 9, 20, 15, 7};
int inorder[] = {9, 3, 15, 20, 7};
int preorderSize = sizeof(preorder) / sizeof(preorder[0]);
int inorderSize = sizeof(inorder) / sizeof(inorder[0]);
struct TreeNode *root = buildTree(preorder, preorderSize, inorder,
inorderSize);
printf("Inorder Traversal: ");
inorderTraversal(root);
printf("\n");
return 0;
}
測驗二
原理解釋
結構定義:
struct hlist_node 和 struct hlist_head:這些結構定義了雜湊表的節點和表頭。
struct list_head:這是一個雙向鏈表的結構。
初始化函數:
INIT_HLIST_HEAD 和 INIT_LIST_HEAD 函數用於初始化雜湊表和鏈表的表頭。
添加函數:
hlist_add_head 函數用於將節點添加到雜湊表中。
list_add 函數用於將節點添加到鏈表中。
刪除函數:
hlist_del 函數用於從雜湊表中刪除節點。
list_del 函數用於從鏈表中刪除節點。
獲取函數:
lRUCacheGet 函數用於從快取中獲取特定鍵的值。它首先計算鍵的雜湊值,然後遍歷對應的雜湊槽,找到對應的節點,並將其移動到鏈表頭部,以表示最近使用。如果找到鍵,則返回其值,否則返回 -1。
放置函數:
lRUCachePut 函數用於將鍵值對放入快取中。它首先計算鍵的雜湊值,然後遍歷對應的雜湊槽,查找是否存在相同的鍵。如果存在,則將其移動到鏈表頭部;否則,如果快取已滿,則淘汰最近最少使用的項目,並添加新的節點;如果快取未滿,則直接添加新的節點。最後,更新鍵對應的值。
釋放函數:
lRUCacheFree 函數用於釋放整個快取的內存。它遍歷快取中的每個節點,從鏈表中刪除節點並釋放內存。
這樣,通過使用雜湊表來實現快取的查找功能,並使用雙向鏈表來實現最近最少使用的淘汰策略,從而實現了 LRU 快取的功能。
改進方法並實作
釋放函數的改進:在 lRUCacheFree 函數中,釋放每個節點的內存後,將應該將節點從雜湊表中刪除。否則,快取的雜湊表可能會保留對已經釋放的節點的引用,這可能導致未定義的行為或記憶體泄漏。
更好的錯誤處理:在 lRUCacheCreate 函數中,應該檢查是否成功分配了快取結構的內存。如果分配失敗,應該進行適當的錯誤處理。
程式碼
測驗三
原理解釋
find_nth_bit 函數是這段程式碼的主體。該函數接受三個參數:記憶體空間的起始地址 addr、記憶體空間的大小 size 和要找到的第 N 個設定的位元位置 n。
如果 n 大於或等於 size,則表示要找的位元超出了記憶體空間的範圍,因此直接返回 size。
如果 size 是一個小於等於 BITS_PER_LONG 的常數,則使用位元掩碼 GENMASK 取出相關的位元組,然後調用 fns 函數在這些位元組中尋找第 N 個設定的位元位置。
如果 size 大於 BITS_PER_LONG,則使用迴圈遍歷記憶體空間中的每個字,計算該字中設定的位元數量,並與要找的位元位置 n 進行比較,以確定要找的位元位於哪個字中。接著,使用 fns 函數在該字中尋找第 N 個設定的位元位置。
最終,返回第 N 個設定的位元位置,如果找不到,則返回 size。
內部輔助函數 __ffs 和 __clear_bit:
__ffs 函數用於在一個字中尋找第一個設定的位元位置,即從右邊數起第一個為 1 的位元的位置。
__clear_bit 函數用於清除指定位元的值,將其設置為 0。