contributed by < chiacyu
>
$ gcc --version
gcc (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
Address sizes: 39 bits physical, 48 bits virtual
CPU(s): 8
On-line CPU(s) list: 0-7
Thread(s) per core: 2
Core(s) per socket: 4
Socket(s): 1
NUMA node(s): 1
Vendor ID: GenuineIntel
CPU family: 6
Model: 60
Model name: Intel(R) Core(TM) i7-4720HQ CPU @ 2.60GHz
Stepping: 3
CPU MHz: 1700.000
CPU max MHz: 3600.0000
CPU min MHz: 800.0000
BogoMIPS: 5187.53
Virtualization: VT-x
L1d cache: 128 KiB
L1i cache: 128 KiB
L2 cache: 1 MiB
L3 cache: 6 MiB
NUMA node0 CPU(s): 0-7
做作業前可以先研讀 你所不知道的 C 語言: linked list 和非連續記憶體對於實作過程大有幫助。
這邊需要注意的部份在根據 malloc
的說明:
On error, these functions return NULL.
若是失敗時會回傳 NULL
因此就算失敗也必須要使用 free
來釋放記憶體。
struct list_head *q_new()
{
struct list_head *q_head = malloc(sizeof(struct list_head));
if (!q_head) {
free(q_head);
return NULL;
}
INIT_LIST_HEAD(q_head);
return q_head;
}
釋放記憶體的部份則是透過 list_for_entry_safe
依序走訪過每一個 element
來釋放每個節點所佔用的記憶體。
void q_free(struct list_head *l)
{
if (!l) {
return;
}
if (list_empty(l)) {
free(l);
}
element_t *entry, *safe;
list_for_each_entry_safe (entry, safe, l, list) {
if (entry->value) {
free(entry->value);
}
free(entry);
}
free(l);
return;
}
bool q_insert_head(struct list_head *head, char *s)
{
if (!head) {
return false;
}
element_t *q = malloc(sizeof(element_t));
if (!q) {
free(q);
return false;
}
q->value = strdup(s);
list_add(&q->list, head);
return true;
}
bool q_insert_tail(struct list_head *head, char *s)
{
if (!head) {
return false;
}
element_t *q = malloc(sizeof(element_t));
if (!q) {
free(q);
return false;
}
q->value = strdup(s);
list_add_tail(&q->list, head);
return true;
}
element_t *q_remove_head(struct list_head *head, char *sp, size_t bufsize)
{
if (!head || list_empty(head)) {
return NULL;
}
element_t *q_head = list_first_entry(head, element_t, list);
list_del(&q_head->list);
if (!q_head) {
free(q_head);
return NULL;
}
strncpy(sp, q_head->value, bufsize);
return q_head;
}
element_t *q_remove_tail(struct list_head *head, char *sp, size_t bufsize)
{
element_t *q_tail;
q_tail = list_last_entry(head, element_t, list);
list_del(&q_tail->list);
if (!q_tail){
free(q_tail);
return NULL;
}
strncpy(sp, q_tail->value, bufsize);
return q_tail;
}
思考如何縮減程式碼。
int q_size(struct list_head *head)
{
if (!head) {
return 0;
}
int len = 0;
struct list_head *li;
list_for_each (li, head) {
len++;
}
return len;
}
這邊用的方式是先計算出 queue
的長度,再取下高斯的方式來找出 mid
的位置
改進漢語描述,並描述對應的演算法。
bool q_delete_mid(struct list_head *head)
{
if (!head){
return false;
}
int q_num = q_size(head);
q_num = q_num/2;
struct list_head *mid = head;
while (q_num--) {
mid = mid->next;
}
element_t *mid_node = list_entry(mid, element_t, list);
list_del(&mid_node->list);
// https://leetcode.com/problems/delete-the-middle-node-of-a-linked-list/
return true;
}
delete
的方式用兩個指標 先去若是遇到重複的字串 將第二個指標持續往 next
前進直到不同 再第一個指標往 next
走得過程刪除重複的節點
改進漢語表達。
bool q_delete_dup(struct list_head *head)
{
if (head == NULL) {
return false;
}
struct list_head *curr = head->next, *nextt;
bool dup_flag = false;
while (curr && curr->next) {
if (curr == head || curr->next == head)
break;
nextt = curr->next;
element_t *node_f = list_entry(curr, element_t, list);
element_t *node_s = list_entry(nextt, element_t, list);
while (strcmp(node_f->value, node_s->value) == 0) {
nextt = nextt->next;
if (nextt == head) {
break;
}
node_s = list_entry(nextt, element_t, list);
dup_flag = true;
}
if (dup_flag) {
while (curr != nextt) {
struct list_head *delete_node = curr;
curr = curr->next;
list_del(delete_node);
free(list_entry(delete_node, element_t, list));
}
dup_flag = false;
}
curr = curr->next;
}
// https://leetcode.com/problems/remove-duplicates-from-sorted-list-ii/
return true;
}
swap
的實作方式則是用兩個指標分別指到,欲插入頭部的位置與欲移動節點之位置。由於後一個節點的 next
會指向 head
因此可以作為判斷之條件來檢視是否走遍所有節點。
void q_swap(struct list_head *head)
{
if (!head)
return;
struct list_head *node = head->next;
while (node && node->next) {
struct list_head *next_n;
if (node == head || node->next == head)
break;
next_n = node->next;
list_del(node);
list_add(node, next_n);
node = node->next;
}
return;
// https://leetcode.com/problems/swap-nodes-in-pairs/
}
void q_reverse(struct list_head *head)
{
if (list_empty(head)) {
return;
}
struct list_head *node, *safe;
list_for_each_safe (node, safe, head) {
list_move(node, head);
}
return;
}
目前的實作方式是用兩個指標,第一個指標放在 前頭
,第二個指標指向欲移動的點。但目前這種方式當遇到 queue
長度能被 k
整除時會有問題,需要再改進。
void q_reverseK(struct list_head *head, int k)
{
// https://leetcode.com/problems/reverse-nodes-in-k-group/
if (list_empty(head) || k < 2) {
return;
}
int reverse_count = q_size(head)/ k;
int interval = k - 1;
struct list_head *start = head->next, *end = head->next;
while (reverse_count--) {
while (interval--) {
end = end->next;
}
while (end != start) {
struct list_head *move_node = end;
end = end->prev;
list_del(move_node);
list_add_tail(move_node, start);
}
start = start->next;
end = start;
if(start == head || start->next== head) break;
}
return;
}
目前是依照 你所不知道的 C 語言: linked list 和非連續記憶體 內文提供的遞迴版本去實作。 唯一的差別是在與這邊的實作是取 first entry
作為 pivot
,範例的版本則是取最後一個 entry
作為 pivot
。 原本在宣告 large_head
跟 small_head
的時候是使用 malloc
但發現在執行時期會發生 FATAL ERROR: Calls to malloc disallowed
後來才改為不需要 malloc
的版本。
void q_sort(struct list_head *head) {
if (list_empty(head) || list_is_singular(head)){
return;
}
struct list_head large_head;
struct list_head small_head;
element_t *pivot_node;
element_t *current_node;
element_t *safe;
INIT_LIST_HEAD(&large_head);
INIT_LIST_HEAD(&small_head);
pivot_node = list_last_entry(head, element_t, list);
list_del(&pivot_node->list);
list_for_each_entry_safe(current_node, safe, head, list){
if(strcmp(current_node->value, pivot_node->value) < 0){
list_move_tail(¤t_node->list, &small_head);
}
else if(strcmp(current_node->value, pivot_node->value) >= 0){
list_move_tail(¤t_node->list, &large_head);
}
}
q_sort(&large_head);
q_sort(&small_head);
list_add(&pivot_node->list, head);
list_splice(&small_head, head);
list_splice_tail(&large_head, head);
}
int q_descend(struct list_head *head)
{
// https://leetcode.com/problems/remove-nodes-from-linked-list/
if (!head || list_empty(head)) {
return 0;
}
struct list_head *curr = head->prev;
struct list_head *safe = head->prev;
char *max_value = '\0';
int count_size = 0;
while (curr != head) {
curr = safe;
safe = curr->prev;
count_size+=1;
element_t *entry = list_entry(curr, element_t, list);
if (strcmp(entry->value, max_value) > 0) {
max_value = strdup(entry->value);
} else {
list_del(curr);
free(entry);
count_size+=1;
}
}
return count_size;
}
這邊的作法是透過,list_for_each_entry_safe
的方式將每一個節點走遍。 假設總共有三個 queue
[A,B,C]
[D,E,F]
[G,H,I]
, 先準備一個 sort_head
並將所有節點移動至此成 [A,B,C,D,E,F,G,H,I]
此時三個佇列變成 []
[]
[]
,在將 sort_head
排序後串回第一個 queue
, 變成 [A,B,C,D,E,F,G,H,I]
[]
[]
。
int q_merge(struct list_head *head)
{
// https://leetcode.com/problems/merge-k-sorted-lists/
if (list_empty(head)) {
return 0;
}
int count = 0;
struct list_head sort_head;
INIT_LIST_HEAD(&sort_head);
queue_contex_t *itr;
queue_contex_t *itr_safe;
list_for_each_entry_safe (itr, itr_safe, head, chain) {
count += itr->size;
list_splice_init(itr->q, &sort_head);
}
q_sort(&sort_head);
list_splice_init(&sort_head, list_first_entry(head, queue_contex_t, chain)->q);
printf("The count is %d\n", count);
return count;
}