【個人筆記】C++ ZeroJudge 基礎題庫解題(純練習) - part 2 === a015.:https://zerojudge.tw/ShowProblem?problemid=a004 難度:★★★☆☆ 二維陣列、巢狀迴圈應用。 ```cpp= #include <iostream> #include <vector> using namespace std; int main() { int rows, cols; while (cin >> rows >> cols) { vector<vector<int>> matrix(rows, vector<int>(cols)); for (int i = 0; i < rows; ++i) { for (int j = 0; j < cols; ++j) { cin >> matrix[i][j]; } } for (int j = 0; j < cols; ++j) { for (int i = 0; i < rows; ++i) { cout << matrix[i][j] << (i == rows - 1 ? "\n" : " "); } } } return 0; } ``` vector 是個很好用的 data structure,可說是動態版的 array,在競程中也常使用到。 矩陣運算需要使用到二維的結構,所以於此便定義一個二維的 vector 結構出來。 一維 vector 定義:`vector <int> my_vector` 二維 vector 定義:`vector<vector<int>> my_vector` `vector<vector<int>> matrix(rows, vector<int>(cols));` 這行等效於如下: ```cpp= int matrix[rows][cols] = { {0,0,0,.......}, {0,0,0,.......}, {0,0,0,.......}, {0,0,0,.......}, ....... } ``` 表示建立一個 rows 列、cols 行的矩陣。 題目敘述當中有提示:`* 測資檔會包含多組矩陣資料`,所以要注意加上 `while (cin >> rows >> cols)` 的寫法。 ```cpp= for (int j = 0; j < cols; ++j) { for (int i = 0; i < rows; ++i) { cout << matrix[i][j] << (i == rows - 1 ? "\n" : " "); } } ``` 也可以從題目範例測資看到,輸出結果是務必要將 rows、cols 反過來,矩陣裡面的一串數字也要顛倒過來。 而在沒有使用 vector 的情況下,使用陣列需要設定一個固定值(看題目要求 rows、cols 大小去固定大小),於講求執行效率跟記憶體空間使用的「競程」來說,vector 顯然是最佳解(動態記憶體跟動態大小)。 而用到三元運算子是為了符合題目的輸出格式所用。 --- a017.:https://zerojudge.tw/ShowProblem?problemid=a017 難度:★★★★★ 運算子優先級、演算法、資料結構、字串處理 ```cpp= #include <iostream> #include <sstream> #include <stack> #include <vector> #include <unordered_map> using namespace std; // 運算子優先級表 unordered_map<char, int> precedence = {{'+', 1}, {'-', 1}, {'*', 2}, {'/', 2}, {'%', 2}}; // 檢查是否為運算子 bool isOperator(char c) { return precedence.find(c) != precedence.end(); // std::find() 尋找 key 有無在雜湊表內 // 有 -> 回傳 iterator, 無 -> 回傳 precedence.end() } // 中序轉後序(RPN:Reverse Polish Notation)、Shunting-yard 演算法 vector<string> infixToPostfix(const vector<string>& tokens) { vector<string> output; // 建立 output vector 容器 stack<char> operators; // 建立 operators 堆疊容器 for (const string& token : tokens) { // range-based for loop if (isdigit(token[0])) { // 數字直接輸出 output.push_back(token); // push_back(), 在尾端插入元素(vector用) } else if (token == "(") { // 左括號入 stack operators.push('('); } else if (token == ")") { // 右括號則彈出直到左括號 while (!operators.empty() && operators.top() != '(') { output.push_back(string(1, operators.top())); operators.pop(); } operators.pop(); // 移除左括號 } else if (isOperator(token[0])) { // 運算子 while (!operators.empty() && isOperator(operators.top()) && precedence[operators.top()] >= precedence[token[0]]) { output.push_back(string(1, operators.top())); operators.pop(); } operators.push(token[0]); } } while (!operators.empty()) { // 剩餘運算子移出 stack output.push_back(string(1, operators.top())); operators.pop(); } return output; } // 計算後序表示法的值 int evaluatePostfix(const vector<string>& tokens) { stack<int> values; for (const string& token : tokens) { if (isdigit(token[0])) { values.push(stoi(token)); } else { int b = values.top(); values.pop(); int a = values.top(); values.pop(); if (token == "+") values.push(a + b); else if (token == "-") values.push(a - b); else if (token == "*") values.push(a * b); else if (token == "/") values.push(a / b); else if (token == "%") values.push(a % b); } } return values.top(); } int main() { string line; while (getline(cin, line)) { stringstream ss(line); string token; vector<string> tokens; while (ss >> token) { tokens.push_back(token); } vector<string> postfix = infixToPostfix(tokens); int result = evaluatePostfix(postfix); cout << result << endl; } return 0; } ``` 此題用到了所謂的「(後綴、後序)逆波蘭表示法(Reverse Polish Notation, RPN)」、「排程場演算法(Shunting-yard)」。 至於函式庫,則為: 1. 「`#include <sstream>`」:字串流 2. 「`#include <stack>`」:堆疊,用於運算子優先順序與後序計算 3. 「`#include <unordered_map>`」:雜湊表,用於運算子優先順序查找 :::success 逆波蘭表示法(Reverse Polish Notation, RPN):所有運算子置於運算元的後面。 例如:a+b 變成 ab+。 a+b 稱為中序,使用此表示法將其轉為後序 ab+。 以下是 RPN 在程式設計上的做法(即使用到排程場演算法(Shunting-yard)): 1. 由左至右讀取運算式中的字元 2. 若為運算元, 則複製到後序運算式字串中 3. 若為左括號, 則 push 至 stack 中 4. 若為右括號, 從 stack 中 pop 字元至後綴表達式, 直到遇到左括號, 然後把左括號 pop 出來 5. 若為運算子, 且若此時 stack top 的運算子優先級 ≥ 此運算子, 彈出 stack top 的運算子到後綴表達式, 直到發現優先級更低的元素位置, 把運算子 push 至 stack 6. 讀到輸入的尾端, 將 stack 元素 pop 出來直到該 stack 為 empty, 將符號寫入後序運算式中 參照:https://clu.gitbook.io/data-structure-note/stack-reverse-polish-notation 排程場演算法是專門用於將中序轉後序的演算法。 ::: --- `#include <unordered_map>` 是一種針對雜湊表的 key-value 容器。(STL 標準函式庫) > 與 `std::map` 不同,`unordered_map` 不保證元素的排序,但通常提供更快的查找速度(時間複雜度O(1))。 map:有序;unordered_map:無序(其實從英文上就看得出來了:unordered)。 以下是基礎語法: ```cpp= #include <unordered_map> std::unordered_map<key_type, value_type> map_name; ``` key_type:鍵的資料型態。 value_type:值的資料型態。 map_name:自定義名稱。 要初始化一個 unordered_map,如下: ```cpp= std::unordered_map<std::string, int> umap = { {"Tom", 1}, {"Ann", 4}, {"Jack", 2} }; ``` 插入元素: ```cpp= myMap.insert({3, "three"}); ``` or ```cpp= myMap[1] = "one"; // 會直接覆蓋 key = 1 的值 ``` 存取元素: ```cpp= std::string value = myMap[1]; // 獲取鍵為 1 的值 ``` 刪除元素: ```cpp= umap.erase(umap.begin()); // 刪除開頭的元素 ``` ```cpp= myMap.erase(1); // 刪除鍵為 1 的元素 ``` ```cpp= umap.erase(umap.find("John"), umap.end()); // 刪除從某元素開始至尾端的元素 ``` 搜尋元素: ```cpp= auto it = myMap.find(2); // 尋找鍵為 2 的元素 if (it != myMap.end()) { std::cout << "Found: " << it->second << std::endl; } ``` --- > `<sstream>` 允許你將字串當作 "輸入 / 輸出流" 來使用,這使得從字串中讀取資料或將資料寫入字串變得非常簡單。 sstream 是 C++ STL 中其中一個的命名空間,包含幾個 class,如下: * istringstream:用於從字串中讀取資料。 * ostringstream:用於將資料寫入字串。 * stringstream:是 istringstream 和 ostringstream 的組合,可以同時進行讀取和寫入運算。 基本語法: ```cpp= #include <sstream> // 使用 istringstream std::istringstream iss("some data"); // 使用 ostringstream std::ostringstream oss; // 使用 stringstream std::stringstream ss; ``` 具體應用大致如下: 1. 從字串讀取資料:如從字串中讀取整數、浮點數等。(`iss >> num1 >> num2`) 2. 向字串寫入資料 3. 使用 stringstream 進行讀寫運算 :::success 反正就是把: 1. istringstream 看成輸入流 ">>" 2. ostringstream 看成輸出流 "<<" ::: 但是通常用 `std::stringstream ss;` 因為他兩者具備。 具體範例可至:https://hackmd.io/@Maxlight/rJwlvj8ad --- 接下來是最後一個函式庫 `<stack>`: 同樣也是 C++ STL 的一部分,也實現了後進先出(LIFO,Last In First Out)的資料結構,對嘛,堆疊的性質就是如此。 以下是 stack 的基本運算: * push(): 在堆疊頂端增加一个元素。 * pop(): 移除堆疊頂端的元素。 * top(): 回傳堆疊頂端元素的參考,但不移除它。 * empty(): 檢查堆疊是否為空。 * size(): 回傳堆疊中元素的數量。 要建立一個堆疊: ```cpp= std::stack<int> s; ``` --- 參考資料 === [C++ 容器类 <unordered_map> | 菜鸟教程](https://www.runoob.com/cplusplus/cpp-libs-unordered_map.html) [C++ std::unordered_map 用法與範例 | ShengYu Talk](https://shengyu7697.github.io/std-unordered_map/) [C++ std::stack 用法與範例 | ShengYu Talk](https://shengyu7697.github.io/std-stack/) [C++ 容器类 <stack> | 菜鸟教程](https://www.runoob.com/cplusplus/cpp-libs-stack.html)