C++[2]

目錄

目錄

複雜度

複雜

複雜度的用處

衡量一個程式的效率

有分時間效率與空間效率

怎麼衡量?

\(O()\)漸進符號

高一數學,方程式的廣域特徵

當我做一個三次方程式

把他拉遠

\(y = x^3\)

\(y = x^3+2x^2+3x+1\)

即使方程式不同,圖形大概的趨勢仍然是三次方程式

當 \(x\),也就是輸入的的資料量極大時

則整體趨勢幾乎只受到最大冪次 \(x^3\) 的影響

回到漸進符號

我們把剛剛方程式裡的 \(y\) 想成電腦的運算次數

\(x\) 想成輸入的資料量

假設我現在的程式,他的運算次數 \(y=x^3+20x^2+17x+101\)

當輸入的資料量極大時,運算次數增加的趨勢幾乎只受到\(x^3\)的影響

所以我們稱此程式的複雜度為 \(O(N^3)\)

漸進符號用的資料量通常以 \(N\) 表示

空間複雜度同理

如果運算次數與資料量無關呢?

則複雜度為 \(O(1)\),也就是常數複雜度

衡量完之後要幹嘛

當然就是要判斷你的程式會不會超時了

電腦的計算速度大約為每秒 \(10^9\) 次

拿出剛才的複雜度 \(O(N^3)\)

這時候當 \(N\) 大於 \(\sqrt[3]{10^9}=10^3\)時,這個程式通常無法在 1 秒內跑完

當然還要考慮一下常數的影響

所以這個 \(N\) 的上限一定會再低一點

衡量完之後要幹嘛

對於一個競賽題目中,輸入資料量的上限,至少需要多低複雜度的程式才能在 1~3 秒內跑完

補充:O、Θ、Ω

O(漸進上界),準確來說,算出來的複雜度是指此程式的最糟情況,如果一個程式對於不同的測資會有複雜度上的變動,這時以漸進上界表示出的複雜度會是最糟的那一個(複雜度上界)

如果需要給定一個較為精準的複雜度,而不是保守估計的上界時,就要使用 Θ(漸進精確界)

除了漸進上界外,當然還有漸進下界,也就是 Ω。

此符號表示出的複雜度是指此程式的最佳情況

舉個例子,線性搜尋的複雜度,以三種符號表示為:

\(\Omega(1)\), \(\Theta(n)\), \(O(n)\)

(\(\Theta(n/2) = \Theta(n)\))

上面三個複雜度都是以漸進法計算的

加油

STL

STL

是 C++ 內建的各種資料結構、演算法、迭代器......

反正很好用,也很常用

Vector

向量?

高級陣列

vector 與傳統陣列最大的不同,就是他可以隨時改變大小,有些題目用 vector 會好寫很多

宣告

vector<int>a(n);

宣告一個名稱為 a,大小為 n,值的資料型態為 int 的 vector

vector<int>a;

也可以不給大小

vector<int>a = {0, 1, 2, 3, 4};
vector<int>b(n, 0);

也可以先給定值

也可以初始化(歸零)

宣告

vector<vector<int>>a(n, vector<int(m, 0));

二維陣列,n*m 的大小

並歸零

常用語法

vector<int>a;
a.push_back(1);
a.push_back(3);
a.push_back(2);
//a = {1, 3, 2}
a.pop_back();
//a = {1, 3}

push_back:從陣列的尾端推一個元素進來

pop_back:把尾端的元素推出去

常用語法

vector<int>a;
    for(int i = 0 ; i < 19 ; i++) a.push_back(1);
cout<<a.size() << ' ' << a.capacity(); //19 32
return 0;

size:抓目前有使用到的空間大小

capacity:抓目前整個陣列(包含已擴充但未使用部分)

甚麼意思?

常用語法

vector<int>a;
a.push_back(1);
//size=1 capacity=1
a.push_back(1);
//size=2 capacity=2
a.push_back(1);
//size=3 capacity=4
a.push_back(1);
//size=4 capacity=4
a.push_back(1);
//size=5 capacity=8

