演算法學習之路|用C++刷演算法會用到的STL(一)——vector

kissjz發表於2018-02-09

STL是Standard Template Library的簡稱,中文名標準模板庫。

從根本上說,STL是一些“容器”的集合,這些“容器”有list,vector,set,map等,STL也是演算法和其他一些元件的集合。STL現在是C++的一部分,因此不用安裝額外的庫檔案。

在C++標準中,STL被組織為下面的17個標頭檔案:

<algorithm>、<deque>、<functional>、<iterator>、<array>、<vector>、<list>、<forward_list>、<map>、<unordered_map>、<memory>、<numeric>、<queue>、<set>、<unordered_set>、<stack>和<utility>。

一、vector

1. vector的自我介紹

  • vector是向量的意思,可以理解為“可變長度的陣列”。
  • 就像陣列一樣,vector也採用的連續儲存空間來儲存元素。也就是意味著可以採用下標對vector的元素進行訪問,和陣列一樣高效。但是又不像陣列,它的大小是可以動態改變的,而且它的大小會被容器自動處理。
  • 相當於一個動態陣列,在你不知道需要的陣列有多大時可以使用它來節省很多空間。在ACM中常常會出現記憶體溢位(out of memory)的問題導致WA(wrong answer),而使用vector就能節省很多記憶體。
  • 使用vector要在程式開頭加上#include<vector>來包含需要的標頭檔案,還有加上“using namespace std;”,這樣就可以在程式碼中使用vector了。

2.vector的定義

  • 定義一個vector:
vector<typename> vectorname;
  • 這個定義相當於定義了一個一維的陣列vectorname[SIZE],只不過這個SIZE的長度是可以改變的,隨著你“加入”進去數的個數而改變。所謂“可變長度的陣列”。
  • 和一維的陣列一樣,這裡的typename可以是任何的型別,如int,double,char,string,long long ,也可以是結構體,甚至是STL的容器如vector,set,queue,stack等。注意!如果typename也是一個容器的話,比如vector<vector<int> > vectorname,要在兩個`<`符號之間加上一個空格,否則編譯時會誤以為是位移操作。

3.vector的建立

vector<double> vec1; // 建立一個空的double向量
vector<int> vec(66); // 建立一個初始大小為66的int向量
vector<double> vec2(vec1); // 建立一個double型的vec2,並用vec1去初始化vec2
vector<char> vec(10,`k`); // 建立一個含有10個char型資料的vector,並全部初始化為`k`
vector<int> vec(10,1);//建立一個初始大小為10的並且值都是1的vector
vector<int> vec2(vec1.begin(),vec1.begin()+3);//用向量vec1的第0個到第2個值初始化vec2
int arr[5] = {1, 2, 3, 4, 5};
vector<int> vec(arr, arr + 5); //將arr陣列的元素用於初始化vec向量,末尾指標指向結束元素
//的下一個
vector<int> vec(&arr[1], &arr[4]); //將arr[1]~arr[4]範圍內的元素作為vec的初始值,不包含arr[4],
//原因如上

4.特別的,元素為vector的vector陣列

  • (其實就是vector的二維陣列)和vector陣列! 如果typename是vector,那麼就這麼定義vector<vector<int> > vectorname; 注意在相鄰的`>`之間要加空格。 這個就像二維陣列的定義,其中一維是一個元素是vector的vector陣列。可以把vector陣列 當作兩個維長度都可變的二維陣列理解。vector陣列的定義vector<typename> Arrayname[arraySize]; 比如,vector<int> vec[66]; 這樣Arrayname[0]~Arrayname[arraySize-1]中每一個都是vector容器。 而vector<vector<int> > vectorname不同的地方時,前者的第一維長度已經固定為arraySize了, 後者卻是兩個維度都可變長的陣列。哈哈哈,用你的大腦,發揮空間想象能力,出現了什麼圖形? 很神奇是不是!

5.vector容器內元素的訪問形式

  • vector一般有兩種訪問形式:通過下標訪問或者是通過迭代器訪問。

(1)通過下標訪問

和訪問普通陣列是一樣的,一個已經定義的vector<typename> vec的vector容器,直接訪問vec[index]就可以了(比如 vec[0],vec[1])。需要注意的一點是,首先這個vec得有size!!!橋黑板!什麼意思呢?就是如果一開始你定義了 比如vector<int> vec1;直接使用vec[0]是不對的!因為裡面還沒有長度啊!開始用的時候我就經常犯這個錯誤(尷尬) 。所以可以通過下標訪問的範圍是從0~vec.size()-1.

(2)通過迭代器訪問

  • 啥叫迭代器啊?好可怕~就理解為指標吧,不去考慮細節,兩者是一樣的(包括在java中最近遇見的引用,這簡直是三胞胎!)。 定義如下: vector<typename>::iterator it;(it就是變數名,一般預設都寫成it) 這樣it就是一個vector<typename>::iterator 型的變數了(hhh,這個名字好長啊),其中,typename就是定義vector時 寫的型別。比如: vector<double>::iterator it; vector<int> vec; vector<int>::iterator it2; it2=vec.begin();(或者合成一條:vector<int>::iterator it2=vec.begin(); ) 這樣就得到了迭代器it(or it2),並且可以通過*it來訪問vector裡的元素。
vector<int> vec;
vector<int>::iterator it;
for (it = vec.begin(); it != vec.end(); it++)//vector的迭代器不支援it<vec.end()寫法,因此迴圈中只能用it!=end()
cout << *it << endl;
//或者
vector<int>::iterator it=vec.begin();
for(int i=0;i<vec.size();i++){
printf("%d ",*(it+i));//橋黑板!!在STL容器中,只有vector和string這兩個好基友,才允許使用vev.begin()+3這種迭代器加上整
//數的寫法,只有這倆。
//從這裡可以看出,vec[i]和*(vec.begin()+i)是等價的
}
//或者
for (size_t i = 0; i < vec.size(); i++) {
cout << vec.at(i) << endl;//見下文分解
}

好了,終於說完訪問形式了,可以介紹基本操作啦!

6.vector的基本操作

(1)對容量的操作

  • 得到向量的大小: vec.size();
  • 得到向量最大可以是多大: vec.max_size();
  • 重新設定容器size的大小: vec.resize(num);
  • 重新設定容器capacity的大小: vec.reserve(num);
  • 向量真實大小: vec.capacity();
  • 判斷向量是否為空: vec.empty();

注:關於resize()和reverse(),我覺得記住一點就行了,容器呼叫resize()函式後, 所有的空間都已經初始化了,所以可以直接訪問。比如: vector<int> vet; vec.resize(100); vec[0]=1;//合法語句! 而reserve()函式預分配出的空間沒有被初始化,所以不可訪問。 推薦一個關於resize()和reserve()寫的不錯的部落格vector中resize()和reserve()區別

(2)修改元素

  • 多個元素賦值: vec.assign(); //類似於初始化時用陣列進行賦值
  • 末尾新增元素: vec.push_back(i)//在末尾新增元素i
  • 末尾刪除元素: vec.pop_back();
  • 任意位置插入元素: vec.insert(it,x);
  1. 用來向vector的任意迭代器(見上文)it處插入一個元素x,時間複雜度O(N);
  • 任意位置刪除元素: vec.erase();
  1. erease(it)即刪除迭代器為it處的元素;
  2. erase(first,last)即刪除[first,last)內的所有元素;(老美的左閉右開,你懂的)
  • 交換兩個向量的元素: vec.swap();//不多講,用的真的很少。同樣,推薦部落格vector利用swap()函式進行記憶體的釋放

(3)驚現迭代器

開始指標:vec.begin();
末尾指標:vec.end(); //指向最後一個元素的下一個位置
指向常量的開始指標: vec.cbegin(); //意思就是不能通過這個指標來修改所指的內容,但還是可以通過其他方式修改的,而且指標也是可以移動的。(用的很少,不妨先忽略吧)
指向常量的末尾指標: vec.cend();(同上,不多說)

(4)元素的訪問

下標訪問: vec[1]; //橋黑板!!並不會檢查是否越界
at方法訪問: vec.at(1); //以上兩者的區別就是at會檢查是否越界,是則丟擲out of range異常
訪問第一個元素: vec.front();
訪問最後一個元素: vec.back();
返回一個指標: int* p = vec.data(); //可行的原因在於vector在記憶體中就是一個連續儲存的陣列,所以可以返回一個指標指向這個陣列。這是是C++11的特性。(可忽略)

(5)一些演算法

  • 遍歷元素
Cvector<int>::iterator it; 
for (it = vec.begin();it != vec.end(); it++)
    cout << *it << endl;
//或者
for (size_t i = 0; i < vec.size(); i++) {
     cout << vec.at(i) << endl; 
}
  • 元素翻轉
C#include <algorithm>
reverse(vec.begin(), vec.end());
  • 元素排序
C#include <algorithm> 
sort(vec.begin(), vec.end()); //採用的是從小到大的排序 
//如果想從大到小排序,可以採用上面反轉函式,也可以採用下面方法: 
bool Comp(const int& a, const int& b) {//固定套路,要加上const,意思是不能修改引用 
return a > b; 
} 
sort(vec.begin(), vec.end(), Comp);

7.vector的用武之地

(1)儲存資料

  • vector本身可以作為陣列使用,而且在一些元素個數不確定的場合可以很好的節省空間。
  • 有些場合需要根據一些條件把部分資料輸出在同一行,資料中間用空格隔開。由於輸出的資料個數是不確定的,為了跟方便的處理最後一個滿足條件的資料後面不輸出額外的空格,可以先用vector記錄所有需要輸出的資料,然後一次性輸出。
  • 有強大的方法可以呼叫(媽呀,java學多了,請不要噴我,暫且就叫方法吧,介紹如上vector的基本操作(1)~(4))

( 2 )用鄰接表儲存圖

  • 使用vector實現鄰接表可以有效避免指標,而且更容易把握。

哈哈哈,終於結束啦(喝口水)。
好了,說了這麼多,當然廢話也不少~~
來幾道題目吧!
PAT A1039. Course List for Student (25)

PAT A1047. Student List for Course (25)

參考:C++ STL之vector用法總結
《演算法筆記》(胡凡,曽磊)
 


相關文章