###### tags: `MDCPP講義` # 基礎資料結構(2) **Author : 謝侑哲** --- ## 上周題目講解 --- [Parentheses Balance](http://mdcpp.mingdao.edu.tw/problem/B005) ---- 分析 : - 當兩個括號互補時,就保證其可以一定合法,不會受其他因素影響 - 他是一個層層相疊的結構,外面的括號會被裡面的括號影響 ---- 思路: - 既然只要括號互補就必定合法,且不受影響,那當其合法,就可以不繼續處理 - 外面的括號會被裡面的括號影響,那能不能把裡面的括號去掉 - 當我們從左看到右來做,最先合法的括號就會是最內層的括號 ---- 解法: 利用stack的特性,從左往右掃,當她出現第一組合法括號時,將其丟掉,一路做到底,如果有剩下的東西,及代表他不合法 ---- 對應思路: - 互補必定合法,不被影響 $\rightarrow$ 可以直接丟掉 - 目標去掉裡面的括號 $\rightarrow$ 利用pop的特性,將其排除 - 最先合法的就是最內層 $\rightarrow$ stack的特性,可以將最後近來的東西丟掉,也就是遇到的最先合法的括號 ---- 圖解: ![](https://i.imgur.com/CDZyjNK.png) ---- ![](https://i.imgur.com/KbQpgdx.png) ---- ![](https://i.imgur.com/6QMZIMG.png) ---- ![](https://i.imgur.com/nO4Ixrx.png) ---- ![](https://i.imgur.com/g1sAwX4.png) ---- ![](https://i.imgur.com/P9Hdllp.png) ---- ![](https://i.imgur.com/m6WlIUn.png) ---- ![](https://i.imgur.com/3YS0J7C.png) ---- 為了不讓你們抄,我已經竄改過程式碼了 ```cpp= #include <bits/stdc++.h> using namespace std; int main () { int n; cin >> n; string c; getline(cin,c); //吃掉多的\n for(int j = 0;j < n;j++){ int o = 0; //判斷後面是否輸出 stack<cr> st; string s; getline(cin , s); // 讀入字串 for(int i = 0;i < s.size();i++){ if(s[j] == '(' || s[i] == '['){ st.push(s[i]); // 如果他是左括號就丟進stack }else if(st.empty()){ if(o == 0) // 當她是右括號且stack空時,必定找不到對應的左括號,所以錯 cout << "No\n"; o = 1; //避免多輸出一次 }else if(st.top() == '(' && s[i] == ')'){ st.pop(); //當他符合()的形式,pop掉,注意這裡只丟掉一個是因為另一個根本沒被丟進來 }else if(st.top() == '[' && s[i] == ']'){ st.pop(); //符合[]形式,pop掉 }else if(o == 0){ cout << "No\n"; // 當字串出現奇怪東西或(]、[)的時候,判斷為錯 o = 1; } } if(o == 0){ //最後判斷stack是不是空的,空的就合法 if(st.empty()) cout << "Yes\n"; else{ cout <<< "No\n"; } } } } ``` --- [智乃還債計畫](http://mdcpp.mingdao.edu.tw/problem/T006) ---- 分析: - 要求在"連續"城鎮中打工 - 要求打工的城鎮數最少,但不限制總數 - 有一個最低的賺錢底線 ---- 思路: - 要求在連續城鎮中打工,所以可以利用先進先出的資料結構,因為這樣裡面可以保持連續 - 求打工的城鎮可以利用資料結構的長度來計算 - 有一個最低的賺錢底線,所以當底線符合時,可以做一些處理 ---- 解法: 利用queue的特性,將資料結構裡的城鎮保持連續。 接者一個一個丟進來,當資料結構裡的城鎮能賺到的錢大於底線時,用size()函式判斷他的長度是否小於目前找到的最短長度,如果是,就更新他。 接者把城鎮從前面丟出去,如果仍符合,代表比剛剛找到的長度短,再判斷是否更新 直到賺的錢再次比底線少時,繼續丟城鎮進來 ---- 對應思路: - 用先進先出的結構維護 $\rightarrow$ 用queue - 用資料結構長度來算城鎮樹 $\rightarrow$ 用size()函式 - 當超過底線做處理 $\rightarrow$ 在長度大於底線時,進行更新,並開始丟東西到小於底線 ---- 圖解 ![](https://i.imgur.com/oLp5Ocb.png) ---- ![](https://i.imgur.com/9FonF13.png) ---- ![](https://i.imgur.com/Z0NZ5cj.png) ---- ![](https://i.imgur.com/hJXeYB9.png) ---- ![](https://i.imgur.com/8dytKIH.png) ---- ![](https://i.imgur.com/r2DAC5k.png) ---- ![](https://i.imgur.com/6IKpdbT.png) ---- ![](https://i.imgur.com/241g5QJ.png) ---- ![](https://i.imgur.com/uKR8K35.png) ---- ![](https://i.imgur.com/5WasSuP.png) ---- ![](https://i.imgur.com/KTVPJdx.png) ---- ![](https://i.imgur.com/gyXwES8.png) ---- ![](https://i.imgur.com/crfmwHZ.png) ---- ![](https://i.imgur.com/Mxh9Iy8.png) ---- ```cpp= #include<bits/stdc++.h> using namespace std; const int maxn = 50; int x[maxn]; queue<int> q; int main(){ int n, k; cin >> n >> k; // 城鎮數&底線錢 for(int i = 0; i < n; i --) cin >> x[i]; int sum = 0, ans = 1e9; //把 ans 開超大,以找最小 for(int i = 0; i < n; i --){ sum += x[i]; // 紀錄城鎮加起來的錢數 q.push(x[i]); // 將城鎮放入queue if(sum >= k) ans = min(ans, (int)q.size()); // 如果超過底線錢,判斷長度是否比上個長度短 sum -= q.front(); q.pop(); while(sum>= k){ //當出去之後還比底線大 ans = min(ans, (int)q.size()); //再判斷 sum -= q.front(); //重複繼續丟 q.pop(); } } cout << ans; } ``` --- 更多STL容器 --- ## set ---- 他是一個可以自動排序的資料結構 如果你丟給她67428 它會讓他變成24678 但他有一個特性是,只能儲存不同的數字 像是給他695655 他會存成569,多的數字就被排掉了 ---- 關於它的原理 因為有些東西還沒教到了,所以我暫時不打算講 不過可以提幾個關於他原理的詞 ---- - 圖的遍歷 - 中序走訪 - 二元搜尋樹 - 紅黑樹 ...... 聽起來超複雜的對吧,那就有機會再講,或是有興趣的可以再來問講師 ---- 所以先學會怎麼用就好 ---- 宣告: ```cpp= #include<set> using namespace std; set<int> s; ``` 就跟之前一樣 ---- - begin() 回傳遞第一個值的迭代器 - rbegin() 回傳最後一個值的迭代器 - *begin() 回傳遞第一個值 - *rbegin() 回傳最後一個值 如果不知道迭代器是甚麼,可以先把他想成是一個資料的地址,後面我再講 後面兩個\*號就是間接尋址運算子,簡單來說就是可以取得某個地址的值 ```cpp= #include<set> using namespace std; set<int> s; s.begin(); // pointer s.rbegin(); // pointer *s.begin(); *s.rbegin(); ``` ---- find(DT value) 找出某個值在set中的迭代器 基本上跟begin()和end()差不多 值得注意的是,如果find不在set裡面的東西,他會回傳end()的值 ```cpp= #include<set> using namespace std; set<int> s; s.find(10); ``` ---- insert(DT value) 插入東西(因為它會自動排序,就沒有從哪裡插的差別了) 時間複雜度 O(logN) 至於為甚麼,你們之後學到二分搜就會懂了 前面說過,他不能存同樣值,所以如果你插了5,再插一個5就不會發生任何事 ```cpp= #include<set> using namespace std; set<int> s; s.insert(5); ``` ---- erase(DT value) 把東西刪掉 複雜度O(logN) 用這要注意,如果你刪掉不屬於set的東西,他可能會爆掉 在judge上會顯示RE ```cpp= #include<set> using namespace std; set<int> s; s.erase(5); ``` ---- 那要怎麼處理刪到不存在的東西呢? ---- 可以利用find的特性,當find找不到東西就會回傳end()的值 所以當s.find()==s.end(),就代表東西不存在 這時候就不要做erase()操作 ```cpp= if(s.find(5) != s.end()) s.erase(5); ``` ---- 剩下的一些東西 size() 就找大小 set<DT, greater<DT\> > 可以讓set從小到大變大到小 ```cpp= s.size() set<int,greater<int>> s; ``` ---- 補充: 前面提到set不能存重複元素 但C++有個multiset可以做到,用法就跟set幾乎一樣 ---- 練習題: [先到先服務](http://mdcpp.mingdao.edu.tw/problem/AP013) --- 迭代器 ---- 就像前面set說的,他是一個存地址的東西 我們稱它為指標 然後他是用來遍歷容器的工具 ---- 另外他也是一種資料型別 宣告方法如下 ```cpp= vector<int> :: iterator vit; //宣告一個 vector<int>的迭代器 set<int> :: iterator sit; //宣告一個 set<int>的迭代器 // 用v、s + it 當名字是因為我們通常稱取迭代器前兩個字it ``` ---- 剛剛那個超長的宣告很難記住吧 所以我們都用C++11以上提供的auto 他可以自動幫你判斷資料的型別是甚麼 如果你用過python或javascript的var 他的功能就類似 ```cpp= auto sit = s.begin(); // sit 會被判斷為 set<int> :: iterator ``` --- map ---- map就是一個讓你可以給一個值索引,並查詢他的結構 然後它的原理也是紅黑樹,所以不講 ---- 直接進用法 ---- 宣告 ```cpp= #include<map> using namespace std; map<int,int > m; m[0] = 1; ``` 可以發現裡面有兩個值,前面的叫key,後面的叫value key就是他的索引 以範例中m[0]=1,0就是key,1就是value ---- 更多宣告 map裡面的資料型別常常會被放很多元的東西 像是字串、字元、pair等等 ```cpp= map<int,int> mii; map<string,int> msi; map<int,pair<int,int>> mp; ``` 基本上想的到都可以放 ---- 補充: 怕有人不知道pair,他是一個可以存兩個值的資料型別 ```cpp= pair<int ,int > pii; pii.first = 1; //第一個值 pii.second = 2; //第二個值 ``` map 存的東西就是pair ---- map基本操作 加入元素: 因為跟set一樣是紅黑樹,所以複雜度是O(logN) 有兩種做法 ---- 作法一 直接在對應的點宣告他的值 ```cpp= map<string,int> mp; mp["abc"] = 12; mp["bca"] = 16; mp["bca"]++; // mp["bca"] = 17 ``` ---- 作法二 利用insert函式 ```cpp= map<string,int> mp; mp.insert( pair<string,int>("aaa",1) ); mp.insert({"aaa",1}); mp.insert(make_pair("aaa",1)); //三的東西其實一樣,只是換了造出pair的方式 ``` 有時候作法一會莫名掛掉,掛掉的時候就可以換成作法二 ---- erase( DT value ) 把對應key的值刪掉 ```cpp= map<string,int> mp; mp["abc"] = 12; s.erase("abc"); ``` 複雜度也是O(logN); ---- 查找元素 find(DT value) 找元素的迭代器 你們可能會想,直接中括號裡面放key不就好了 但這樣不是最好的做法 ---- ```cpp= mp["loli"] = 12; if(mp.find("loli") != mp.end()) cout << mp["loli"] << '\n'; //建議用法 優點 時間較快 不會增加多餘記憶體 if(mp["loli"]) cout << mp["loli"] << '\n'; //不建議用法 缺點如果查找元素不再map中 //會多開出記憶體並把值設為0且時間較慢 ``` ---- 清空 ```cpp= mp.clear(); ``` ---- 迴圈遍歷map ```cpp= for(auto i : mp) //這是遍歷STL容器中所有元素的做法,在vector之類的也可以用 cout << i.first << ' ' << i.second << '\n'; ``` ---- 練習題: [2-SET](http://mdcpp.mingdao.edu.tw/problem/B033)
{"metaMigratedAt":"2023-06-16T13:05:23.562Z","metaMigratedFrom":"Content","title":"基礎資料結構(2)","breaks":true,"contributors":"[{\"id\":\"f547d745-63f3-4bad-986b-1751eeec19d1\",\"add\":7245,\"del\":250},{\"id\":\"6a375517-4167-4b7c-a983-1e595a29262c\",\"add\":24,\"del\":62}]","description":"Author : 謝侑哲"}
    733 views
   Owned this note