資料結構 &
STL Container
記得用Esc看看有什麼東西喔!

Special Thanks:
bookgin
資料結構是什麼?
如何去存一個資料
你想怎麼存OOXX?

- 「啊,好像棋盤似的,用二維 a[3][3] 陣列」
- 「我看倒有點像稿紙,用一維 a[9] 就夠了」
- 「真像一塊塊綠豆糕,不如用整數的九個位數來存。」
如何存資料是你的事。
- 每個題目你都可以用隨便一個資料結構來實做。
- 但是可能時間複雜度(所花的時間)就不一樣了。

Array & Linked List

Photo Credit: 112CS 蔡欣穆教授的DSA
Queue
什麼是Queue?

像排隊一樣,先進先出。
後來的人塞屁股。
先進先出的專有名詞:FIFO (First In First Out)
std::queue用法
函式庫 | #include <queue> |
---|---|
宣告 | std::queue<T> Q; |
enqueue(放屁股) | Q.push(T); |
dequeue(拿頭) | Q.pop(); |
看前面是誰 | Q.front(); |
現在裡面有幾個? | Q.size(); |
現在是不是空的? | Q.empty() |
T可以帶入所有型態,包括你自己寫的struct。
以上全O(1)操作。
std::queue 例子
#include <iostream> #include <queue> using namespace std; struct crood{ int x,y; crood(int _x,int _y){ x = _x, y = _y; } }; int main(){ queue<crood> Q; Q.push(crood(1,1)); Q.push(crood(3,3)); cout << Q.front().x << endl; Q.pop(); cout << Q.front().x << endl; } // 輸出 1 3
Queue 練習!
Stack
什麼是Stack?
像你的衣櫃,被早被塞進去的都會忘記(?)
先進先出的專有名詞:LIFO (Last In First Out)

std::stack用法
函式庫 | #include <stack> |
---|---|
宣告 | std::stack<T> S; |
enqueue(放屁股) | S.push(T); |
dequeue(拿頭) | S.pop(); |
看屁股是誰 | S.top(); |
現在裡面有幾個? | S.size(); |
現在是不是空的? | S.empty() |
T可以帶入所有型態,包括你自己寫的struct。
以上全O(1)操作。
std::stack 例子
#include <iostream> #include <stack> using namespace std; struct crood{ int x,y; crood(int _x,int _y){ x = _x, y = _y; } }; int main(){ stack<crood> S; S.push(crood(1,1)); S.push(crood(3,3)); cout << S.top().x << endl; S.pop(); cout << S.top().x << endl; } // 輸出 3 1
Stack 練習!
Challenge - 1
Deque
有沒有一種天方夜譚...
有stack和queue的功能,
但是時間複雜度都是O(1)?
Stack & Queue...

什麼是Deque?
就是同時有stack和queue的資料結構!
它唸作Deck,不唸De-queue

std::deque用法
函式庫 | #include <deque> |
---|---|
宣告 | std::deque<T> D; |
放頭 | D.push_front(T); |
放屁股 | D.push_back(T); |
拿頭 | D.pop_front(T); |
拿屁股 | D.pop_back(); |
看頭是誰 | D.front(); |
看屁股是誰 | D.back(); |
現在是不是空的? | D.empty(); |
std::deque 例子
#include <iostream> #include <deque> using namespace std; struct crood{ int x,y; crood(int _x,int _y){ x = _x, y = _y; } }; int main(){ deque<crood> D; D.push_back(crood(1,1)); D.push_back(crood(2,2)); D.push_front(crood(3,3)); cout << D.front().x << endl; D.pop_front(); cout << D.front().x << endl; D.pop_front(); cout << D.front().x << endl; } // 輸出 3 1 2
Challenge - 2
Vector
會不會有一天...
題目請你存一個數列,但是卻步知道這個數列最多有幾個?
那該怎麼辦呢?
直接開到最大也不是太好?
Dynamic Array

當你用到2^k個的時候,就幫你擴充到2^(k+1),也就是兩倍。
-> 空間複雜度O(n),不會浪費辣麼多
STL的vector就是一種Dynamic Array!
std::vector用法
函式庫 | #include <vector> |
---|---|
一般宣告 先宣告N個東西 先宣告N個c的東西 |
std::vector<T> V; std::vector<T> V(N); std::vector<T> V(N,c); |
enqueue(放屁股) | V.push_back(T); |
dequeue(拿屁股) | V.pop_back(); |
清空整個vector | V.clear(); |
現在裡面有幾個? | V.size(); |
現在是不是空的? | V.empty(); |
在vector的iter插入x | V.insert(iter,x); |
在iter刪除東西 | V.erase(iter); |
存取第k個東西 | V[k] |
開頭的iterator | V.begin() |
結尾的iterator(指向最後一個元素+1) | V.end() |
std::vector 例子
#include <iostream> #include <vector> using namespace std; int main(){ vector<int> V(5,-1); for(int i=0;i<5;i++) V.push_back(i); V[2] = -10; V.pop_back(); for(int i=0;i<V.size();i++) cout << V[i] << " "; } // 輸出-1 -1 -10 -1 -1 0 1 2 3
有沒有發現一件事情?
好像vector可以代替stack也...
沒錯,
所以你可以乾脆直接用vector。
通常會寫stack都是為了讓自己知道自己要用的是stack而已啦~
Array & Vector & Linked List

