STL 基礎及原理

STL教程概述

也許你在解決 TopCoder 上的問題時已經用過 C++,可能你會發現別人在實現算法的時候,代碼很簡潔幹練。又或許你是一個初級 C++ 程序員,那麼你就更應該好好讀一下這篇stl教程了。在本教程中,我會逐步向你介紹 C++ STL(Standard Template Library)的強大特性,這樣你在各種 OJ 上挑戰的時候或者在工作中,在算法實現上能節省很多時間。

1. stl教程之Containers

要學好 STL,首先要從 Containers 開始。當你要處理一系列的元素的時候,必然離不開某種特定的 container,在原生的 C 語言中,只有一種 container ——數組(array)。但是 array 只有基本的功能,而我們往往需要功能很強大的 container。比如以下功能:
1)往 container 中添加 string;
2)刪除 container 中的 string;
3)查詢 container 中是否有一個 string;
4)返回 container 中不同元素的個數;
5)遍歷整個 container 獲取裏面的所有 string 列表。
當然你可以利用 array 自己來實現所有這些功能,但是這就要你花很多時間處理很多繁瑣的細節。所以還是明智點,直接用 STL 吧。
在使用 STL 時,要先 #include 適當的頭文件。例如你要用 stack,那麼就應該在程序開頭加上這句:

#include <stack>

STL 的各 Container 以及 algorithms 並定義在命令空間“std”中,並不是定義在全局的命名空間中,所以在 include 對應頭文件後,還得加上這句:
using namespace std;
還有一點要注意的是,container 的類型都是模板參數類型,模板參數放在 <>(尖括號中),如定義int類型的 vector,如下:

vector<int> v1;

在定義嵌套的 container 的時候,尖括號間要加上適當的空格:

vector< vector<int> > v1; // 正確的定義方式
vector<vector<int>> v2; // 錯誤的定義方式,編譯器會把 >> 和輸出操作的 >> 混淆

2. stl教程之Vector

STL 中最簡單的 container 是 vector,vector 只是簡單的擴展了一下 array 的功能,它也是唯一個能向後兼容 C 標準的 container,因爲它本身就是 array,只是多了一些特性而已。

 vector<int> v(10); 
 for(int i = 0; i < 10; i++) { 
      v[i] = (i+1)*(i+1); 
 } 
 for(int i = 9; i > 0; i--) { 
      v[i] -= v[i-1]; 
 } 

vector<int> v;

在定義 v 的時候,就創建了一個空的 vector。
注意下面這行代碼:

vector<int> v[10]; 

在這裏,v 是一個長度爲 10 的數組,每個數組元素是一個 vector,而不是 int。但是我們的本意是要定義一個 vector,其長度爲 10,即存放 10 個整數的一個 vector。我們應該這樣做

 vector<int> v(10); 

要用圓括號,而不是方括號。

要知道vector的長度,我們可以調用size函數:

int elements_count = v.size(); 

關於size函數,我們要注意兩點:(1)size() 返回的是無符號整型,但是有時我們可能會定義一些宏,如 sz(C),這個宏用於返回 C 的長度,它返回的是 int 類型(有符號的);(2)在判斷一個 container 是否爲空的時候,最好不要用 v.size() 和 0 比較來判斷,而是用 empty() 函數:

 bool is_nonempty_notgood = (v.size() >= 0); // 最好不要這這樣寫
 bool is_nonempty_ok = !v.empty(); // 應該這樣寫

因爲並不是所有的 containers 的 size() 函數都是 O(1) 的,因爲有的 container 在調用 size() 函數的時候,要做遍歷操作等。

vecto r的另一個常用函數是 push_back(),該函數向 vector 尾部添加一個新的元素,並把 vector 的 size 增 1 :

 vector<int> v; 
 for(int i = 1; i < 1000000; i *= 2) { 
      v.push_back(i); 
 } 
 int elements_count = v.size(); 

