【個人筆記】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)