# 2019q1 第 1 週測驗題
:::info
目的: 檢驗學員對 [bitwise operator](https://hackmd.io/s/By0MltO_m) 及遞迴程式設計的認知
:::
---
### 測驗 `1`
西洋棋中的皇后可以直線前進,吃掉遇到的所有棋子,如果棋盤上有 8 個皇后,則這 8 個皇后如何相安無事地放置在棋盤上,1970 年與 1971 年, E.W.Dijkstra 與 N.Wirth 曾用這個問題來講解程式設計之技巧。
關於棋盤的問題,都可以用遞迴求解,然而如何減少遞迴的次數?在八個皇后的問題中,不必要所有的格子都檢查過,例如若某列檢查過,該該列的其它格子就 不用再檢查了,這個方法稱為分支修剪。
![](https://i.imgur.com/Ntc7TDQ.png)
所以檢查時,先判斷是否在已放置皇后的可行進方向上,如果沒有再行放置下一個皇后,如此就可大大減少遞迴的次數,例如以下為修剪過後的遞迴檢 查行進路徑:
![](https://i.imgur.com/0aUWpaI.png)
8 個皇后的話,會有 92 個解答,如果考慮棋盤的旋轉,則旋轉後扣去對稱的,會有 12 組基本解。 [ [source](https://openhome.cc/Gossip/AlgorithmGossip/EightQueen.htm) ]
透過 [bitwise 演算法](http://jgpettibone.github.io/bitwise-n-queens/) 改寫為以下 C 程式:
```cpp
#include <stdio.h>
#include <stdlib.h>
static int sol_count = 0;
void recursive_solver(int row, int maj_con, int min_con, int col_con, int n)
{
int queen_position;
int conflicts = min_con | maj_con | col_con;
int i = 0;
min_con &= 65535;
while (i < n) {
queen_position = 1 << (A1 - i);
if (!(queen_position & conflicts)) {
if (row == n - 1)
sol_count++;
else
recursive_solver(row + A2, (maj_con | queen_position) >> A3,
(min_con | queen_position) << A4,
col_con | queen_position, n);
}
i++;
}
}
void solve_queens(int n)
{
int minor_dconflict = 0, major_dconflict = 0, column_conflict = 0;
recursive_solver(0, column_conflict, major_dconflict, minor_dconflict, n);
printf("solutions = %d\n", sol_count);
}
```
其中 `solve_queens` 的參數 `n`,數值介於 `4` 到 `16`。
請補完程式碼。
==作答區==
A1 = ?
* `(a)` 16
* `(b)` 15
* `(c)` 14
* `(d)` 13
* `(e)` 12
* `(f)` 11
* `(g)` 10
* `(h)` 9
* `(i)` 8
A2 = ?
* `(a)` 7
* `(b)` 6
* `(c)` 5
* `(d)` 4
* `(e)` 3
* `(f)` 2
* `(g)` 1
* `(h)` 0
A3 = ?
* `(a)` 7
* `(b)` 6
* `(c)` 5
* `(d)` 4
* `(e)` 3
* `(f)` 2
* `(g)` 1
* `(h)` 0
A4 = ?
* `(a)` 7
* `(b)` 6
* `(c)` 5
* `(d)` 4
* `(e)` 3
* `(f)` 2
* `(g)` 1
* `(h)` 0
:::success
延伸題目:
1. 讓參數 `n` 的有效範圍比 `[4, 16]` 更大,但仍透過 bitwise 操作,並改寫為 iteration 程式;
2. 考慮到 tail-call optimization (TCO),改進上述遞迴程式碼並分析效能表現;
3. 考慮旋轉後扣去對稱的解法,求出基本解法的總量;
:::
---
### 測驗 `2`
考慮到以下程式碼:
```clike
#include <stdint.h>
#define ALIGN_uint32(x, a) __ALIGN_MASK(x, ((uint32_t)(a)) - 1)
#define __ALIGN_MASK(x, mask) (((x) + (mask)) & ~(mask))
```
這是 data alignment 的巨集,data alignment 的意思是,data 的 address 扣除 base address 後 (也就是 offset) 可被 1, 2, 4, 8 等等這些數字整除,從這些數字可以發現他們都是 2^N^ (N 為從 0 開始的整數)。換句話說,這些 data object 可以用 1, 2, 4, 8 byte 去 alignment
* 電腦處理器如何抓取資料呢?CPU 不會一次只抓取 1 byte 的資料,因為這樣太慢了,如果有個資料型態是 int 的 資料,如果你只抓取 1 byte ,就必須要抓 4 次 (假設 int 資料寬度為 4 byte),相當不理想。所以 CPU 傾向一次擷取 4 byte(實際看硬體規格而定,32 位元的 cpu 一次可以讀取 32 bit 的資料,64 位元一次可以讀取 64 bit),並按照順序取的。舉例來說:
* 第一次取: bit 0 ~ bit 3
* 第二次取: bit 4 ~ bit 7
![](https://i.imgur.com/aDCYyWc.png)
所以如果你的資料是分布在 bit 1 ~ bit 4 那還是會
* 第一次取 byte 0 ~ byte 3,再去除 byte 0 的資料,留下 byte 1 ~ byte 3
* 第二次取 byte 4 ~ byte 7,並去除 byte 5 ~ byte 7 的資料,留下 byte 4
* 再將 byte 1 ~ 3 和 byte 4 整合為 32-bit
![](https://i.imgur.com/wIfEVy9.png)
由於資料分布不在 4 的倍數,導致了存取速度降低,編譯器在分配記憶體時,就會按照宣告的型態去做 alignment ,例如 int 就是 4 byte alignment。
考慮 4-byte boundary,當 x 是 `0x1006`, a 是 `4`, 那麼 `ALIGN(x, a)` 會得到什麼數值?
==作答區==
X = ?
* `(a)` 0x1000
* `(b)` 0x1004
* `(c)` 0x1008
* `(d)` 0x100b
* `(e)` 0x1010