不用擔心內存分配問題 —— 在分配內存的時候,vector 並不是逐一申請每個元素的內存的,每次在進行 push_back 的時候,如果空間不夠大,它會一次性申請多個元素的空間。
如果你要調整 vector 的大小,可以調用 resize() 函數:

 vector<int> v(20); 
 for(int i = 0; i < 20; i++) { 
      v[i] = i+1; 
 } 
 v.resize(25); 
 for(int i = 20; i < 25; i++) { 
      v[i] = i*2; 
 } 

resize() 函數調整出來的 vector 會包含它原先有的那些元素,但是如果你把空間調小到不足以容納原來那些元素的程序,那麼末尾的元素會被捨棄。如果 resize() 的空間大於原先的長度,那麼新創建的元素會被填充 0 。

注意如果你是在調用 resize() 函數後調用 push_back(),系統還是會分配新的空間來存放添加進來的元素:

 vector<int> v(20); 
 for(int i = 0; i < 20; i++) { 
      v[i] = i+1; 
 } 
 v.resize(25); 
 for(int i = 20; i < 25; i++) { 
      v.push_back(i*2); // Writes to elements with indices [25..30), not [20..25) ! 
 }

如上我們把 v 調大到 25,然後調用 push_back() 函數添加一些元素,但是新的元素是從下標爲 25 開始的位置開始存放的,而不是 20 。
在清空 vector,就調用 clear() 函數,這個函數會使得 vector 包含的元素個數變成 0,而不是把元素的值變爲 0 。

初始化 vector 的方式有很多種,也可以用已有的 vector 來初始化新聲明的 vector:

 vector<int> v1; 
 // ... 
 vector<int> v2 = v1; 
 vector<int> v3(v1); 

如上初始化後, v2 和 v3 是一樣的。
如果你要創建指定開度的 vector,可以如下調用:

vector<int> Data(1000); 

如上定義了一個長度爲 1000 的 vector,它所有元素的初值會被置爲 0 。如果你不想用默認的初始值,你可以自己指定:

vector<string> names(20, “Unknown”); 

如上定義了一個長度爲 20 的 vector,存放 string 類型,所有元素初值爲 "Unknown" 。

還可以定義多維的 vector,最簡單的二維 vector 定義如下:

vector< vector<int> > Matrix; 

我們也可以指定二維 vector 的長度:

 int N, M; 
 // ... 
 vector< vector<int> > Matrix(N, vector<int>(M, -1)); 

如上,我們定義了一個 N*M 的矩陣,所有元素初值爲 -1 。

現在我們知道 push_back() 函數總是會在 vector 的末尾添加元素,但是如果我們想在指定的位置添加元素的話,那麼就得調用 insert() 函數了;對應的 erase() 函數用於刪掉指定的元素,我們稍後介紹完 iterators 講到它們。

要注意一點:如果你把 vector 當成參數直接傳遞給一個函數,那麼該函數在接收的時候,實際上會創建一個這個 vector 的拷貝,通常這會花費很多時間和空間,而實際上往往不需要這麼做:

 void some_function(vector<int> v) { // 除非你確定要這樣做,否則最好不要這樣寫 
      // ... 
 } 

我們應該這樣寫(傳遞 vector 的引用)

 void some_function(const vector<int>& v) { // OK 
      // ... 
 } 

加上 const 限定,被調用函數就不能修改 vector 的內容。如果你想讓被調用函數能修改原 vector 的內容,那麼應該這樣寫:

 int modify_vector(vector<int>& v) { // Correct 
      V[0]++; 
 } 

3. stl教程之Pairs

在講 iterators 前,我們先來簡單講一下 pairs,pairs 在 STL 中的應用非常廣泛。pairs 的基本定義如下:

 template<typename T1, typename T2> struct pair { 
      T1 first; 
      T2 second; 
 }; 

pair<int, int> 定義了一對整數,pair<string, pair<int, int> > 定義一個 string 和兩個整數對,可如下訪問其元素值:

 pair<string, pair<int,int> > P; 
 string s = P.first; // 指前面的 string 
 int x = P.second.first; // 指後面的第一個 int 
 int y = P.second.second; // 指後面的第二個 int 

