Advance C/C++
Table of Contents
1. Sort
排序(Sorting)是將一組資料依照特定的順序重新排列的過程,是程式設計中最基礎也最重要的演算法之一。
1.1. Bubble Sort (氣泡排序)
氣泡排序的概念很簡單:反覆比較相鄰的兩個元素,如果順序不對就交換,就像水中的氣泡一樣,較大的元素會逐漸「浮」到陣列的尾端。
1: #include <iostream> 2: using namespace std; 3: 4: int main() { 5: int arr[] = {64, 34, 25, 12, 22, 11, 90}; 6: int n = 7; 7: 8: // Bubble Sort 9: for (int i = 0; i < n - 1; i++) { // 共需 n-1 輪 10: for (int j = 0; j < n - 1 - i; j++) { // 每輪少比一次 11: if (arr[j] > arr[j + 1]) { 12: // 交換 13: int temp = arr[j]; 14: arr[j] = arr[j + 1]; 15: arr[j + 1] = temp; 16: } 17: } 18: } 19: 20: cout << "排序結果: "; 21: for (int i = 0; i < n; i++) 22: cout << arr[i] << " "; 23: cout << endl; 24: return 0; 25: }
- 時間複雜度:O(n²)
- 空間複雜度:O(1)
1.2. Selection Sort (選擇排序)
選擇排序的想法是:每一輪從未排序的部分中找出最小值,放到已排序部分的尾端。
1: #include <iostream> 2: using namespace std; 3: 4: int main() { 5: int arr[] = {64, 25, 12, 22, 11}; 6: int n = 5; 7: 8: for (int i = 0; i < n - 1; i++) { 9: int minIdx = i; // 假設目前位置為最小值 10: for (int j = i + 1; j < n; j++) { 11: if (arr[j] < arr[minIdx]) 12: minIdx = j; // 記錄更小值的位置 13: } 14: // 將最小值交換到正確位置 15: int temp = arr[i]; 16: arr[i] = arr[minIdx]; 17: arr[minIdx] = temp; 18: } 19: 20: cout << "排序結果: "; 21: for (int i = 0; i < n; i++) 22: cout << arr[i] << " "; 23: cout << endl; 24: return 0; 25: }
- 時間複雜度:O(n²)
- 空間複雜度:O(1)
1.3. Insertion Sort (插入排序)
插入排序的概念類似於打撲克牌時整理手牌的方式:從第二張牌開始,將每張牌插入到前面已排序部分的正確位置。
1: #include <iostream> 2: using namespace std; 3: 4: int main() { 5: int arr[] = {12, 11, 13, 5, 6}; 6: int n = 5; 7: 8: for (int i = 1; i < n; i++) { 9: int key = arr[i]; // 要插入的元素 10: int j = i - 1; 11: // 將比 key 大的元素往後移 12: while (j >= 0 && arr[j] > key) { 13: arr[j + 1] = arr[j]; 14: j--; 15: } 16: arr[j + 1] = key; // 插入正確位置 17: } 18: 19: cout << "排序結果: "; 20: for (int i = 0; i < n; i++) 21: cout << arr[i] << " "; 22: cout << endl; 23: return 0; 24: }
- 時間複雜度:O(n²),但對於近乎排序好的資料效率很高
- 空間複雜度:O(1)
1.4. 三種排序法比較
| 排序法 | 最佳 | 平均 | 最差 | 穩定性 |
|---|---|---|---|---|
| Bubble | O(n) | O(n²) | O(n²) | 穩定 |
| Selection | O(n²) | O(n²) | O(n²) | 不穩定 |
| Insertion | O(n) | O(n²) | O(n²) | 穩定 |
3. 2D Array
3.1. Array of string
- CPP
- C
1: #include <stdio.h> 2: #include <string.h> 3: #include <stdlib.h> 4: int main(int argc, const char * argv[]) { 5: // insert code here... 6: char poker[53][5]; 7: char m[4] = {'A','B','C','D'}; 8: char tmp[5]; 9: int i, j, k; 10: for (i = 0; i < 4; i++) { 11: for (j = 0; j < 13; j++) { 12: k = 13 * i + j; 13: sprintf(tmp, "%d%c", j+1, m[i]); 14: //printf("%d: %s\n", k, tmp); 15: strcpy(poker[k], tmp); 16: } 17: } 18: for (i = 0; i < 52; i++) { 19: printf("%s\n", poker[i]); 20: } 21: 22: return 0; 23: } 24:
4. Variable
4.1. Global v.s. Local
在 C/C++ 中,變數依照宣告的位置不同,可分為全域變數(Global Variable)和區域變數(Local Variable),兩者的生命週期與可見範圍(scope)截然不同。
- Global Variable (全域變數)
宣告在所有函式之外的變數,程式中任何函式都可以存取。
- Local Variable (區域變數)
宣告在函式或區塊(大括號 {} )內的變數,只在該區塊內有效。
1: #include <iostream> 2: using namespace std; 3: 4: int globalVar = 100; // 全域變數,所有函式都能存取 5: 6: void showValues() { 7: int localVar = 50; // 區域變數,只在此函式內有效 8: cout << "在 showValues() 中:" << endl; 9: cout << " globalVar = " << globalVar << endl; 10: cout << " localVar = " << localVar << endl; 11: } 12: 13: int main() { 14: int localVar = 200; // 此 localVar 與 showValues() 中的不同 15: 16: cout << "在 main() 中:" << endl; 17: cout << " globalVar = " << globalVar << endl; 18: cout << " localVar = " << localVar << endl; 19: 20: showValues(); 21: 22: globalVar = 999; // 修改全域變數 23: cout << "修改後 globalVar = " << globalVar << endl; 24: 25: return 0; 26: }
- 注意事項
- 全域變數若未初始化,預設值為 0;區域變數若未初始化,其值為不確定(garbage value)
- 當全域變數和區域變數同名時,在函式內會優先使用區域變數(區域變數會遮蔽全域變數)
- 過度使用全域變數會讓程式難以維護與除錯,應盡量使用區域變數
- 全域變數若未初始化,預設值為 0;區域變數若未初始化,其值為不確定(garbage value)
5. Function
5.2. Recursion
遞迴(Recursion)是指函式在執行過程中呼叫自己本身的技巧。每一個遞迴函式都需要具備兩個要素:
- *終止條件(Base Case)*:讓遞迴停止的條件,否則會無窮呼叫導致程式崩潰
- *遞迴呼叫(Recursive Case)*:函式呼叫自己,但問題規模必須逐漸縮小
- 階乘 (Factorial)
n! = n × (n-1) × (n-2) × … × 1,例如 5! = 5 × 4 × 3 × 2 × 1 = 120。
1: #include <iostream> 2: using namespace std; 3: 4: int factorial(int n) { 5: if (n <= 1) // Base Case:0! = 1, 1! = 1 6: return 1; 7: return n * factorial(n - 1); // Recursive Case 8: } 9: 10: int main() { 11: for (int i = 0; i <= 10; i++) 12: cout << i << "! = " << factorial(i) << endl; 13: return 0; 14: }
- 費氏數列 (Fibonacci)
費氏數列的定義為:F(0)=0, F(1)=1, F(n)=F(n-1)+F(n-2)。
1: #include <iostream> 2: using namespace std; 3: 4: int fibonacci(int n) { 5: if (n == 0) return 0; // Base Case 6: if (n == 1) return 1; // Base Case 7: return fibonacci(n - 1) + fibonacci(n - 2); // Recursive Case 8: } 9: 10: int main() { 11: cout << "費氏數列前 15 項:" << endl; 12: for (int i = 0; i < 15; i++) 13: cout << "F(" << i << ") = " << fibonacci(i) << endl; 14: return 0; 15: }
- 河乃乃塔 (Tower of Hanoi)
經典的遞迴問題:將 n 個盤子從 A 柱移動到 C 柱,過程中可借助 B 柱,且大盤不可放在小盤上方。
1: #include <iostream> 2: using namespace std; 3: 4: void hanoi(int n, char from, char to, char aux) { 5: if (n == 1) { 6: cout << "將盤子 1 從 " << from << " 移到 " << to << endl; 7: return; 8: } 9: hanoi(n - 1, from, aux, to); // 先將上方 n-1 個盤子移到輔助柱 10: cout << "將盤子 " << n << " 從 " << from << " 移到 " << to << endl; 11: hanoi(n - 1, aux, to, from); // 再將 n-1 個盤子從輔助柱移到目標柱 12: } 13: 14: int main() { 15: int n = 3; 16: cout << n << " 個盤子的河乃乃塔步驟:" << endl; 17: hanoi(n, 'A', 'C', 'B'); 18: return 0; 19: }
- 遞迴 vs. 迴圈
- 遞迴程式碼通常較簡潔易讀,但每次呼叫會佔用 stack 記憶體,層數過深會導致 stack overflow
- 迴圈效率通常較高,不會有 stack overflow 的問題
- 許多遞迴問題都可以改寫為迴圈版本
- 遞迴程式碼通常較簡潔易讀,但每次呼叫會佔用 stack 記憶體,層數過深會導致 stack overflow
7. 資料結構
7.1. Array
- Find 2’s complement: https://www.youtube.com/watch?v=cDCWZ-9ugYM
- First Unique Character in a string: https://www.youtube.com/watch?v=vCB_0cFQr5o
7.2. Stack
Stack(堆疊)是一種後進先出(LIFO, Last In First Out)的資料結構,就像疊盤子一樣,最後放上去的盤子會最先被拿走。
Stack 的基本操作:
- *push*:將元素放入堆疊頂端
- *pop*:移除堆疊頂端的元素
- *top*:查看堆疊頂端的元素(不移除)
- *empty*:檢查堆疊是否為空
- 用陣列實作 Stack
1: #include <iostream> 2: using namespace std; 3: 4: const int MAX_SIZE = 100; 5: int stack[MAX_SIZE]; 6: int topIdx = -1; // -1 表示空堆疊 7: 8: void push(int val) { 9: if (topIdx >= MAX_SIZE - 1) { 10: cout << "Stack Overflow!" << endl; 11: return; 12: } 13: stack[++topIdx] = val; 14: } 15: 16: void pop() { 17: if (topIdx < 0) { 18: cout << "Stack Underflow!" << endl; 19: return; 20: } 21: topIdx--; 22: } 23: 24: int top() { 25: return stack[topIdx]; 26: } 27: 28: bool empty() { 29: return topIdx < 0; 30: } 31: 32: int main() { 33: push(10); 34: push(20); 35: push(30); 36: cout << "頂端元素: " << top() << endl; // 30 37: 38: pop(); 39: cout << "pop 後頂端元素: " << top() << endl; // 20 40: 41: pop(); 42: pop(); 43: cout << "Stack 是否為空: " << (empty() ? "是" : "否") << endl; 44: return 0; 45: }
- 使用 C++ STL 的 stack
1: #include <iostream> 2: #include <stack> 3: using namespace std; 4: 5: int main() { 6: stack<int> s; 7: s.push(10); 8: s.push(20); 9: s.push(30); 10: 11: cout << "頂端: " << s.top() << endl; 12: s.pop(); 13: cout << "pop 後頂端: " << s.top() << endl; 14: cout << "大小: " << s.size() << endl; 15: return 0; 16: }
- Stack 的應用
- 括號配對檢查
- 運算式求值(中序轉後序)
- 函式呼叫的 call stack
- 瀏覽器的上一頁功能(Back)
- 括號配對檢查
7.3. Queue
Queue(佇列)是一種先進先出(FIFO, First In First Out)的資料結構,就像排隊買東西一樣,先排的人先服務。
Queue 的基本操作:
- *enqueue(push)*:將元素加入佇列尾端
- *dequeue(pop)*:移除佇列前端的元素
- *front*:查看佇列前端的元素
- *empty*:檢查佇列是否為空
- 用陣列實作 Queue
1: #include <iostream> 2: using namespace std; 3: 4: const int MAX_SIZE = 100; 5: int queue[MAX_SIZE]; 6: int frontIdx = 0, rearIdx = 0; 7: 8: void enqueue(int val) { 9: if (rearIdx >= MAX_SIZE) { 10: cout << "Queue 已滿!" << endl; 11: return; 12: } 13: queue[rearIdx++] = val; 14: } 15: 16: void dequeue() { 17: if (frontIdx >= rearIdx) { 18: cout << "Queue 為空!" << endl; 19: return; 20: } 21: frontIdx++; 22: } 23: 24: int front() { 25: return queue[frontIdx]; 26: } 27: 28: bool empty() { 29: return frontIdx >= rearIdx; 30: } 31: 32: int main() { 33: enqueue(10); 34: enqueue(20); 35: enqueue(30); 36: cout << "前端元素: " << front() << endl; // 10 37: 38: dequeue(); 39: cout << "dequeue 後前端元素: " << front() << endl; // 20 40: 41: cout << "Queue 是否為空: " << (empty() ? "是" : "否") << endl; 42: return 0; 43: }
- 使用 C++ STL 的 queue
1: #include <iostream> 2: #include <queue> 3: using namespace std; 4: 5: int main() { 6: queue<int> q; 7: q.push(10); 8: q.push(20); 9: q.push(30); 10: 11: cout << "前端: " << q.front() << endl; 12: cout << "尾端: " << q.back() << endl; 13: q.pop(); 14: cout << "pop 後前端: " << q.front() << endl; 15: cout << "大小: " << q.size() << endl; 16: return 0; 17: }
- Queue 的應用
- 作業系統的工作排程(CPU scheduling)
- 印表機列印佇列
- 廣度優先搜尋(BFS)
- 訊息傳遞系統
- 作業系統的工作排程(CPU scheduling)
7.4. Vector: 強化版的陣列
9. LeeCode
- 【C 語言的 LeetCode 五月挑戰】第六天 (Majority Element)
- brute force: for-for
- random
- sort
- hashtable
- divide and conquer
- brute force: for-for
- 【C 語言的 LeetCode 五月挑戰】第七天 (Cousins in Binary Tree)
- Binary tree
- coutn tree depth
- Binary tree
- 【C 語言的 LeetCode 五月挑戰】第八天 (Check If It Is a Straight Line)
- 【C 語言的 LeetCode 五月挑戰】第三天 (Ransom Note)
- 1d Array
- 宇元計數與比對
- 1d Array