資料結構 &
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
By Arvin Liu
STL
Teaching Slide - stack/queue/list/vector & pass by ??
- 1,631