pairs 最大的作用是它內部有一個比較函數,可用於元素間的比較。如果兩個 pairs 的第一個元素不相等,那麼它們的大小由第一個元素的比較決定;否則由第二個元素的比較決定。元素類型爲 pairs 的 array 或 vector,可以非常方便的調用 STL 內部的排序函數來排序。
pairs 在關聯容器(associative containers)中的使用很廣泛,稍後我們會講到。

4. stl教程之Iterators

在訪問 containers 中的數據的時候,就得用到 Iterators 了。如下示例是一個反轉數組元素的程序,我們先用類 C 的方式來實現(第一種方法):

 void reverse_array_simple(int *A, int N) { 
      int first = 0, last = N-1;
      While(first < last) {
           swap(A[first], A[last]); // swap(a,b) is the STL function 
           first++; // Move first index forward 
           last--; // Move last index back 
      } 
 } 

我們還可以用指針來寫(第二種方法):

 void reverse_array(int *A, int N) { 
      int *first = A, *last = A+N-1; 
      while(first < last) { 
           Swap(*first, *last); 
           first++; 
           last--; 
      } 
 }

然後我們再來考慮一個問題:如何對一個雙向鏈表進行反轉操作呢?顯然按上面的第一種方法是不可行的,因爲它基於元素下標索引。而在雙向鏈表中, 我們沒法在 O(1) 的時間內取得所有結點的下標。但是第二種方法是可行的,因爲它通過指針來操作元素:獲取元素值、比較元素大小、指針自增、指針自減,Iterators 就實現這些基本功能。所有的 STL container 都可以通過 iterator 遍歷。

Iterators 和指針很類似,它定義瞭如下基本操作:
1)獲取一個 Iterators 的值:int x = *iter;
2)自增、自減 Iterators:iter++, iter--;
3)可以用 != < 等比較運算符對不同的 Iterators 進行比較;
4)Iterators 移動動指定的偏移位置,如 iter += 20(iter 前移 20 個元素的位置);
5)獲取兩個 Iterators 間的距離:int distance = iter2 - iter1;
但是和指針不同的是,Iterators 提供了更多的功能,它不僅可以操作 container,還可以進行範圍檢查、容器分析等。
Iterators 最大的優點是實現了代碼重用:基於 Iterators 實現的算法,可以用在不同的 containers 中。
但是並不是所有的 Iterators 都提供了相同的功能,我們可以把 Iterators 大致分爲 “普通的 Iterators” 和 “隨機訪問的 Iterators”。對於普通 Iterators,我們可以用 ==、!= 等比較運算符進行比較,也可以自增、自減,但是它們不可以相減,也不可以給普通 Iterators 增加指定的值。
基於 Iterators,前面的程序可以這樣實現:

 template<typename T> void reverse_array(T *first, T *last) { 
      if(first != last) { 
           while(true) { 
                swap(*first, *last); 
                first++; 
                if(first == last) { 
                     break; 
                } 
                last--; 
                if(first == last) { 
                     break; 
                } 
           } 
      } 
 }

這個程序和前面的程序的最大區別在於:不用 < 進行比較,而是用 == 。
通常 STL 的算法都會用兩個 Iterators,一個 begin iterator、一個 end iterator,begin 指向 container 的第一個元素,end 指向 container 最後一個元素的下一個位置。每個 STL container 都有兩個函數 begin()、end(),分別用於獲取該 container 的 begin iterator 和 end iterator。
我們可以用 c.begin()==c.end() 來判斷 container 是否爲空,用 c.end()-c.begin() 來獲取 container 的元素個數(和 c.size() 等效),當然並不是所有 container 都可以用這個相減操作。
STL 源碼裏的反轉函數是這樣的:

 template<typename T> void reverse_array_stl_compliant(T *begin, T *end) { 
      // We should at first decrement 'end' 
      // But only for non-empty range 
      if(begin != end) 
      { 
           end--; 
           if(begin != end) { 
                while(true) { 
                     swap(*begin, *end); 
                     begin++; 
                     If(begin == end) { 
                          break; 
                     } 
                     end--; 
                     if(begin == end) { 
                          break; 
                     } 
                } 
           } 
      } 
 } 

