# 2020q3 Homework1 (quiz1) contributed by < `jeremy3951` > 加入日期:2020/9/15 :::success 延伸問題1: 解釋上述程式運作原理,搭配 Graphviz,比照 Linked List 練習題 在 HackMD 筆記上視覺化展現; ::: ###### tags: `(2020q3)進階電腦系統理論與實作` ## `add_entry`: 在`add_entry(&head,72)`這個指令中可以看到第一個引數是head的位址,相對的,在此function中第一個input就是要用**的形式來接值. - 新增一個indirect來紀錄head(拿來移動的) - 接下來三行所描述的就是新增一個node並將pointer指向NULL - AA1: `assert(new_node)` assert function用來確定此new node是否有成功建立. - while迴圈: `*indirect` : 代表head的位址 `*indirect->next` : head下一個點的記憶體位址 `indirect = &(*indirect)->next` : 把裝著上述(裝著head下一個點的記憶體位址)的記憶體位址取出,並寫回`indirect` ```graphviz digraph struct{ indirect[shape=plaintext,fontcolor=red] head[shape=plaintext,fontcolor=red] NULL[shape=plaintext,fontcolor=black] rankdir = "LR" subgraph struct1{ node1 [ shape = record label = " value|*next" ] } indirect->head->node1 node1->NULL } ``` ## `find_entry`: 根據指標指到的點比較value值,若比較到一樣的value則跳出for迴圈並return當下的點的位址 ## `remove_entry`: 跟add很像,但是要刪除節點前要先找到該節點! `*indirect = entry->next`這行把entry指向的點寫入`*indirect`中以代替它. ## `swap_pair`: :::warning 此function的功能是把linked list中的兩個點為一數作交換. ::: 我當時的疑問:BB1為何要走兩格? tmp是負責紀錄第一個點,node是負責紀錄第二個點,node交換完後會變第一個點,所以要走兩格到第三個點去做接下來的交換. ```graphviz digraph struct{ node_[shape=plaintext,fontcolor=red] tmp[shape=plaintext,fontcolor=red] NULL[shape=plaintext,fontcolor=black] rankdir = LR; { rank = "same"; node_->tmp->node1 } node1->node2->node3->NULL } ``` After `*node = (*node)->next` ```graphviz digraph struct{ node_[shape=plaintext,fontcolor=red] tmp[shape=plaintext,fontcolor=red] NULL[shape=plaintext,fontcolor=black] rankdir = LR; { rank = "same"; tmp->node1 } { rank = "same"; node_->node2 } node1->node2->node3->NULL } ``` After `tmp->next = (*node)->next;` ```graphviz digraph struct{ node_[shape=plaintext,fontcolor=red] tmp[shape=plaintext,fontcolor=red] NULL[shape=plaintext,fontcolor=black] rankdir = LR; { rank = "same"; tmp->node1 } { rank = "same"; node_->node2->node3 } node1->node3->NULL } ``` After `(*node)->next = tmp;` ```graphviz digraph struct{ node_[shape=plaintext,fontcolor=red] tmp[shape=plaintext,fontcolor=red] NULL[shape=plaintext,fontcolor=black] rankdir = LR; { rank = "same"; tmp->node1 } { rank = "same"; node_->node2->node1 } node1->node3->NULL } ``` ## `reverse` : :::warning 此function的功能是把lined list倒序 ::: `head` :當下的點 `cursor` :倒序中的下一點(等等要被head指向的點) `next` :原序中的下一點 ```graphviz digraph struct{ rankdir = LR; head[shape=plaintext,fontcolor=red] cursor[shape=plaintext,fontcolor=red] next[shape=plaintext,fontcolor=red] NULL[shape=plaintext,fontcolor=black] NULL1[shape=plaintext,fontcolor=black] { rank = "same"; head->node1 } { rank = "same"; next->node2 } cursor->NULL1 node1->node2->node3->NULL } ``` After `head->next = cursor;cursor = head` ```graphviz digraph struct{ head[shape=plaintext,fontcolor=red] cursor[shape=plaintext,fontcolor=red] next[shape=plaintext,fontcolor=red] NULL[shape=plaintext,fontcolor=black] NULL1[shape=plaintext,fontcolor=black] rankdir = LR; { head->node1 next->node2 cursor->node1 } node1->NULL1 node2->node3->NULL } ``` After `head = next` ` ```graphviz digraph struct{ head[shape=plaintext,fontcolor=red] cursor[shape=plaintext,fontcolor=red] next[shape=plaintext,fontcolor=red] NULL[shape=plaintext,fontcolor=black] NULL1[shape=plaintext,fontcolor=black] rankdir = LR; { head->node2 next->node2 cursor->node1->NULL1 } node2->node3->NULL } ``` next再往下走 ```graphviz digraph struct{ head[shape=plaintext,fontcolor=red] cursor[shape=plaintext,fontcolor=red] next[shape=plaintext,fontcolor=red] NULL[shape=plaintext,fontcolor=black] NULL1[shape=plaintext,fontcolor=black] rankdir = LR; { head->node2 next->node3 cursor->node1->NULL1 } node2->node3->NULL } ``` 再回到第一張圖重複執行直到head==NULL,最後再return cursor當作head即可. ## `print_list`: 用current指標走訪整個linked list並且output value. :::success 延伸問題2: 函式`swap_pair`和`reverse`對於指標的操作方式顯然異於`add_entry`及 `remove_entry`,需要額外做`head = ...`的更新,請用指標的指標來改寫.並避免回傳指標; ::: ## `swap_pair` 改寫: ```cpp void swap_pair(node_t **head) { for (node_t **node = head; *node && (*node)->next ; node = &(*node)->next->next) { node_t *tmp = *node; *node = (*node)->next; (tmp)->next = (*node)->next; (*node)->next = tmp; } } ``` 參考:[你所不知道的c語言:指標篇](https://hackmd.io/@sysprog/c-pointer?type=view#%E6%B2%92%E6%9C%89%E3%80%8C%E9%9B%99%E6%8C%87%E6%A8%99%E3%80%8D%E5%8F%AA%E6%9C%89%E3%80%8C%E6%8C%87%E6%A8%99%E7%9A%84%E6%8C%87%E6%A8%99%E3%80%8D) 看了這篇文章之後一開始還不太了解意思,不過透過實作+紙筆trace後,我才了解到為何改成指標的指標後就不用再return head了. ![](https://i.imgur.com/ga5XZQI.png) 這是原本的swap但是我拿掉了 return head 跑出來的結果. 再執行一次: ![](https://i.imgur.com/rrDvTzD.png) 看到這邊我發現,`swap_pair(head)`是第一個點,經過轉換後變成了第二個點,可是第一個點就沒有被指到,所以在print_list的時候總是從第二個點開始印,這也是為何原本的function要 return head 的原因. 最重要的是 : **head裡面的記憶體位置一直沒被改變!!** 一直都是指向value=72的這個點,所以一開始要用head的記憶體位址來當作input,然後當執行完`*node = (*node)->next;`時,head的內容的被改成第二個點也就是等等swap完後的第一個點,這樣就能達到目的了. ## `reverse` 改寫: ```cpp void reverse(node_t **head) { node_t *cursor = NULL; while (*head) { node_t *next = (*head)->next; (*head)->next = cursor; cursor = *head; *head = next; } *head = cursor; } ``` 當while迴圈執行完後,`*head`會指向NULL,所以最後一行把真正的head位址寫入`*head`. 跟`swap_pair`的問題很類似, 如果沒有`return cursor`的話會只印出一個點(72),因為head沒有被改變,所以依然指向72. :::success 延伸問題3: 以遞迴改寫上述的`reverse`,注意,你可能因此需要建立新的函式,如`rev_recursive`,隨後在`reverse`函式中呼叫`rev_recursive`; ::: ```cpp node_t *rev_recursive(node_t **head ,node_t *cursor){ if((*head)->next!=NULL){ node_t *next = (*head)->next; (*head)->next = cursor; cursor = *head; *head = next; rev_recursive(&next , cursor); } else{ (*head)->next = cursor; return *head; } } ``` 此function的使用方式: `head = rev_recursive(&head,NULL)` 解釋: 把reverse想成recursive的模式,我是由1.傳值2.重複做一樣的事這兩個特性下去想的. ```graphviz digraph struct{ head[shape=plaintext,fontcolor=red] cursor[shape=plaintext,fontcolor=red] next[shape=plaintext,fontcolor=red] NULL[shape=plaintext,fontcolor=black] rankdir = LR; { rank = "same"; head->node1 } { rank = "same"; next->node2 } { rank = "same"; cursor->NULL } node1->node2->node3->NULL } ``` 透過觀察我發現cursor,head,next會很有規律的往右邊移動,這部份就是遞迴的部份. 再來就是傳值,cursor無法藉由head找到(如下圖),只有next可以,所以我把這兩個值(head,cursor)當引數傳遞. ```graphviz digraph struct{ head[shape=plaintext,fontcolor=red] cursor[shape=plaintext,fontcolor=red] next[shape=plaintext,fontcolor=red] NULL[shape=plaintext,fontcolor=black] rankdir = LR; head->node2 { rank = "same"; next->node2 } { rank = "same"; cursor->node1->NULL } node2->node3->NULL } ``` ## 實作 Fisher–Yates shuffle :::success 延伸問題4: 針對`singlly-linked list`的節點,實作 [Fisher–Yates shuffle](https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle) ,你應該盡量降低記憶體的使用量; ::: 此演算法一開始是用另一個陣列來裝亂數值,又或者把亂數值裝在陣列後面,如下圖 ```graphviz digraph struct{ rankdir=LR; box[shape=record]; box[label="{<value> 1| <next>2|<next> 3|<next>|<next>|<next>}"]; } ``` 不過用linked list的方法,可以省去裝亂數的記憶體空間. linked list的方法: ```cpp void ran(node_t **current,int num){ srand(time(NULL)); for(int i=0;i<num;i++){ int ra = rand()%num; //ra means the (ra+1)th number node_t *head = *current; // head node_t *pre = NULL; //before the current if(ra!=0){ while(ra!=0){ pre = *current; *current = (*current)->next; ra--; } //走到要被置換的點 pre->next = (*current)->next; (*current)->next = head; } } } ``` - (ra+1) : 第幾個數字要被換到最前面 - head : 永遠指著第一個點 - current : 當下要被置換的點 - pre : 被置換的點的前一個點 使用: ```cpp node_t *head = NULL; int num; scanf("%d",&num); for(int i =1;i<=num;i++){ add_entry(&head, i); } ran(&head,num); ``` 觀念: 從linked list中隨機選一個數字(1 ~ n),將此數字移到最前面當作head,再從剩下的list中(2 ~ n)再隨機選一個數字,再移到最前面,以此類推. 假設選到第二個node: ```graphviz digraph struct{ head[shape=plaintext,fontcolor=red] current[shape=plaintext,fontcolor=red] pre[shape=plaintext,fontcolor=red] NULL[shape=plaintext,fontcolor=black] rankdir = LR; { rank = "same"; head->node1 } { rank = "same"; pre->node1 } { rank = "same"; current->node2 } node1->node2->node3->NULL } ``` node1指向node3: ```graphviz digraph struct{ head[shape=plaintext,fontcolor=red] current[shape=plaintext,fontcolor=red] pre[shape=plaintext,fontcolor=red] NULL[shape=plaintext,fontcolor=black] rankdir = LR; { rank = "same"; head->node1 } { rank = "same"; pre->node1 } { rank = "same"; current->node2 } node1->node3->NULL node2->node3 } ``` 把node2指向head: ```graphviz digraph struct{ head[shape=plaintext,fontcolor=red] current[shape=plaintext,fontcolor=red] pre[shape=plaintext,fontcolor=red] NULL[shape=plaintext,fontcolor=black] rankdir = LR; { rank = "same"; head->node2 } { rank = "same"; pre->node2 } { rank = "same"; current->node2 } node2->node1->node3->NULL } ``` 再隨機選下一個數..... ## 心得: 這份作業對我而言是很好的鍛鍊也是一個大挑戰,linked list我一直都沒真正的實作過,都是紙上談兵而已,對於指標的概念也是非常模糊... 透過實作這次的作業以及和朋友激烈的討論,我才發現我原本的某些觀念是不對的(我以前跟本沒發現自己的bug),甚至有些非常基本的觀念我也是到現在才知道,雖然才寫完這份作業,但我感覺到很充實!