# 2020q3 Homework4 (quiz4) contributed by < `jonec76` > ###### tags: `sysprog-2020` > [作業說明](https://hackmd.io/@sysprog/r1z0WPWLD) > [題目](https://hackmd.io/@sysprog/2020-quiz4) ## 開發環境 ```shell $ uname -a Linux jonec76-Aspire-M7720 5.4.0-47-generic #51-Ubuntu SMP GNU/Linux ``` ## 參考解答 OP = ^ AAA = int ** BBB = -1 CCC = 1 << i KK1 = div5 KK2 = div3 KK3 = 2 ## Q1 - 解題想法 ```c=1 int hammingDistance(int x, int y) { return __builtin_popcount(x ^ y); } ``` 可以先觀察一下圖上的點,如果兩點之間 bit 中只有 1 位不相同的話,那可以知道兩個點的距離只差 1。根據這個原則,只要找出兩個數字有多少個 bit 不同即可。 那如何找出呢?這邊可以使用 XOR 找出,因為 XOR 的特性剛好可以使不同 bit (`0^1`)出來的結果是 1,若是相同的話就會變成 0。 接著,再使用 `__builtin_popcount` 計算出有多少個 1,即可知道答案了。 ## Q2 - 解題方向 在解 AAA 時,可以從下方這行程式碼得出 ```c=1 obj->parent = malloc(n * sizeof(int *)); ``` 表示 `obj->parent` 是個具有 n 個 `int *` 的 pointer,如此即可知道應為 `int **` 型態。 在解 BBB 之前,可以先了解此題題目目的,參考下方程式碼 ```c=1 int max_level = 32 - __builtin_clz(n) + 1; for (int i = 0; i < n; i++) { obj->parent[i] = malloc(max_level * sizeof(int)); for (int j = 0; j < max_level; j++) obj->parent[i][j] = -1; } ``` 找出 `max_level` 的方法蠻有趣的,背後含義是做了 $\lfloor log\ n\rfloor$。一開始想說應該是想要找出 n 最大會在哪個 level,但是!之後再看一次題目才知道不是這樣子(需要配合 `treeAncestorGetKthAncestor` 來看) 這邊就是建立每個點的 parent table,用來記錄該點之上的 "幾個" parent(不是所有都紀錄!),並且對每個 node 的 parent table 做初始化。 ```c=1 for (int i = 0; i < parentSize; i++) obj->parent[i][0] = parent[i]; ``` 接著可以看到下方 BBB 的填空 ```c=1 for (int i = 0; i < parentSize; i++) { obj->parent[i][j] = obj->parent[i][j + BBB] == -1 ? -1 : obj->parent[obj->parent[i][j - 1]][j - 1]; if (obj->parent[i][j] != -1) quit = 0; } ``` 若是倒著推回來的話,可以先思考一下怎樣的情況,parent 會是 `-1`? 同時,又可以看到需要根據該點之外的其他 parent ,來更新目前的 parent。此時就可以推測出,如果在 `j - 1` 已經是 `-1`,也就是沒有任何 node 在 `j - 1` 之上,那 `j` 一定也不會有 parent。 接著,看到最後 CCC 選項 ```c=1 int treeAncestorGetKthAncestor(TreeAncestor *obj, int node, int k) { int max_level = obj->max_level; for (int i = 0; i < max_level && node != -1; ++i) if (k & (CCC)) node = obj->parent[node][i]; return node; } ``` 上述程式碼蠻有趣的,是要拿第 k 個 parent。當初看到此題時,想說就是 n 往上數到第 k 個 node 並且回傳。但事實上並非如此,以下圖為例子 ```graphviz digraph skew_BST { size="10,3!" node [fontname="Arial"]; 0 -> 1; 1 -> 2; 2 -> 3; 3 -> 4; 4 -> 5; 5 -> 6; } ``` 以例子來看,在 node 6 的 parent table 應為下圖,parent[i] 紀錄的是往上數第 $2^i$ 個點 ```graphviz digraph hash_table { nodesep = .05; rankdir = LR; node[shape = record, width = .1, height = .1]; node0[label = "<f9> 6", height = 0.5]; node [width = 1.5]; node9[label = "{ 5|4|2}"]; node0:f9 -> node9; } ``` why? 由下方程式碼可以看出 ```c=1 for (int j = 1;; j++) { int quit = 1; for (int i = 0; i < parentSize; i++) { obj->parent[i][j] = obj->parent[i][j + BBB] == -1 ? -1 : obj->parent[obj->parent[i][j - 1]][j - 1]; ``` 以 `parent[5][2]` (node 6) 來看,那就會被更新為 `parent[4][1]` (node 5)。 以 `parent[5][3]` (node 6) 來看,那就會被更新為 `parent[3][2]` (node 4)。 這樣的紀錄方法,將會使得 `parent[i]` 紀錄的是往上數第 $2^i$ 個點。對於這樣設計的原因,自己思考後是認為這樣的設計能夠處理 balanced tree,也能處理 skew tree,不需要因為 tree 的高度而變動 parent table size,反而只要知道 node 總數即可。 既然如此,再回到 CCC 選項。若 node=6, k=5 時,也就是要找出 node=6 往上數第 5 個的 node。這邊以 `treeAncestorGetKthAncestor` 來看,跑法會是 1. 取出 node 6 的第 $2^0$ parent,也就是 5 2. 5 再取出第 $2^2$ 點,也就是 0 打破原先那直觀的觀念(往上走 5 步),而是用走 "2 的冪次方" 的方法取點。 ## Q3 - 程式碼分析 ```c=1 if (i % 3 == 0) printf("Fizz"); if (i % 5 == 0) printf("Buzz"); if (i % 15 == 0) printf("FizzBuzz"); if ((i % 3) && (i % 5)) printf("%u", i); ``` 此題的原意其實很簡單,目的就是根據以上的程式碼印出。 接著此題希望使用 `strncpy` 的方式,來複製出原始字串的 substring。複製的參數是 1. start 字串的起始位置 2. 複製的長度 複製程式碼如下: ```c=1 #define MSG_LEN 8 char fmt[MSG_LEN + 1]; strncpy(fmt, &"FizzBuzz%u"[start], length); fmt[length] = '\0'; printf(fmt, i); printf("\n"); ``` - 解題想法 ```c=1 strncpy(fmt, &"FizzBuzz%u"[(MSG_LEN >> KK1) >> (KK2 << KK3)], length); ``` 此題基本上就是要把數字湊出來,在 [quiz2-Q4](https://hackmd.io/@jonec76/2020q3-quiz2#Q4) 中分析過 `is_divisible` 的原理。所以可以知道此題的 `div3` 、 `div5` 就會表示出目前的數可否被相對 `3` 或 `5` 整除,可以的話就得 `1`,否則 `0`。 以下為 output 合併 `strncpy` 參數的 table: |value|output|start index|length| |---|---|---|---|---| |n 整除 3|"Fizz"|0|4| |n 整除 5|"Buzz"|4|4| |n 整除 15|"FizzBuzz"|0|8| |以上皆非|n|0|0| 湊了幾組後即可得到答案。 這邊在課堂上寫的時候發生了粗心的情況,順便記錄一下。當時把 `div3 << 2` 跟 `2 << div3` 寫相反,當 `div3=1` 時的確值是一樣,但是缺少考慮了 `div3=0` 的 case。