注意這和 C++ 標準庫的 std::reverse(T begin, T end) 函數是不一樣的。

此外,因爲模板的強大作用,STL 的 algorithms 和 functions 可以接收多種類型的 iterator:

 vector<int> v; 
 // ... 
 vector<int> v2(v); 
 vector<int> v3(v.begin(), v.end()); // v3 equals to v2 

 int data[] = { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31 }; 
 vector<int> primes(data, data+(sizeof(data) / sizeof(data[0]))); 

最後一行代碼通過 C 的原始數組創建一個 vector。

我們還可以這樣來創建一個新的 vector:

 vector<int> v; 
 // ... 
 vector<int> v2(v.begin(), v.begin() + (v.size()/2)); 

如上 v2 等於 v1 的前半段。

下面是 reverse() 函數的應用實例(只反轉指定區域的元素):

int data[10] = { 1, 3, 5, 7, 9, 11, 13, 15, 17, 19 }; 
 reverse(data+2, data+6); // the range { 5, 7, 9, 11 } is now { 11, 9, 7, 5 }; 

每個 container 都有兩個函數 rbegin() 和 rend(),用於返回反向的 iterators,rbegin() 指向結尾、rend() 指向開頭。在需要反向遍歷 container 的時候,可用這兩個函數。

 vector<int> v; 
 vector<int> v2(v.rbegin()+(v.size()/2), v.rend()); 

上面用 v 的前半段來創建 v2,v2 的內容是 v 的前半段,但是元素順序剛好相反。

在創建 iterator 對象的時候,必須指定它的類型,類型指定可以通過在 container 後加上 ::iterator、::const_iterator、::reverse_iterator、::const_reverse_iterator 等來實現,如我們可以這樣來遍歷一個 vector:

 vector<int> v; 

 // ... 

 // Traverse all container, from begin() to end() 
 for(vector<int>::iterator it = v.begin(); it != v.end(); it++) { 
      *it++; // Increment the value iterator is pointing to 
 } 

出於性能方面的考慮,建議用 != 代替 <,用 empty() 代替 size()!=0。

find() 函數可用於查找指定範圍內是否有某個元素,如果存在,那麼返回指向第一個指定元素的 iterator,如果不存在,那麼返回值等於 end interator:

 vector<int> v; 
 for(int i = 1; i < 100; i++) { 
      v.push_back(i*i); 
 } 

 if(find(v.begin(), v.end(), 49) != v.end()) { 
      // ... 
 }

如果想獲取所找到的元素的下標,那麼就用相減操作:

 int i = (find(v.begin(), v.end(), 49) - v.begin(); 
 if(i < v.size()) { 
      // ... 
 } 

注意用到 STL 的 algorithms 的時候,要 #include 。

下面還有些常用的方法:

int data[5] = { 1, 5, 2, 4, 3 }; 
 vector<int> X(data, data+5); 
 int v1 = *max_element(X.begin(), X.end()); // 返回 vector 中的最大值 
 int i1 = min_element(X.begin(), X.end()) – X.begin; // 返回 vector 中最小元素的下標 

 int v2 = *max_element(data, data+5); // 返回 array 中最大的元素 
 int i3 = min_element(data, data+5) – data; // 返回 array 中最小元素的下標 

我們可以定義一個宏:

#define all(c) c.begin(), c.end() 

然後有下面的排序方法:

vector<int> X; 
 // ... 
 sort(X.begin(), X.end()); // 升序排序 
 sort(all(X)); // 升序排序, 用 #define 
 sort(X.rbegin(), X.rend()); // 用 iterator 進行降序排序 

5. Compiling STL Programs

首先我們來看一下一個經典的錯誤:

 void f(const vector<int>& v) { 
      for( 
           vector<int>::iterator it = v.begin(); // 這裏有錯,看出來了嗎
           // ... 
      // ... 
 }

錯誤在於形參被 const 修飾了,但是我們獲取的 iterator 不是 const 類型,應該改成

 void f(const vector<int>& v) { 
      int r = 0; 
      // Traverse the vector using const_iterator 
      for(vector<int>::const_iterator it = v.begin(); it != v.end(); it++) { 
           r += (*it)*(*it); 
      } 
      return r; 
 } 

然後我們來簡單介紹一下一個 GNU C 標準裏特有的擴展: typeof,typeof 用於自動推導後邊 () 裏的數據類型,如:
typeof(a+b) x=(a+b)
這行代碼定義了一個變量 x,x 的類型和 a+b 的結果類型一樣,這就是這裏 typeof(a+b) 的作用。typeof 還可用於 container 的遍歷:

#define tr(container, it) \ 
      for(typeof(container.begin()) it = container.begin(); it != container.end(); it++) 

因爲 typeof 的自動類型推導功能,我們可以利用這個宏遍歷任意類型的 container,如

 void f(const vector<int>& v) { 
      int r = 0; 
      tr(v, it) { 
           r += (*it)*(*it); 
      } 
      return r; 
 } 

對 vector 來說,可能這個宏定義的作用並不大,但是當我們要遍歷複雜的數據類型的時候,就非常有用了,可以省掉很多代碼。

6. stl教程之 vector 中的數據操作

我們可以用 insert() 函數向指定的位置插入一個新的元素:

 vector<int> v; 
 // ... 
 v.insert(1, 42); // Insert value 42 after the first 

這樣,從 第二個元素(index=1)開始的所有元素都要向後移一個位置,以給新的元素騰出空間。如果我們要批量插入元素,這樣調用這個函數效率就比較低了,因爲插入一個元素,所有後面的元素都要身後移動。這裏我們可以這樣調用:

 vector<int> v; 
 vector<int> v2; 
 // .. 
 // Shift all elements from second to last to the appropriate number of elements. 
 // Then copy the contents of v2 into v. 
 v.insert(1, all(v2)); 

把從第二個元素開始的所有元素都向後移動適當的位置,然後把 v2 中的所有內容填充到 v 移動元素後騰出的空間中。
同樣的,erase() 函數也有兩個類似的版本:

 erase(iterator); 
 erase(begin iterator, end iterator); 

第一個函數刪除指定位置的元素,第二個刪除指定區域的所有元素。

7. stl教程之String(字符串)

string 是 STL 中用於操作字符串的 container,它和 vector 是不一樣的。主要的區別在於它們的字符串操作和內存管理方式策略。
String 有一個 substr() 函數,這個函數只需要通過下標值操作,不依賴於 iterators,如:

 string s = "hello"; 
 string 
      s1 = s.substr(0, 3), // "hel" 
      s2 = s.substr(1, 3), // "ell" 
      s3 = s.substr(0, s.length()-1), "hell" 
      s4 = s.substr(1); // "ello" 

8. stl教程之Set(集合)

STL 的 Set(集合) 有以下基本性質或操作:
1)任意兩元素都是不相等的;
2)可添加、刪除元素;
3)可獲取元素的個數;
4)可查詢集合中是否有指定元素(可在 O(logN) 時間內完成)。

先來看個簡單的例子:

 set<int> s; 

 for(int i = 1; i <= 100; i++) { 
      s.insert(i); // Insert 100 elements, [1..100] 
 } 

 s.insert(42); // does nothing, 42 already exists in set 

 for(int i = 2; i <= 100; i += 2) { 
      s.erase(i); // Erase even values 
 } 

 int n = int(s.size()); // n will be 50 

Set 是沒有 push_back() 函數的,因爲它的元素不需要順序,也正是這個原因,也不可通過下標來訪問集合中的元素。遍歷 Set 的唯一方法是用 iterators:

 // Calculate the sum of elements in set 
 set<int> S; 
 // ... 
 int r = 0; 
 for(set<int>::const_iterator it = S.begin(); it != S.end(); it++) { 
      r += *it; 
 } 

