C++STL學習之algorithm庫函數


本文目的

最近溫習了一下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代表線性無關。計算公式如下:

clip_image002

看看下面的代碼:

#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行。

輸出結果如下:

clip_image004

 

參考資料


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM