最基礎的資料結構大概就是陣列了,相信大家也都會使用。
STL 的方便之處是它提供了一個動態型的陣列:vector
,它可以像一般的陣列一樣操作,並且可以隨時更動陣列大小以配合內部元素數量的增減。
我們可以用以下方式來宣告 vector
:
vector<int> vecA;
// 宣告一個元素型別為 int 的空 vector
vector<int> vecB(10);
// 宣告一個長度為 10 的vector,初始值為 0
vector<int> vecC(10,2);
// 宣告一個長度為 10 的 vector,初始值為 2
vector<int> vecD = {1, 2, 3, 4, 5}; // 賦予初始值
vector<int> vecE {1, 2, 3, 4, 5}; // 也可以這樣賦予初值
< >
中的資料型別可以替換,代表陣列中元素的型別。
vector
的 push_back
函數可以將元素新增到 vector
的最尾端;pop_back
函數則刪除最尾端的元素。
vector<int> vec;
vec.push_back(1); // [1]
vec.push_back(4); // [1,4]
vec.pop_back(); // [1];
vector
與一般陣列一樣,可以用 operator[]
進行查找元素。另外也可以透過 STL 內建的 at
函數與 back
函數來進行元素的查找。
cout << v[0] << endl;
// 印出 vector 的第 0 個元素
cout << v.at(0) << endl;
// 也可以這樣
cout << v.back() << endl;
// 印出 vector 尾端的元素值
尋訪 vector
裡的每個元素可以用更簡短的寫法:
for (auto &x : vec) {
cout << x << endl;
}
size
函數回傳有多少元素在這個 vector
裡面。
for (int i = 0; i < vec.size(); i++)
cout << vec[i] << endl;
size()
函數的回傳值是 unsigned
,所以如果寫 for (int i = 0; i < vec.size() - 1; i++)
的迴圈的話,要注意 vec
的大小是否可能是 0
,否則 vec.size() - 1
會變成 $2^{32}-1$ 或是 $2^{64}-1$,導致迴圈跑不完。
建議在這種狀況時轉型成 int
,寫成 (int)vec.size() - 1
。
還記得指標(pointer)嗎?他會指向一個變數所在的位置,並且在陣列裡面可以用來指向陣列的某一項。vector
也有類似指標的東西,他的名字是迭代器(Iterator)。顧名思義,迭代器就是一個能枚舉資料結構的資料的物件。讓我們來看他實際的使用方法吧!
下面的程式碼會列印出 a
的每一項:
vector<int>::iterator p = a.begin();
while (p != a.end()) {
cout << *p << endl;
p = next(p);
}
Iterator 的型態依據他使用的資料結構而異,因此這裡使用的型態是 vector<int>::iterator
。a.begin()
是指向 vector 第一項的物件,a.end()
則是 vector 最後一項的下一項的物件。如果要指向一個 iterator 的下一項,那麼可以使用 next()
,上一項則可以使用 prev()
。
下面的程式碼一樣會列印出 a
的每一項:
auto p = a.begin();
while (p != a.end()) {
cout << *p << endl;
p = p + 1;
}
注意到,我們把 vector<int>::iterator
換成了 auto
,這個是 C++ 的關鍵字,可以讓編譯器自己判斷變數的型態。另外我們將 next(p)
的部份改成了 p + 1
,這是因為 vector
和一般的陣列一樣,數值是連續排列的,所以 Iterator 也可以用加減法來指向旁邊的項目。
如果想要更加了解 Iterator 的內容,可以參考 Iterator。
讓我們來看看 vector
有什麼方便的功能吧!
首先是 find()
函式,這個函式可以尋找某個元素在 vector
出現的第一個位置。注意到他回傳的也是 Iterator 喔!
vector<int> a = {4, 3, 10, 1, 9};
auto it = find(a.begin(), a.end(), 1);
int pos = it - a.begin();
cout << "1 occurs at position " << pos << "\n";
if (find(a.begin(), a.end(), 2) == a.end()) {
cout << "2 is not found\n";
}
在 int pos
那一行,我們將 find()
找到的 iterator 與 a.begin()
相減,得到數字 1 出現在陣列的位置。而如果一個數字不在 vector
裡面的話,find()
會回傳 a.end()
。
find()
必須要依序看過 vector
的每一項,因此時間複雜度是 $O(n)$。
接下來是 insert
和 erase
兩個功能:
vector<int> a = {1, 2, 3};
// 在 a[0] 前插入 4
a.insert(a.begin(), 4); // 4, 1, 2, 3
// 在 a[3] 前插入 5
a.insert(a.begin() + 3, 5); // 4, 1, 2, 5, 3
vector<int> b = {10, 20};
//在 a 的結尾加上 b
a.insert(a.end(), b.begin(), b.end()); // 4, 1, 2, 5, 3, 10, 20
// 刪除 a[2]
a.erase(a.begin() + 2); // 4, 1, 5, 3, 10, 20
// 刪除 a[1] 和 a[2] (不含 a[3])
a.erase(a.begin() + 1, a.begin() + 3); // 4, 3, 10, 20
insert
功能是在某個位置之後插入一個(或多個)東西,erase
則是刪除一個(或多個)位置的東西。注意到這裡傳入的區間都是左閉右開的(包含第一項,不包含最後一項)。
還有一些常見的功能:
vector<int> a = {1, 2, 3, 4, 5};
// 將 a 反轉
reverse(a.begin(), a.end()); // 5, 4, 3, 2, 1
// 將 a 排序
sort(a.begin(), a.end()) // 1, 2, 3, 4, 5
// 將 a 清空
a.clear()
有一些函式(如 insert
, erase
, clear
)等等的寫法是 vec.function
,但是 find
, reverse
, sort
的寫法卻是 function(begin, end)
。前者的函式是屬於 vector
這個類別底下的 member function,後者則是 <algorithm>
底下的函式,相信讀者在基礎演算法 / 標準函式庫 ── <algorithm> 與 <numeric>已經有所了解。