2024q1 Homework2 (quiz1+2)
contributed by < LULser0204 >
參考 Optimized QuickSort — C Implementation (Non-Recursive),實作非遞迴 (non-recursive; iterative) 的快速排序法。作者是用了一個明確的堆疊( beg[] 和 end[] )取代了遞迴呼叫使用,減少了函式調用的開銷。
除此之外,作者在每一輪中只通過 swap
交換一次軸心點,避免了重複的移動開銷,減少了元素與自身交換的情況。
自定義一個鍊結串列結構體:
在 node_t
結構中,他有一個指標 next
指向下一個節點,而 left
和 right
並沒有在 quicksort
中特別著墨,猜測此為單向鍊結串列。
在這個函式的迴圈中,有幾個重要的步驟:
1.選擇軸心點 (pivot) 並將其從鍊表中分離。
2.遍歷練表,根據截點值與軸心點值得比較結果,將截點添加到 left
或 right
鍊表中
3.更新 begin
、 end
陣列以反映新的子鍊表界線。
4.重設 left
和 right
為 NULL
並增加i
以處理下一層
過程如下圖:
我認為可行的改進之處:
1.由於 quick sort
的效率很大程度依賴於軸心點的選擇。使用第一個作為軸心點在 worst case
下會導致很差的性能。
可以考慮使用"三數取中"策略,即從鍊表的(首部、中部和尾部)選擇三個元素,然後將這三個元素的中間值作為軸心點
Tim Peter 他結合合併排序和插入排序的特點,提出 Timsort。在現實世界中,許多資料往往是由一些已經部分排序的序列組合而成,於是他的策略是首先識別出資料中已排序的子序列 (這些子序列稱為 run),然後透過不斷合併這些 run 來達成全資料範圍的排序。
雙向鏈結串列
鏈結串列元素
程式碼
AAAA
應該要填 &head
。因為要初始化 tail
指標的位址,使其指向鍊結串列的 head
位址。這樣 *tail
就可以被賦值為第一個節點,即合併後鍊節串節的頭節點。BBBB
和 CCCC
應該要填 &(a->next)
和 &(b->next)
從上面可以得知 *tail
要放入的是下一個節點,故要更新 tail
值使其指向下一個節點的 next
。這麼做是為了在下一次跌代中,將下一個節點插入鍊結串列。DDDD
和 EEEE
應該要填 head->prev
和 tail->next
完成雙向鍊結串列的循環。FFFF
應該填 1
,因為如果大於1代表鍊結串列還可以被合併run_size
:根據鍊結串列的頭節點計算當前運行的大小。
merge
:合併兩個已排序的鍊結串列。
build_prev_link
:重新連接鍊結串列的 prev
指針。
find_run
:在鍊結串列中找到一個遞增或是遞減的序列,並且翻轉遞減的序列使其成為遞增序列。
merge_at
、 merge_force_collapse
、 merge_collapse
:這些函式負責串列的合併操作。
merge_final
:所有的 runs 都通過上面的 merge 函式合併後,負責將最後兩個 runs 合併成一個完全有序的串列,以及重建雙向鍊結。
而主函式 timsort:
1.初始化和準備:將雙向鍊結串列轉換為以 NULL
為結尾的單向鍊結串列。
2.尋找並合併 runs
:通過 find_run
尋找 runs
,然後利用 merge_collapse
或 merge_force_collapse
根據特定規則來合併runs。後者是在排序過程接近完成時使用。
3.最終合併:當輸入的鍊結串列完全轉化為 runs 後,使用 merge_force_collapse
強制合併剩餘的所有 runs ,最後通過 merge_final
或 build_prev_link
重建整個串列的雙向鍊結。
待補…
LeetCode 105 Construct Binary Tree from Preorder and Inorder Traversal 題意:
給定由二個由整數構成陣列的前序 (preorder) 和中序 (inorder) 形式,分別是對同一棵二元樹的前序及中序走訪結果,藉此重建此二元樹。
先觀察hash table 實作中,用以處理 hash 數值碰撞的 hlist_node,注意他的 pprev 是 pointer to pointer
示意圖
AAAA
應該填 h->first
。因為要將新節點 n 插入 hash table 的頭部, n->next 應該指向原鍊結串列的第一個節點,即目前的 h->firstBBBB
應該填 &heads[hash]
。使用 hlist_for_each
遍歷特定的 hlist 。而 &heads[hash]
是該 hlist 的頭。CCCC
應該填 list_entry
。從括號內的參數剛好對應 list_entry 的 (ptr, type, member) 推測。DDDD
應該填 &heads[hash]
。在 node_add 函式中,其功能為將一個新的節點加入到 hash table 。 DDDD 應該指向我們想要增加新節點的鍊節串列的頭。num
和 hash table 大小 size
,在 hash table 中尋找對應的 order_node
,返回找到的節點其索引值。preorder
結果找到當節的根節點,然後在 inorder
結果中找到該節點,從而分隔出左右子樹,然後遞迴構建左右子樹。in_heads
紀錄中序遍歷中每個元素的 hlist 頭部。接下來,在迴圈內使用 node_add
進行中序遍歷,並且將其中的每個元素和對應的索引加到 hash table 。最後通過使用 dfs 函式構建二元樹,並返回構建好的二元樹根節點待補…
題目說明:有一個 LRU Cache 其有一定的容量 (capcity) ,然後使用 put 放入 Cache 裡面,如果放入時 Cache 已滿,則把 LRU (Least Recently Used) key 拋棄,並把新的 key 放入 Cache ;而 get 則是看當下 Cache 裡面是否有對應的 key ,有則返回其值,並將對應的 key 更新 ,沒有則返回 -1 。
結構:
LRUCache :LRU 主要結構,包含容量 (capcity) 、當前數據量 (count) 、雙向鏈結的頭用於實現 LRU 功能 (dhead) 、hash table 陣列 (hhead) 用於快速查詢
LRUNode :存放在 LRUCache 中的節點結構,包含 key、value、hash table 節點、雙向鏈結串列節點 link
主要函式:
lRUCacheCreate :創建一件新的 LRU Cache 物件,初始化其 capcity、count、dhead、hhead
lRUCacheFree :釋放LRU Cache 中所占用的記憶體空間,包括所有 LRUNode 節點
lRUCacheGet: 從 Cache 中獲取一個元素的值。如果找到,將其移至雙向鏈結串列的頭,代表最近被使用。
lRUCachePut: 往 Cache 中放入一個新元素或更新現有元素的值。如果 Cache 已滿,則先移除 LRU 元素,然後在鏈結串列頭部放入新元素
EEEE :在 hlist_del 函式中,應該填入" next->pprev "。舉例:假設現在有 A->B->C 的串列,現在要刪除節點 B。
刪除前:
待補…
在指定的記憶體空間找出第 N 個設定的位元(即 1
主要函式:
FIND_NTH_BIT :找到第 n 個 bit的位置。
_ffs : Find First Set 找到第一個 bit 位置。返回給定的無號整數最小有效位置的 index 。
__clear_bit :清除再給定位址中特定位。
fns :在一個常整數中找到第n個設置位的位置。通常迴圈使用 __ffs 來找到並清除最低設置位,直到找到第 n 個為止。
AAAA : 要填 0xffffffff 。根據下面判斷式中( 0x1、0x3、0xf…)的規律推導出來的
BBBB : 要填 ~mask 。 __clear_bit 函式主要功能是對於特定 bit 進行清除。要清除一個位元需要通過 AND 操作。
CCCC : 要填 % 。用於尋找第 n 個 bit 為 1 的索引。
find_nth_bit 是整個核心。通過遍歷數組中每個字,使用 hweight_long 快速跳過完全為 0 的字,直到找到包含目標位置 bit 。然後再使用 fns 函式定位出確切的位置。
待補