Photo Credit: 112CS 蔡欣穆教授的DSA
等等,
是不是漏講了什麼?
erase和insert呢?
Iterator
Iterator (迭代器)
- 跟指標87%像
- 基本上你可以當作是STL的專用指標。
- 不要管怎麼實做的,你會豆頁痛。
iterator的各種操作
假設宣告了 ... | vector<int> V | int ary[N] |
---|---|---|
拿到一個container的開頭iter/ptr | V.begin() | ary |
拿到一個container的結尾iter/ptr | V.end() | ary+N |
這一個iterator指到的東西是什麼? | *(iter) | *ptr |
下一個iterator是誰? | iter + 1 | ptr + 1 |
讓它變成下一個iterator | iter++ | ptr ++ |
iterator/ptr的型態? | vector<int>::iterator | ptr |
一個簡單的圖例

- 所以V.end()是不能夠 *的,因為它沒有東西
- 跟int ary[N]的ary[N]沒有東西是一樣的!
所以那個insert和erase...
在vector的iter插入x | V.insert(iter,x); |
---|---|
在iter刪除東西 | V.erase(iter); |
在vector的地k個插入x | V.insert(V.begin()+k,x); |
---|---|
在vector的第k個刪除 | V.erase(V.begin()+k); |
配合iterator就可以寫成...
不能直接給數字喔!會CE
std::vector::iterator 例子
#include <iostream> #include <vector> using namespace std; int main(){ vector<int> V(5,-1); for(int i=0;i<5;i++) V.push_back(i); vector<int>::iterator iter; V.insert(V.begin()+1, -10); V.erase(V.begin()); for(iter = V.begin(); iter != V.end() ; iter++) cout << *(iter) << " "; cout << endl; } // 輸出-10 -1 -1 -1 -1 0 1 2 3 4
一些iterator的警告
我們不知道
STL怎麼實做這些資料結構的。
所以一個vector被你push_back一個東西後,可能STL就會幫你換個位置!
例如
#include <iostream> #include <vector> using namespace std; int main(){ vector<int> V(1),V2(1024,77); vector<int>::iterator iter=V.begin()+2047; for(int i=0;i<=2048;i++) V.push_back(i); cout << *iter << endl; }
你會發現答案有可能不是2046,而是一個奇怪的數字,甚至RE。
所以...
要是改到container的話,
最好要小心iterator的位置。
題外話: auto
std::vector<int>::iterator
也太長吧! 有什麼簡寫呢?
auto
自動偵測型態並且幫你填補。
假設宣告了 ... | auto會幫你填.... |
---|---|
auto i = 0; | int |
auto s = "aoa"; | char * |
auto d = 0.0; | double |
auto c = 'X'; | char |
auto iter = std::vector<int>.begin() | std::vector<int>::iterator |
auto iter = std::list<int>.begin() | std::vector<list>::iterator |
auto的各種預測
題外話: for-each
如果你哪一天需要這樣寫...
vector<int> V(5,-1); std::vector<int>::iterator iter; for(iter = V.begin(); iter != V.end() ; iter++){ int i = *(iter); // 做一些事情 }
vector<int> V(5,-1); for( int i : V){ // 做一些事情 }
你可以簡寫成這樣
簡單來說
for-each就是幫你從container把一個一個東西丟到迴圈。
for( auto i : container ){ // 做一些事情 }
所以對於所有container可以寫成這樣
List
List其實就是
Doubly Linked List!
std::list用法
函式庫 | #include <list> |
---|---|
宣告 | std::list<T> L; |
放頭 | L.push_front(T); |
放屁股 | L.push_back(T); |
拿頭 | L.pop_front(T); |
拿屁股 | L.pop_back(); |
頭的iterator | L.begin(); |
結尾的iterator (最後一個+1) | L.end(); |
iterator指向下一個 | iter ++ ; **list的iter不能+=1** |
iterator指到上一個 | iter -- ; |
在iter插入x | L.insert(iter,x); |
在iter刪除x | L.erase(iter); |
你可以想成其實List的功能
就是vector + deque !
有vector的insert/erase 和
deque 的雙重push/pop
但注意list::iter不能夠iter+k。
std::list 例子
#include <iostream> #include <list> using namespace std; int main(){ list<int> L; L.push_back(1); // front (1) - end L.push_front(0); // front (0) - 1 - end L.push_back(3); // front (0) - 1 - 3 - end list<int>::iterator iter; iter = L.begin(); // front/iter (0) - 1 - 3 - end cout << *iter << endl; L.insert(iter,5); // front(5) - iter(0) - 1 - 3 - end iter++; // front(5) - 0 - iter(1) - 3 - end L.erase(iter); // front(5) - 0 - 3 - end for(auto i: L) cout << i << " "; cout << endl; } // 輸出 5 0 3
小小總結
整理表
名稱 | 拿front |
拿end | 放front | 放end | 看front | 看end |
---|---|---|---|---|---|---|
stack | S.pop() | S.push() | S.top() | |||
queue | Q.pop() |
Q.push() | Q.front() | |||
deque | Q.pop_front() |
Q.pop_back() | Q.push_front() | Q.push_back() | Q.front() | Q.back() |
vector | Q.pop_back() | Q.push_back() | Q.front() | Q.back() | ||
list | Q.pop_front() |
Q.pop_back() | Q.push_front() | Q.push_back() |
名稱 | empty() | size() | clear() | 開頭iter: begin() | 結尾iter: end() |
---|---|---|---|---|---|
stack | O | O | X | 沒有iterator | 沒有iterator |
queue | O | O | X | 沒有iterator | 沒有iterator |
deque | O | O | O | O | O |
vector | O | O | O | O | O |
list | O | O | O | O | O |
結語

- 等價交換 -
- 等價交換 -
雖然看起來list什麼事情都做的到,但是代價就是速度很慢。
相對的,vector就快很多。
因此對一個情境找到一個最適當的資料結構才是最好的。
去寫
Challenge / HW
囉:D
* Challenge 1難度 > Challenge 2難度 >> HW難度
Appendix
std::pair
可以一次裝兩個東西的container
宣告: std::pair<int,int>
做出一個pair:std::make_pair(a,b);
Challenge 1 - Solution
1. 對於每位勇者能夠"往前"溝通的勇者,
必是非嚴格遞減序列。 (否則就會被阻斷)
這個要用stack來做。
2. 假設一個勇者等級為k,
他可以往前溝通到所有等級<=k的勇者。
但是小於k的勇者們這次溝通完就用不到了。因為這個勇者會阻斷他根後面勇者的溝通
3. 假設一個勇者等級為k,
他最多只能夠溝通到一位等級大於k的勇者
這是一個特判。
Challenge 1 - Solution Code
#include <iostream> #include <stack> using namespace std; int T,n,x; int main(){ cin >> T; while(T--){ long long ans = 0; scanf("%d",&n); stack<pair<int,int> > S; while(n--){ scanf("%d",&x); while(!S.empty() && S.top().first < x) ans+=S.top().second,S.pop(); int tmp = 0; if(!S.empty() && S.top().first == x){ ans += tmp = S.top().second; S.pop(); } if(!S.empty()) ans++; S.push(make_pair(x,tmp+1)); } cout << ans << endl; } return 0; }
Challenge 2 - Solution
1. 預先處理好所有答案。不要問了再做。
2. 每算完一個隊伍,都會新增一個人,砍掉一個人。這個稱為Sliding Window
假如現在做[0,3],那麼下一個就是[1,4],再下一個就是[2,5]
3. 如果現在是[i,i+k-1],發現第i+k的B數比i+k-1的B數更低,我們可以把i+k-1拿掉。因為他始終不會是答案。
所以你的container會是一個非嚴格遞增數列。 -> 答案都會是deque.front()
4. 綜合2跟3,要pop_front pop_back & push_back,
所以要用deque。
Challenge 2 - Solution Code
#include <iostream> #include <deque> using namespace std; int ans[10000000]; int main(){ deque<pair<int,int> > D; int n,k,q,i,x; cin >> n >> k; cin >> x; D.push_back(make_pair(x,0)); for(int i=1;i<n;i++){ cin >> x; while(!D.empty() && D.back().first > x) D.pop_back(); D.push_back(make_pair(x,i)); if(D.front().second == i-k) D.pop_front(); ans[i] = D.front().first; } scanf("%d",&q); while(q--){ cin >> i; cout << ans[i+k-1] << endl; } }
資料結構 & STL Container
STL
By Arvin Liu
STL
Teaching Slide - stack/queue/list/vector & pass by ??
- 1,695