每次push_back時,如果空間不夠,就會先把實際大小擴充成原本的兩倍

常用語法

vector<int>a;
auto bit = a.begin();
auto eit = a.end();

a.begin():陣列中第一個值的 iterator

a.end():陣列中最後一個值的 iterator

sort(a.begin(), a.end());
reverse(a.begin(), a.end());

可用於 sort, reverse, upper/lower bound 等地方

不好用的語法

vector<int>a(2, 3);
a.insert(1, 4);
//a = {3, 4, 3}

a.insert(位置, 數值);

在陣列中的一個位置插入一個數值

\(O(N)\),爛

vector<int>a(15, 3);
a.erase(a.begin() + 3, a.end());
//a = {3, 3, 3}
a.erase(a.begin());
a = {3, 3}

a.erase(iterator, iterator);

把陣列中的一段數值刪除

即使只有刪除一個數值,複雜度還是\(O(N)\),還是爛

題目

pair

一對

一次存兩個變數

一個 pair 有兩個變數,而且資料型態可以不同

像是我可以把一個人名配對一個分數,再對他做排序等動作,兩個被包在一起的元素就會一起移動

宣告

pair<string, int>a;

宣告一個包著 string 與 int 的 pair

pair<string, int>a("wut", 17);

可以初始化

vector<pair<int, int>>a;

還能包進其他 STL 裡

語法

pair<string, int>a("wut", 17);
a.first = "eee";
cout << a.first; //eee
cout << a.second; //17

a.first:pair 的第一個變數

a.second:pair 的第二個變數

注意,這兩個後面都不用加()

pair<string, int>a;
a = make_pair("idk", 123);

make_pair():生成一個 pair

遍歷

便利

python for

a = [1, 2, 4, 3, 5]
for i in a:
	print(i, end = ' ')
#1 2 4 3 5

如果你想要把整個陣列的數字一個一個輸出,在 python 中有這種寫法

注意這時候的 i ,他不像一般的 for 迴圈裡的 i,是一個 int而是跑到陣列裡面變成陣列裡的數值
稱為迭代器

要怎麼用 C++ 寫?

C++ for

vector<int> a = {1, 2, 3, 4, 5};
for (int i = 0 ; i < a.size() ; i++){
	cout << v[i] << ' ';
}
//1 2 3 4 5
vector<int> a = {1, 2, 3, 4, 5};
for(vector<int>::iterator it = a.begin() ; it != a.end() ; it++){
        cout << *it << " ";
}
//1 2 3 4 5

欸不是這太噁心了吧

還記得 auto 嗎

vector 有自己迭代器的資料型態

C++ for

vector<int> a = {1, 2, 3, 4, 5};
for(auto it = a.begin() ; it != a.end() ; it++){
        cout << *it << " ";
}
//1 2 3 4 5

還是沒有比原本的好寫啊?

vector<int> a = {1, 2, 3, 4, 5};
for(auto i : a){
        cout << i << " ";
}
//1 2 3 4 5

這很簡短了

基本上這跟 python 的寫法是很像的

a = [1, 2, 4, 3, 5]
for i in a:
	print(i, end = ' ')
#1 2 4 3 5

stack

一疊椅子

你們應該知道這種椅子

當你要拿走椅子的時候,都會是從最上面拿

當你要放椅子的時候,也是從最上面放

想一下現在有一張椅子,他是第一個放進去的

當你要拿椅子的時候,他是不是在最下面,最後一個才能拿到

這種先進後出(LIFO)的資料結構即為 stack

宣告

stack<int>st;

stack 無法初始化

常用語法

stack<int>st;
st.push(1);
st.push(3);
cout << st.top(); //3

st.push():像疊椅子那樣,把變數疊起來

st.top():獲取 stack 中最上面的那個元素

常用語法

stack<int>st;
cout << st.empty(); //true
cout << st.size(); //0
st.push(1);
st.pop();
cout << st.top(); //segment fault

st.empty():回傳一個布林值代表 stack 是否為空

st.size():回傳 stack 目前的大小

