contributed by < cwl0429
>
1
#define MAP_HASH_SIZE(bits) (1 << bits)
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(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;
}
記得將 map->ht 所有的 first 指標初始化,避免有存取安全疑慮
for (int i = 0; i < MAP_HASH_SIZE(map->bits); i++)
(map->ht)[i].first = NULL;
struct hash_key {
int key;
void *data;
struct hlist_node node;
};
#define container_of(ptr, type, member) \
({ \
void *__mptr = (void *) (ptr); \
((type *) (__mptr - offsetof(type, member))); \
})
#define GOLDEN_RATIO_32 0x61C88647
static inline unsigned int hash(unsigned int val, unsigned int bits) {
/* High bits are more random, so use them. */
return (val * GOLDEN_RATIO_32) >> (32 - bits);
}
為何使用 GOLDEN RATIO 作為 hash function?
根據 Fibonacci Hashing 的說明,在作者設計的實驗中,fibonacci hashing 較傳統的 integer modulo 快了兩倍左右(在資料量大於 400000 時),圖表如下:
先藉由 key 找到對應的 hlist_head,接著在此 head 不斷往後找,直到有 key 相等或找完所有 hlist
static struct hash_key *find_key(map_t *map, int key) {
struct hlist_head *head = &(map->ht)[hash(key, map->bits)];
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;
}
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; // AAA;
if (first)
first->pprev = &n->next;
h->first = n;
n->pprev = &h->first; // BBB;
原先的 code 缺少了 AAA 及 BBB 兩部分,以下是不加 AAA 及 BBB 的示意圖(圖表改自開發紀錄(quiz1),假設 hash key 為 n ):
可以看出,有兩條 link 沒有正確串接,分別是 n->next 和 n->pprev,於是將其補上,使其變成下面的圖表:
map->ht[0]
開始,先將此 slot 內的 hlist_node 逐一釋放,重複此動作,直到釋放完所有的 map->ht[MAP_HASH_SIZE]
需要注意的是,這段 code 使用到 goto 技巧,用以跳過 16~20 行,但我目前還沒想到何時會有 unhash 的狀況發生
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;
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);
}
想法是不斷地將 nums 的數放入 map 中,直到 map_get 找到正確的數進行回傳
int *twoSum(int *nums, int numsSize, int target, int *returnSize)
{
map_t *map = map_init(10);
*returnSize = 0;
int *ret = malloc(sizeof(int) * 2);
if (!ret)
goto bail;
for (int i = 0; i < numsSize; i++) {
int *p = map_get(map, target - nums[i]);
if (p) { /* found */
ret[0] = i, ret[1] = *p;
*returnSize = 2;
break;
}
p = malloc(sizeof(int));
*p = i;
map_add(map, nums[i], p);
}
bail:
map_deinit(map);
return ret;
}
研讀 Linux 核心原始程式碼 include/linux/hashtable.h 及對應的文件 How does the kernel implements Hashtables?,解釋 hash table 的設計和實作手法,並留意到 tools/include/linux/hash.h 的 GOLDEN_RATIO_PRIME,探討其實作考量
2
觀察 COND1
,此時需要進入 if 內做刪除重複 node 的動作,因此推斷 COND1
會需要 head->val == head->next->val
的條件,另外又需要考慮到若是 head->next
為 NULL 會產生問題,所以再將 head->next
加入判斷,於是得到結論
COND1:
head->next && head->val == head->next->val
再觀察 COND2
會發現需要的條件和 COND2
相同,所以
COND2:
head->next && head->val == head->next->val
#include <stddef.h>
struct ListNode {
int val;
struct ListNode *next;
};
struct ListNode *deleteDuplicates(struct ListNode *head)
{
if (!head)
return NULL;
if (COND1) {
/* Remove all duplicate numbers */
while (COND2)
head = head->next;
return deleteDuplicates(head->next);
}
head->next = deleteDuplicates(head->next);
return head;
}
利用指標的指標 **tmp
移除重複的 node,方便移除 node
struct ListNode* deleteDuplicates(struct ListNode* head) {
if(!head){
return NULL;
}
int curr = -111;
for(struct ListNode **tmp = &head; *tmp ;) {
if((*tmp)->next && (*tmp)->val == (*tmp)->next->val) {
*tmp = (*tmp)->next;
curr = (*tmp)->val;
}
if(curr == (*tmp)->val)
*tmp = (*tmp)->next;
else
tmp = &(*tmp)->next;
}
return head;
}