利用宏進行遍歷會更好些,想象一下如果有定義 set< pair<string, pair< int, vector > >,我們怎麼定義這玩意呢?所以還是用宏吧:

 set< pair<string, pair< int, vector<int> > > SS; 
 int total = 0; 
 tr(SS, it) { 
      total += it->second.first; 
 } 

注意 it->second.first,因爲 it 是個 iterator,所以我們得取到它的值,才能進行操作。這句代碼也可以這樣寫 (*it).second.first 。
要查找集合中是否指定元素,可用 find() 函數,但是注意了,STL 的全局 algorithm 裏面也有個 find() 函數,也可以用於 Set 的查找操作,但是它的複雜度是 O(N)。而 Set 本身的 find() 方法,是爲 Set 量身定做的,其算法複雜度爲 O(logN),類似的 multiset、multimap、hash_map、hash_set 中也有類似方法。

 set<int> s; 
 // ... 
 if(s.find(42) != s.end()) { 
      // 42 presents in set 
 } 
 else { 
      // 42 not presents in set 
 } 

另一個複雜度爲 O(logN) 的函數是 count() 函數:

 if(s.count(42) != 0) { 
      // … 
 } 

或者

 if(s.count(42)) { 
      // … 
 } 

但是,我個人傾向於用宏來實現和 count() 函數一樣的功能,用於 Set 中元素的 count,因爲要 count 的元素要麼存在,要麼不存在於 Set 中,沒有數量這個說法:

 #define present(container, element) (container.find(element) != container.end()) 
 #define cpresent(container, element) (find(all(container),element) != container.end()) 

all(c) 展開爲 c.begin(), c.end(前面我們定義過了)。
present 用於一般的 container,cpresent 用於 vector。

從集合中刪除元素,可以用 erase() 函數:

 set<int> s; 
 // … 
 s.insert(54); 
 s.erase(29); 

erase() 也可以指定區間:

set<int> s; 
 // .. 
 set<int>::iterator it1, it2; 
 it1 = s.find(10); 
 it2 = s.find(100); 
 // Will work if it1 and it2 are valid iterators, i.e. values 10 and 100 present in set. 
 s.erase(it1, it2); // Note that 10 will be deleted, but 100 will remain in the container 

同樣的,其構造函數也可以用指定的區間作爲參數:

 int data[5] = { 5, 1, 4, 2, 3 }; 
 set<int> S(data, data+5); 

還可以利用 Set 把 vector 中重複的元素去掉並排序:

 vector<int> v; 
 // … 
 set<int> s(all(v)); 
 vector<int> v2(all(s)); 

這樣,v2 會包含 v 中所有的元素,且按升序排序,且沒有重複的元素。Set 中所有可比較的元素都可以排序。

9. stl教程之Map(映射)

Map 的簡單示例如下:

 map<string, int> M; 
 M["Top"] = 1; 
 M["Coder"] = 2; 
 M["SRM"] = 10; 

 int x = M["Top"] + M["Coder"]; 

 if(M.find("SRM") != M.end()) { 
      M.erase(M.find("SRM")); // or even M.erase("SRM") 
 } 

其實 Map 和 Set 很相像,只是 Map 包含的不是 values,而是 <key, value>,即鍵值對。Map 保證了 key 的唯一性,而且還支持 [] 運算符。我們可以用前面定義過的 tr() 宏輕鬆實現對 map 的遍歷。Map 的 iterator 是標準的 std::pair 類型,所以取值語句爲 it->second:

 map<string, int> M; 
 // … 
 int r = 0; 
 tr(M, it) { 
      r += it->second; 
 } 

注意在遍歷的時候,不要修改任何元素的 key,因爲這樣會破壞其實體完整性。

