**程式設計Ⅰ【筆記Ⅱ】** === * [name=Ivy Lin] * [筆記1](https://hackmd.io/NPFilj4fSNu020tfh5s3TQ) * [筆記3](https://hackmd.io/bvpG9TeaSTa5Y9cQGVWKaw) ## 第九週<2023.4.11> ### 遞迴(gcd) :::info 歐幾里得演算法原理: >A=B*q+R >(A,B)=(B,R) 說明: >將兩數相減(大減小),直到一邊為0則另一邊剩餘的數及為兩數的最大公因數。 ::: ```clike= //程式_遞迴寫法 int gcd(int a,int b){ if(b==0) return a; else return gcd(b,a%b); } ``` ### 擺皇后(西洋棋) 這題比較複雜,因此我們先從三個城堡問題開始。 #### **三個城堡(初階棋盤遞迴)** * **敘述** >城堡可以走十字,我們想在3*3的棋盤上擺上3個城堡 >請問有哪些擺法? * **思路** >1. 先放旗子 >2. 把不能放的地方刪掉 >3. 遞迴=>剩餘的地方擺放 >4. 停止點=>完整擺放 * **程式** ```clike= #include <stdio.h> // 城堡位置(1,1)~(3,3) int board[5][5]; // 印出棋盤 void show_board() { for (int i = 1; i <= 3; i++) { for (int j = 1; j <= 3; j++) printf("%d", board[i][j]); printf("\n"); } printf("\n"); } // 判斷此位置能否放置城堡 int check(int row, int i) { for (int j = 1; j < row; j++) { if (board[j][i]) return 0; } return 1; } // 判斷現在要放第幾行 void place(int row) { if (row > 3) show_board(); else { for (int i = 1; i <= 3; i++) { if (check(row, i)) { // 遞迴處!!! board[row][i] = 1; place(row + 1); // 這個步驟非常重要!!!(把城堡拿起來) board[row][i] = 0; } } } } int main() { place(1); return 0; } ``` :::danger 注意 >在這個程式中,最重要的部分其實是"把城堡拿起來"。 每次的遞迴,都是在執行"放下一行的城堡" 因此結束遞迴後,要"回復"上一階段的動作就需要"取消"你放城堡的動作。 ::: #### **擺放皇后(棋盤遞迴)** * **敘述** >皇后可以走米字,我們想在n*n的棋盤上擺上n個皇后 >請問有哪些擺法? * **思路** >1. 和城堡的思路一樣(但在檢查的地方要額外檢查斜線) >2. 精簡紀錄的點=>不記錄行數,改成紀錄放置第幾格即可(可以有效簡化記錄的資料) >3. 檢驗是否可放的函數記得要多檢查45度(斜上方的格子是否有被放置棋子) * **程式** ```clike= #include <stdio.h> int board[20]; int ans; // int place(int row, int size); // int check(int row, int inde); int place(int row, int size) { // check if the row is equal to the size of the board //printf("place(row,size)=(%d,%d)\n", row, size); if (row == size) ans += 1; else { // place the queen for (int i = 0; i < size; i++) { // check (row,i) can place the queen or not //printf("check(row,inde)=(%d,%d)\n", row, i); if (check(row, i)) { board[row] = i; place(row + 1, size); } } } return ans; } int check(int row, int inde) { for (int i = 0; i < row; i++) { if (board[i] == inde || board[i] - inde == i - row || board[i] - inde == row - i) { //printf("can't put in (%d,%d)\n\n", row, inde); return 0; } } //printf("put in (%d,%d)\n\n", row, inde); return 1; } int main() { int size; scanf("%d", &size); printf("%d\n", place(0, size)); return 0; }#include <stdio.h> int board[20]; int ans; // int place(int row, int size); // int check(int row, int inde); int place(int row, int size) { // check if the row is equal to the size of the board //printf("place(row,size)=(%d,%d)\n", row, size); if (row == size) ans += 1; else { // place the queen for (int i = 0; i < size; i++) { // check (row,i) can place the queen or not //printf("check(row,inde)=(%d,%d)\n", row, i); if (check(row, i)) { board[row] = i; place(row + 1, size); } } } return ans; } int check(int row, int inde) { for (int i = 0; i < row; i++) { if (board[i] == inde || board[i] - inde == i - row || board[i] - inde == row - i) { //printf("can't put in (%d,%d)\n\n", row, inde); return 0; } } //printf("put in (%d,%d)\n\n", row, inde); return 1; } int main() { int size; scanf("%d", &size); printf("%d\n", place(0, size)); return 0; } ``` #### **找錢問題** * **敘述** >輸入不同面值的銅板,然後輸入金額,將可能的找零方式列出 * **思路** >1. 重複做的地方=>要選擇幾個面值銅板(可以有很多種配法) >2. 遞迴=>考慮兩種可能 >(1) 使用1元=>錢變成M-1元 >(2) 不使用1=>考慮剩下的5和10元來湊 >3. 停止遞迴=>手上剩下0元、手邊剩下小於0元、沒有辦法湊了 * **程式** ```clike= #include <stdio.h> int count[10]; int number[10]; void change(int money, int inde, int total) { // 檢查點1=>是否超過可用的面額種類 if (inde < total) { // 檢查點2=>完成題目條件(money=0) if (money == 0) show(total); // 檢查點3=>money是否還可以找錢(小於0就爆掉QAQ) else if (money > 0) { // 遞迴處!!! // 第一種狀況=>使用大一個面額湊 change(money, inde + 1, total); // 第二種狀況=>多使用一個同面額 number[inde] += 1; change(money - count[inde], inde, total); number[inde] -= 1; // 記得要記得"拿起來"這個動作!!! } } } ``` ```clike= void show(int total) { printf("(%d", number[0]); for (int i = 1; i < total; i++) printf(",%d", number[i]); printf(")\n"); } int main() { // 輸入總共有幾種面額的銅板 int total; scanf("%d", &total); // 輸入面額為多少 for (int i = 0; i < total; i++) scanf("%d", &count[i]); // 輸入要找的錢 int money; scanf("%d", &money); change(money, 0, total); return 0; } ``` #### **其他遞迴_進位轉換** * **敘述** >轉換10進位=>2進位 * **思路** >1. 重複做的地方=>/2、%2 >2. 遞迴=> >(1)將數字想成=>n/2的二進位表示(前)+n%2的值(後) >(2)所以每次遞迴就是=>將數字拆兩份,前一份遞迴 >3. 停止點=>n/2=0 * **程式** ```clike= #include <stdio.h> void binary(int n) { if (n > 0) { //遞迴處!!! binary(n / 2); //注意是先遞迴後印數字 printf("%d", i % 2); } } int main() { int n; scanf("%d", &n); binary(n); return 0; } ``` #### **其他遞迴_費氏數列** * **敘述** >f(0)=0; >f(1)=1; >f(n+1)=f(n)+f(n-1); * **思路** >1. 重複做的地方=>加法 >2. 停止點=>n=0或n=1 * **程式** ```clike= #include <stdio.h> int table[100]; int fa_sequence(int n) { if (n == 0 || n == 1) return table[n]; if (table[n] != 0) return table[n]; else { table[n] = fa_sequence(n - 1) + fa_sequence(n - 2); return table[n]; } } int main() { table[0] = 0; table[1] = 1; int n; scanf("%d", &n); printf("%d\n", fa_sequence(n)); return 0; } ``` :::danger 注意 >費氏數列和河內塔一樣,數字呈指數型態成長, >遞迴只適用於數字小的狀況 ::: #### **其他遞迴_優先運算** * **敘述** >將運算子往前移,該如何顯示運算結果 * **思路** >1. 看到運算子=>準備接收數值 >2. 停止點=>收到數值&return * **程式** ```clike= #include <stdio.h> #include <ctype.h> char c; int a, op1, op2; int read() { c = getchar(); if (c == ' ') return read(); else if (isdigit(c)) { //把收到的數值吐回到stdin(標準輸入) ungetc(c, stdin); scanf("%d", &a); return a; } else if (c == '+') { //遞迴處!!! //op1和op2分別接收接下來的兩個數字 op1 = read(); op2 = read(); return op1 + op2; } else if (c == '-') { op1 = read(); op2 = read(); return op1 - op2; } else if (c == '*') { op1 = read(); op2 = read(); return op1 * op2; } } int main() { printf("%d\n", read()); return 0; } ``` * **程式_改成標準寫法** ```clike= #include <stdio.h> #include <ctype.h> int cal(void) { int ans, op1, op2; char c = getchar(); if (c == ' ') return cal(); // 直接收到數字(吐回去&直接印出來) else if (isdigit(c)) { ungetc(c, stdin); scanf("%d", &ans); printf("%d", ans); return ans; } // 收到需要處理的值=>遞迴後印出運算 if (c != '*') printf("("); op1 = cal(); printf(" %c ", c); op2 = cal(); if (c != '*') printf(")"); // 運算最後解答 if (c == '+') return op1 + op2; else if (c == '-') return op1 - op2; else if (c == '*') return op1 * op2; } int main(void) { printf(" = %d\n", cal()); return 0; } ``` ### Binary Search >時間複雜度=O(log2N) ```clike= #include <stdio.h> int data[] = {1, 7, 9, 10, 27, 41, 60, 75, 89, 100}; void search(int s, int l, int r) { int mid = (l + r) / 2; if (l > r) printf("Not in the data\n"); if (data[mid] > s) search(s, mid + 1, r); if (data[mid] < s) search(s, l, mid - 1); if (data[mid] == s) printf("data[%d]=%d\n", mid, s); } int main() { int s; scanf("%d", &s); search(s, 0, 9) return 0; } ``` ### Merge Sort >要做binary sort前,需要由小到大(或由大到小)排列好的資料, >Merge Sort 就是其中一種排列方法 ## 第十週<2023.4.18> ### 指標 * 指標小介紹 >typedef使用 >指標變數宣告=>型別+ * >取址符號=>& >取值符號=>* (和變數宣告那個*不同) >印出sizeof(n)=> %lu * 陣列vs指標 >陣列名即為陣列第一項的指標(所以scanf陣列有時候不需要加上&) >a[i] <=> *(a+i) >&a[i] <=> (a+i) * 指標變數byte >sizeof char*: 8 >sizeof short*: 8 >sizeof int*: 8 >sizeof long int*: 8 >sizeof float*: 8 >sizeof double*: 8 >sizeof char: 1 >sizeof short: 2 >sizeof int: 4 >sizeof long int: 8 >sizeof float: 4 >sizeof double: 8 * 等價寫法 >a[i] ⟺ *(a+i) >&a[i] ⟺ (a+i) * 讀檔案 ```clike= FILE *fopen(const char *filename, const char *mode) int fscanf(FILE *stream, const char *format [, argument, ...]) int fprintf(FILE *stream, const char *format [, argument, ...]) int fclose(FILE *stream) ```