本文目的
最近溫習了一下C++ STL中的algorithm庫函數,記得上次看這些內容時,還在讀書,可以追溯到2009年春天了(剛好4年J)。正所謂為“溫故而知新,可以為師矣”。閑話少說,開始正文。
算法庫
C++標准算法庫中包含一些模版函數,用於執行基本的算法,比如for_each(遍歷容器),random_shuffle(隨機打亂容器)等。主要實現包含在頭文件<algorthim>中,少量在<numeric>中。它是STL的三大核心組件之一,其他兩個是容器(container,常用數據結構)和迭代器(Iterator,數據結構訪問適配器)。設計思想:算法函數通過迭代器作用在容器上,最大程度的復用。比如for_each函數,可以通過迭代器作用在set,map,list,vector,deque等容器上。
所有的算法都會接受容器的迭代器作為參數,而不是容器本身,這樣算法可以作用於全部或者部分容器中的元素,十分靈活。如果算法(比如std::transform)需要訪問兩個容器,一般傳入第一個容器的第一個元素,第一個容器的最后一個元素和第二個容器的第一元素。不需要傳入第二個容器的最后一個元素,因為可以通過第一個容器的兩個迭代器計算出來。除非此算法允許作用在兩個不一樣長度的容器上,比如search函數。
為了使容器算法函數具有更高的靈活性,一般算法函數會接受一個函數或則函數對象(類似javascript的回調函數),這個函數在算法執行過程中內部使用,執行特殊的業務邏輯。
算法函數還有一個規律是具有兩種后綴,
- 后綴_if 此后綴的函數一般有一個沒有后綴的版本與之對應。如find和find_if,前者接受一個值,根據該值尋找容器中對應的元素,后者接受一個函數或函數對象(operator()必須返回bool,標識是否匹配)。
- 后綴_copy 此后綴用於將算法修改后的元素拷貝到一個新的容器中,原始容器不被修改,所以此算法需要更多的內存。
迭代器范圍(Range)
STL的迭代器尊首一個原則:前閉后開,[first, last)。容器begin函數返回的迭代器表示容器中的第一個元素,而end函數返回的迭代器最后一個元素后面的位置(the one after the last element),也就是說*(end)沒有意義,*(end-1)表示最后一個元素。這樣有幾個好處:1)統一標識容器結尾;2)計算迭代器距離時,不用額外加1。
示例
為什么要使用算法函數。然道不能用C++的基本語法完成同樣的功能嗎?答案是肯定的,算法庫中的所有功能均可以使用最原始的C++語法實現,但是為什么要重復造輪子呢?而且,算法庫提供而外的好處:1)代碼簡潔優雅,便於閱讀和維護;2)大多數算法會比你實現的效率更高(由C++委員會的大牛們實現的,能不快嗎?);3)更靈活,使用模版和迭代器風格,可以適配不同類型的數據類型和容器類型。
代碼最優說服力,看看下面的例子吧!計算pearson系數,一種計算兩個向量是否線性相關。取值范圍[-1, 1],絕對值越大,越相關,-1代表線性遞減,1代表線性遞增,0代表線性無關。計算公式如下:
看看下面的代碼:
#include <algorithm> #include <numeric> #include <vector> #include <iostream> #include <cmath> using namespace std; /** * calculate person without STL style */ template<class T1, class T2> double pearson(const vector<T1>& col1,const vector<T2>& col2) { int n = col1.size(); double xy_sum = 0; for(int i = 0; i < n; ++i) { xy_sum += col1[i] * col2[i]; } double x2_sum = 0; for(int i = 0; i < n; ++i) { x2_sum += pow(static_cast<double>(col1[i]),2); } double x_sum = 0; for(int i = 0; i < n; ++i) { x_sum += col1[i]; } double y2_sum = 0; for(int i = 0; i < n; ++i) { y2_sum += pow(static_cast<double>(col2[i]),2); } double y_sum = 0; for(int i = 0; i < n; ++i) { y_sum += col2[i]; } double deno = sqrt((x2_sum - 1.0 * pow(x_sum, 2) / n)*(y2_sum - 1.0 * pow(y_sum, 2) / n)); return (xy_sum - 1.0 * x_sum * y_sum / n)/ deno; } /** * STL Style for pearson */ template<class InputIt1, class InputIt2> double person_stl(InputIt1 firstX, InputIt1 lastX, InputIt2 firstY) { int n = distance(firstX, lastX); double xy_sum = inner_product(firstX, lastX, firstY, 0); double x2_sum = inner_product(firstX, lastX, firstX, 0); double y2_sum = inner_product(firstY, firstY + n, firstY, 0); double x_sum = accumulate(firstX, lastX, 0); double y_sum = accumulate(firstY, firstY + n, 0); double deno = sqrt((x2_sum - 1.0 * pow(x_sum, 2) / n)*(y2_sum - 1.0 * pow(y_sum, 2) / n)); return (xy_sum - 1.0 * x_sum * y_sum / n)/ deno; } int main(int argc, char** argv) { vector<int> col1,col2; for (int i = 0; i < 10; ++i) { col1.push_back(i); col2.push_back(10-i); } cout << "Normal Style : " << pearson(col1,col2) << endl; cout << "STL Style : " << person_stl(col1.begin(),col1.end(),col2.begin()) << endl; return 0; }
采用了兩種方法實現了pearson系數,第一種采用的C++原始語法實現。第二種采用STL風格,可以看到前者用去了25行,而后者不到10行。
輸出結果如下:
參考資料