Map 的 map::find() 函數和 map::operator[] 有個很大的區別是:map::find() 不會改變 map 的內容,而 operator[] 會在元素不存在的時候,創建一個對應的元素。如果你不想往 map 中添加元素,那就不要用 operator[] 。如果形參是被 const 修飾的 map,那麼 operator[] 也是不可用的:

 void f(const map<string, int>& M) { 
      if(M["the meaning"] == 42) { // Error! Cannot use [] on const map objects! 
      } 
      if(M.find("the meaning") != M.end() && M.find("the meaning")->second == 42) { // Correct 
           cout << "Don't Panic!" << endl; 
      } 
 } 

10. Notice on Map and Set

在底層實現上,map 和 set 都是基於紅黑樹的,底層的實現細節,我們不用太過關心。我們要記住的是:map 和 set 本身就以升序的順序存儲元素,在遍歷時,map 和 set 總以升序的順序遍歷,所以說最好不要在遍歷的過程中修改 map 或 set 的 key。在算法實現上,map 和 set 是有序的這個特點非常有用。
還有重要的一點:map 和 set 的 iterators 也可用 ++ 和 -- 運算符。如下,假設 set 中有一個元素 42,且它的前驅和後續都存在,那麼下面的代碼就有效:

 set<int> S; 
 // ... 
 set<int>::iterator it = S.find(42); 
 set<int>::iterator it1 = it, it2 = it; 
 it1--; 
 it2++; 
 int a = *it1, b = *it2; 

這樣我們就得到 42 的左右鄰居結點了。

11. More on algorithms

STL 的 algorithms 都聲明在 #include 頭文件中,其中最基本的四個函數:
1)min(a, b),求兩者中的小者;
2)max(a, b),求兩者中的大者;
3)swap(a, b),交互兩個元素的值。
4)sort(begin, end),用於對指定區域進行升序排序,但是 sort() 函數只能對 random access iterators 進行排序,所以並不是所有的 containers 都支持 sort() 函數。注意 set 和 map 都是有序的,不需要再用 sort() 進行排序了。

還有兩個重要的函數 next_permutation(begin, end) 和 prev_permutation(begin, end),分別用於返回指定內的元素的下一個排列和上一個排列,對 next_permutation() 函數,如果已經是最後一個排列, 則返回 false;對 prev_permutation() 函數,如果已是最前面一個排列,則返回 false。但是注意,在調用 next_permutation() 函數前,要先保證 container 已經排好序了:

 vector<int> v; 

 for(int i = 0; i < 10; i++) { 
      v.push_back(i); 
 } 

 do { 
      Solve(..., v); 
 } while(next_permutation(all(v)); 

12. stl教程之String Streams

C++ 有兩個 istringstream 和 ostringstream 用於處理 input、output 流,它們都定義在 #include
1)istringstream 用於 standard input 讀取數據:

 void f(const string& s) { 

      // Construct an object to parse strings 
      istringstream is(s); 

      // Vector to store data 
      vector<int> v; 

      // Read integer while possible and add it to the vector 
      int tmp; 
      while(is >> tmp) { 
           v.push_back(tmp); 
      } 
 } 

2)ostringstream 用於格式化 output:

 string f(const vector<int>& v) { 

      // Constucvt an object to do formatted output 
      ostringstream os; 

      // Copy all elements from vector<int> to string stream as text 
      tr(v, it) { 
           os << ' ' << *it; 
      } 

      // Get string from string stream 
      string s = os.str(); 

      // Remove first space character 
      if(!s.empty()) { // Beware of empty string here 
           s = s.substr(1); 
      } 

      return s; 
 } 

13. stl教程總結

我總結了一些常用宏,可用在 TopCoder 中:

 typedef vector<int> vi; 
 typedef vector<vi> vvi; 
 typedef pair<int,int> ii; 
 #define sz(a) int((a).size()) 
 #define pb push_back 
 #defile all(c) (c).begin(),(c).end() 
 #define tr(c,i) for(typeof((c).begin() i = (c).begin(); i != (c).end(); i++) 
 #define present(c,x) ((c).find(x) != (c).end()) 
 #define cpresent(c,x) (find(all(c),x) != (c).end()) 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章