st.pop():把最上面的椅子(元素)抽出來丟掉

注意,當 stack 為空時,st.top() 會爆炸

注意事項

stack<int>st;
st.push(1);
cout << st[0]; //報錯

stack 無法用中括號索引取值

題目

queue

排隊

概念

想像一下你在排隊

越早到的人排得越前面

排得越前面的人能先拿到東西,然後離開隊伍

這種先進先出(FIFO)的資料結構即為 queue

宣告

就這樣

一樣無法初始化

queue<int>q;

常用語法

queue<int>q;
q.push(1);
q.push(2);
cout << q.front(); //1
q.pop();
cout << q.front(); //2
cout << q.size(); //1
cout << q.empty(); //false

q.push():丟一個元素進去排隊

q.front():回傳排隊排得最前面的元素

q.pop():把最前面的元素踢掉

q.size():回傳 queue 目前的大小

q.empty():回傳 queue 是否為空

注意事項

queue<int>q;
q.push(1);
cout << q[0]; //報錯

queue 也無法用中括號索引取值

題目

priority_queue

概念

還是想像你在排隊

但是年紀越大的排越前面

當有一個人排進來的時候,會依據自己的年齡插隊到正確的位置

宣告

priority_queue<int>pq;

字有點多,可能要記一下

常用語法

priority_queue<int>pq;
pq.push(1);
pq.push(2);
cout << pq.top(); //2
pq.pop();
cout << pq.top(); //1
pq.push(0);
cout << pq.top(); //1
cout << pq.empty(); //false
cout << pq.size(); //2

pq_push():把元素丟進隊伍裡

pq.top():獲取排得最前的值(就是最大值)

pq.pop():把最前面的值踢出去

pq.size():獲取當前 priority_queue 的大小

pq_empty():回傳當前貯列是否為空

注意事項

priority_queue<int>pq;
pq.push(1);
cout << pq[0]; //報錯

priority_queue 也無法用中括號索引取值

題目

list

還記得 insert 跟 erase 嗎

概念

在 Vector 中,一個元素裡存放一個值

而在 List 中,一個元素裡存放一個值、以及左右一格元素的指標

與 vector 相比,他不能直接使用索引值抓取元素

但是像 insert, erase 這種操作,它們單次操作的複雜度僅有\(O(1)\)

常見語法

l.push_front():從前面推入一個元素

l.pop_front():把最前面的元素拿出來丟掉

l.front():獲取第一個元素的值

l.back():獲取最後一個元素的值

其他的語法跟 vector 都是一樣的

list<int>l;
l.push_back(1);
l.push_back(3);
l.insert(1, 2);
cout << l.front(); //1
cout << l.back(); //3
l.push_front(0);
cout << l.front(); //0
l.pop_back();
l.pop_front();
//l = {1, 2}
cout << l.size() //2
cout << l.empty() //false
l.insert(1, 2);
l.erase(l.begin() + 1, l.end());
//l = {1}

迭代器

list<int>l(5);
for (auto &i : l) {
	cin >> i;
}
for (auto i : l) {
	cout << i << " ";
}

取值

list<int> l = {1, 2, 3, 4, 6};
auto it = l.begin();
advance(it, 4);
cout << *it;//6
list<int> l = {1, 2, 3, 4, 6};
cout << l[3];

剛剛說了不行

這個才行

但是advance() 是 O(n)

MAP

地圖

與 array 的不同之處

array 的 index 是固定的一串遞增的數字

而 map 可以指定一個 index,與其對應的值

index 也可以不是數字

map 語法

map<int, int>m;

宣告

第一個 int 為 index 的資料型態

第二個 int 為值的資料型態

map 語法

map<string, int>m;
m["ayo"] = 27;
cout << m["ayo"]; //27

index 資料型態為 string 的範例

要新增 index 的話不用額外操作

直接向第二行那樣就好

例題

TIOJ2428

喔對了這是這學期資訊能力競賽初賽的第一題

報寒訓!!

C++[2]

By 硼/Boron

C++[2]

  • 119