contributed by < Brianpan
>
作業書寫規範:
[TOC]
: 筆記左上方已有 Table of Contents (TOC) 功能,不需要畫蛇添足c=
或 cpp=
變更為 c
或 cpp
。行號只在後續討論明確需要行號時,才要出現,否則維持精簡的展現。可留意「你所不知道的 C 語言: linked list 和非連續記憶體」裡頭程式碼展現的方式diff
標示),可附上對應 GitHub commit 的超連結,列出程式碼是為了「檢討」和「便於他人參與討論」:::info
, :::success
, :::warning
等標示,儘量用清晰的文字書寫。:::danger
則僅限授課教師作為批注使用〈
和 〉
,非「小於」和「大於」符號Repo: Link
題目要寫一個list_insert_before函式, p是指到next指標的地址,所以終止條件是
*p 代表指到的那個元素
static inline void list_insert_before(list_t *l,
list_item_t *before,
list_item_t *item)
{
list_item_t **p;
for (p = &l->head; *p != before || *p != NULL; p = &(*p)->next)
;
*p = item;
item->next = before;
}
延伸問題: 撰寫合併排序
commit
演算法主要是在做移除符合適當大小的節點,這個設計沒有考慮樹的再平衡
移除的情況有三種
情況2,3比較直觀
情況2直接把那個子節點拉上來
block_t *child = ((*node_ptr)->l) ? (*node_ptr)->l : (*node_ptr)->r;
*node_ptr = child;
情況3就直接把目標節點從樹中移除
*node_ptr = NULL
情況1的狀況是要思考這個刪除的節點要怎麼取代
有兩種方法
接下來找最右節點又有兩種情況
block_t *old_right = (*node_ptr)->r;
*node_ptr = *pred_ptr; /
情況2.以這個子樹為例我們要刪除9, 我們找到最大右節點5
做以下步驟
實作中透過指標的指標去間接更新刪除節點的母節點指標
找左子樹最右邊的節點
/*
* Structure representing a free memory block in the memory allocator.
* The free tree is a binary search tree that organizes free blocks (of type block_t)
* to efficiently locate a block of appropriate size during memory allocation.
*/
void remove_free_tree(block_t **root, block_t *target)
{
/* Locate the pointer to the target node in the tree. */
block_t **node_ptr = find_free_tree(root, target);
/* If the target node has two children, we need to find a replacement. */
if ((*node_ptr)->l && (*node_ptr)->r) {
/* Find the in-order predecessor:
* This is the rightmost node in the left subtree.
*/
block_t **pred_ptr = &(*node_ptr)->l;
while (*pred_ptr && (*pred_ptr)->right)
pred_ptr = &(*pred_ptr)->r;
/* Verify the found predecessor using a helper function (for debugging).
*/
block_t *expected_pred = find_predecessor_free_tree(root, *node_ptr);
assert(expected_pred == *pred_ptr);
/* If the predecessor is the immediate left child. */
if (*pred_ptr == (*node_ptr)->l) {
block_t *old_right = (*node_ptr)->r;
*node_ptr = *pred_ptr; /* Replace target with its left child. */
(*node_ptr)->r = old_right; /* Attach the original right subtree. */
assert(*node_ptr != (*node_ptr)->l);
assert(*node_ptr != (*node_ptr)->r);
} else {
/* The predecessor is deeper in the left subtree. */
block_t *old_left = (*node_ptr)->l;
block_t *old_right = (*node_ptr)->r;
block_t *pred_node = *pred_ptr;
/* Remove the predecessor from its original location. */
remove_free_tree(&old_left, *pred_ptr);
/* Replace the target node with the predecessor. */
*node_ptr = pred_node;
(*node_ptr)->l = old_left;
(*node_ptr)->r = old_right;
assert(*node_ptr != (*node_ptr)->l);
assert(*node_ptr != (*node_ptr)->r);
}
}
/* If the target node has one child (or none), simply splice it out. */
else if ((*node_ptr)->l || (*node_ptr)->r) {
block_t *child = ((*node_ptr)->l) ? (*node_ptr)->l : (*node_ptr)->r;
*node_ptr = child;
} else {
/* No children: remove the node. */
*node_ptr = NULL;
}
/* Clear the removed node's child pointers to avoid dangling references. */
target->l = NULL;
target->r = NULL;
}
https://github.com/Brianpan/kernel-hw2/blob/master/q2.c
TBD
想像一個虛擬的堆疊用迴圈迭代確認是否為空
這個例子是用begin,end陣列指到的值
初始狀態是
begin[0] = *list;
end[0] = list_tail(list);
每一輪結束後的更新狀態是
begin[i] = left;
end[i] = list_tail(&left);
begin[i + 1] = pivot;
end[i + 1] = pivot;
begin[i + 2] = right;
end[i + 2] = list_tail(&right);
收斂是當L==R, 像是pivot那一點就是確認已排序
每個回合結束後會i+2,換句話說我們先排右邊的序列
初始狀態
把pivot節點從串列移除
p處理完節點1
p處理完節點0
p處理完所有節點
更新begin[i], end[i]指標
意義就是下一輪的左右邊界
void quick_sort(node_t **list)
{
int n = list_length(list);
int value;
int i = 0;
int max_level = 2 * n;
node_t *begin[max_level], *end[max_level];
node_t *result = NULL, *left = NULL, *right = NULL;
begin[0] = *list;
end[0] = list_tail(list);
while (i >= 0) {
node_t *L = begin[i], *R = end[i];
if (L != R) {
node_t *pivot = L;
value = pivot->value;
node_t *p = pivot->next;
while (p) {
node_t *n = p;
p = p->next;
list_add(n->value > value ? &right : &left, n);
}
begin[i] = left;
end[i] = list_tail(&left);
begin[i + 1] = pivot;
end[i + 1] = pivot;
begin[i + 2] = right;
end[i + 2] = list_tail(&right);
left = right = NULL;
i += 2;
} else {
if (L)
list_add(&result, L);
i--;
}
}
*list = result;
}
連結
此版本和單向串列的主要差別
static void list_quicksort(struct list_head *head)
{
struct list_head list_less, list_greater;
struct listitem *pivot;
struct listitem *item = NULL, *is = NULL;
if (list_empty(head) || list_is_singular(head))
return;
INIT_LIST_HEAD(&list_less);
INIT_LIST_HEAD(&list_greater);
// get the pivot and remove it from the circular list
pivot = list_first_entry(head, struct listitem, list);
list_del(&pivot->list);
// move item to either list_less or list_greater list
list_for_each_entry_safe (item, is, head, list) {
if (cmpint(&item->i, &pivot->i) < 0)
list_move_tail(&item->list, &list_less);
else
list_move_tail(&item->list, &list_greater);
}
// divide part
// recusively sort left, right sublist
list_quicksort(&list_less);
list_quicksort(&list_greater);
// merge phase
list_add(&pivot->list, head);
// list_less to the beginning of the list_head
list_splice(&list_less, head);
// list_greater to the end of the list_head
list_splice_tail(&list_greater, head);
}
更改原本的實作,使用比較前後元素確認排序是否成功
i = 0;
list_for_each_entry_safe (item, is, &testlist, list) {
if (is)
assert(cmpint(&item->i, &is->i) < 0);
list_del(&item->list);
free(item);
i++;
}
stable sorting
假設排序中有兩個元素在比較下是相同的 cmp(a, b) == 0, stable sorting會保證排序後相同排序大小的元素有按照原本的出現順序
回到原本的問題為什麼無法滿足stable sorting, 那是因為使用list_move就讓原本的順序相反,因為是往前插入
commit
使用quicksort實作有兩個perf test沒有跑過
TBD
從長度32開始每次遞迴都把長度減半
終止條件:剩下兩位元時查表
magic number代表在2bit時候的快速查表
// divide and conquer width
static const int mask[] = {0, 8, 12, 14};
// magic numbers for 2 bits
// 0 -> 2
// 1 -> 1
// 2 -> 0
// 3 -> 0
static const int magic[] = {2, 1, 0, 0};
unsigned clz2(uint32_t x, int c)
{
if (!x && !c)
return 32;
uint32_t upper = (x >> (16 >> c));
uint32_t lower = (x & (0xFFFF >> mask[c]));
// 2 bits left which implies c == 3
// c = 0 => 16 bits
// c = 1 => 8 bits
// c = 2 => 4 bits
// c = 3 => 2 bits
if (c == 3)
return upper ? magic[upper] : 2 + magic[lower];
return upper ? clz2(upper, c + 1) : (16 >> (c)) + clz2(lower, c + 1);
}
64位元是32位元延伸, 先判斷前32位元clz32是否是0, 是則判斷下32位元的clz32值
static inline int clz64(uint64_t x)
{
return clz32(x >> 32) ? clz32(x >> 32) :
clz32(x & 0xFFFFFFFF) + 32;
}
uint64_t sqrti(uint64_t x)
{
uint64_t m, y = 0;
if (x <= 1)
return x;
int total_bits = 64;
/* clz64(x) returns the count of leading zeros in x.
* (total_bits - 1 - clz64(x)) gives the index of the highest set bit.
* Rounding that index down to an even number ensures our starting m is a
* power of 4.
*/
int shift = (total_bits - 1 - clz64(x)) & 0xFFFFFFFE;
m = 1ULL << shift;
while(m) {
uint64_t b = y + m;
y >>= 1;
if (x >= b) {
x -= b;
y += m;
}
m >>= 2;
}
return y;
}
TBD
理解中, 要用自己的話說一次
reference
這裡是使用linked list方式實作map,這個方式好處是不用碰撞後還要挪元素
(probing)
linux 用於hash table實作的資料結構include/linux/types.h
struct hlist_head {
struct hlist_node *first;
}
struct hlist_node {
struct hlist_node *next, **pprev;
}
資料結構和巨集
#define MAP_HASH_SIZE(bits) (1 << (bits))
#define GOLDEN_RATION_32 0x61C88647
// definition of the map structs
typedef struct {
int bits;
struct hlist_head *ht;
} map_t;
struct hash_key {
int key;
void *data;
struct hlist_node node;
};
初始化map
map_t *map_init(int bits)
{
map_t *map = malloc(sizeof(map_t));
if (!map)
return NULL;
map->bits = bits;
map->ht = malloc(sizeof(struct hlist_head) * MAP_HASH_SIZE(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;
}
湊雜函式
static inline unsigned int hash(unsigned int val, unsigned int bits)
{
return (val * GOLDEN_RATION_32) >> (32 - bits);
}
取得函式
這裡的處理比較直觀
static struct hash_key *find_key(map_t *map, int key)
{
struct hlist_head *head = &(map->ht)[hash(key, map->bits)];
// find exact mach of the key from the hash table's hlist
for (struct hlist_node *p = head->first; p; p = p->next) {
struct hash_key *kn = container_of(p, struct hash_key, node);
if (kn->key == key)
return kn;
}
return NULL;
}
void *map_get(map_t *map, int key)
{
struct hash_key *kn = find_key(map, key);
return kn ? kn->data : NULL;
}
新增函式
比較值得注意的是從頭插入
需要更新**pprev, *next指標
void map_add(map_t *map, int key, void *data)
{
struct hash_key *kn = find_key(map, key);
// key exists
if (kn)
return;
kn = malloc(sizeof(struct hash_key));
if (!kn)
return;
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;
// renew pprev of old first node
if (first)
first->pprev = &n->next;
h->first = n;
n->pprev = &h->first;
}
刪除map
每個map串列逐個刪除
這邊pprev的更新是把它更新成&head, 下一輪再做刪除
void map_deinit(map_t *map)
{
if (!map)
return;
for (int i = 0; i < MAP_HASH_SIZE(map->bits); i++) {
struct hlist_head *head = &map->ht[i];
for (struct hlist_node *p = head->first; p;) {
struct hash_key *kn = container_of(p, struct hash_key, node);
struct hlist_node *n = p;
p = p->next;
if (!n->pprev) /* unhashed */
goto bail;
// next's pprev to cur's pperv
struct hlist_node *next = n->next, **pprev = n->pprev;
*pprev = next;
if (next)
next->pprev = pprev;
n->next = NULL, n->pprev = NULL;
bail:
free(kn->data);
free(kn);
}
}
free(map);
}
or
By clicking below, you agree to our terms of service.
New to HackMD? Sign up