1. 程式人生 > 程式設計 >C++演算法與泛型演算法(algorithm、numeric)

C++演算法與泛型演算法(algorithm、numeric)

本文包括的演算法有:

  • 只讀演算法:find()、count()、accumulate()、equal()
  • 寫演算法:fill()、fill_n()、back_inserter()、copy()、copy_backward()、replace()、replace_copy()、next_permutation()、prev_permutation()
  • 重排元素演算法:sort()、stable_sort()、unique()

一、演算法簡介

大多數演算法在標頭檔案algorithm中。標準庫還在標頭檔案numeric中定義了一組數值泛型演算法
演算法是如何工作的:

  • 迭代器令演算法不依賴於容器型別:演算法不依賴於容器所儲存的元素型別。只要有一個迭代器可以來訪問元素,就可以進行運算
  • 但演算法依賴於元素型別的操作:雖然迭代器令演算法不依賴於容器型別,但大多數演算法都是用了一個(多個)元素型別上的操作。例如find用元素型別的==運算子完成每個元素與給定值的比較。其他演算法可能要求元素型別支援“<”運算子。不過,我們將看到,大多數演算法提供了一種方法,允許我們使用自定義的操作來替代預設的運算子

二、泛型演算法

  • 標準庫提供了超過100個演算法,這些演算法都對一個範圍內的元素來進行操作
  • 演算法基本上分為3類:是否讀取元素、改變元素、重排元素順序

三、只讀演算法

  • 只可以操作容器元素,不可以改變容器內的值
  • 因為是隻讀,所以建議使用只讀迭代器(cbegin()、cend())

find()

  • 功能:遍歷一個範圍中是否包含某元素
  • 引數:前2個引數是一個迭代器範圍或者指標範圍。第3個引數是要查詢的元素
  • 返回值:成功返回要查詢的元素所在的迭代器。失敗返回引數2
//判斷value在vec中是否存在,因為find如果失敗返回的是引數2.所以可以用來判斷是否查詢成功
 
vector<int> vec{ 1,2,3};
int value = 2;
auto result=find(vec.cbegin(),vec.cend(),value);
cout << "The value " << value << (result == vec.cend()
 ? "is not present" : "is present") << endl;
vector<string> vec{ "A","B","C" };
auto result=find(vec.cbegin(),"B");
cout << "The B "<< (result == vec.cend()
 ? "is not present" : "is present") << endl;

對陣列的操作:可以用內建函式begin()、end()作為陣列的迭代器,也可以用指標作為迭代器

int arr[] = { 1,3,4,5 };
int val = 4;
int* result = find(begin(arr),end(arr),val);
if (result != end(arr)) {
 cout << "find succcess,value is:"<< *result<< endl;
}
int arr[] = { 1,5 };
int value = 3;
auto result = find(arr + 1,arr + 3,value);
cout << "The value "<<value<<((result == arr + 3)
	?" is not present":"is present")<< endl;

count()

  • 功能:返回元素在指定迭代器範圍內出現的次數
  • 返回值:成功返回出現的次數,沒有返回0
list<int> li{ 1,66,100 };
cout <<"The 66 count is:" 
	<<count(li.cbegin(),li.cend(),66)<< endl;

accumulate()

  • 標頭檔案:numeric
  • 功能:將指定範圍內的元素進行和運算,引數3為和運算的初始值
  • 返回值:返回和運算的結果
  • 注意:此函式操作的元素必須能夠與+運算子進行操作
//計算li元素的和,和的初始值為0
list<int> li{ 1,3 };
cout <<"The sum is:" <<accumulate(li.cbegin(),0)<< endl; //6

使用string時,必須顯示地呼叫,不能夠直接使用字串,因為這樣會被accumulate函式認為是一個const char*物件而出錯

//正確
ist<string> li{"A","C"};
cout <<accumulate(li.cbegin(),string("String:"))<< endl; //String:ABC
//錯誤
list<string> li{"A","String:")<< endl;
//正確
list<string> li{"A","C"};
string s = "String:";
cout <<accumulate(li.cbegin(),s)<< endl;

附加:如果想要進行別的執行,例如乘、除等,可以使用引數4.例如下面是對一個數組內的元素進行乘操作(備註:初始化不要填0,否則結果就為0)

int *p = new int[4] {1,4};
cout << accumulate(p,p + 4,1,multiplies<int>()) << endl; //24

equal()

  • 功能:用來比較兩個指定範圍內的元素是否都是相同的值(常來比較兩個元素是否相同)
  • 引數:引數1和引數2指定一個容器的範圍。引數3指定另一個容器的開始範圍
  • 比較規則:將引數1和2指定的範圍內的所有元素,檢視這些元素是否與引數3所指定的另一個容器的開始處是否都存在(不一定要個數都相同,只要容器1的元素在容器2中都要一一對應)
  • 返回值:都相同返回1。不相同返回0
  • 因為equal呼叫迭代器完成操作,所以equal可以用來比較兩個不同型別的容器。例如vector<string>和list<const char*>可以進行比較
vector<int> vec1{ 1,2};
vector<int> vec2{ 1,3};
vector<int> vec3{ 1,4};
vector<int> vec4{ 1,4 };
 
cout << equal(vec1.cbegin(),vec1.cend(),vec4.cbegin())<< endl; //1
cout << equal(vec2.cbegin(),vec2.cend(),vec4.cbegin()) << endl; //1
cout << equal(vec3.cbegin(),vec3.cend(),vec4.cbegin()) << endl; //1
vector<int> vec1{ 2,3};
vector<int> vec2{ 1,vec2.cbegin())<< endl; //0
vector<string> vec1{ "A","B"};
vector<string> vec2{ "B" };
vector<const char*> vec3{ "A","C" };
 
cout << equal(vec1.cbegin(),vec3.cbegin()) << endl; //1
cout << equal(vec2.cbegin(),vec3.cbegin())<< endl; //0

四、寫演算法

可以讀寫容器內的元素,但不可以改變容器的大小。因此操作時要注意容器的大小(例如不能對空容器操作)

因為會改變值,所以不能使用只讀迭代器(cbegin()、cend())

fill()

  • 用來改變指定位置處的元素
  • 引數:引數1、2指定容器的範圍,引數3位要設定的值
vector<int> vec{ 1,5 };
fill(vec.begin(),vec.end(),0);//將vec全部置為0
for (auto v = vec.cbegin(); v != vec.cend(); v++)
	cout << *v << endl;
vector<int> vec{ 1,5,6 };
fill(vec.begin(),vec.begin()+vec.size()/2,66); //將vec的前半部分元素變為66
 
for (auto v = vec.cbegin(); v != vec.cend(); v++)
	cout << *v << endl;

fill_n()

  • 用來將指定數量的元素變為某個值
  • 引數:引數1為迭代器起始位置。引數2為要改變的元素個數。引數3為要設定的值
  • 注意:要注意引數2的設定,不能超出容器的大小,也不能對空容器操作
vector<int> vec{ 1,6 };
 
fill_n(vec.begin(),66); //將vec的前3個元素變為66
for (auto v = vec.cbegin(); v != vec.cend(); v++)
	cout << *v << endl;
 
fill_n(vec.begin(),vec.size(),66); //將vec全部變為66
for (auto v = vec.cbegin(); v != vec.cend(); v++)
	cout << *v << endl;
//下面程式碼不會出錯,但是也不會有效果,因為vec是空向量
 
vector<int> vec;
fill_n(vec.begin(),66);
for (auto v = vec.cbegin(); v != vec.cend(); v++) //不列印任何資訊
	cout << *v << endl;

back_inserter()

  • 又名插入迭代器
  • 引數:為一個容器的引用
  • 返回值:返回與該容器繫結的插入迭代器
  • 功能:常用來返回一個容器的迭代器,然後對此迭代器進行操作
  • 當我們通過返回的插入迭代器賦值時,會自動呼叫push_back將一個具有給定值的元素新增到容器中
  • 標頭檔案iterator
vector<int> vec; //空容器
 
auto it = back_inserter(vec); //返回vec的第一個迭代器
*it = 42; //此時vec有了一個元素,值為42

現在我們可以使用fill_n來給一個空容器賦值:在每次迭代中,back_inserter返回迭代器,因此每次賦值都會在vec上呼叫push_back,因此fill_n就能夠操作了。下面是在vec的末尾新增10個新的元素

vector<int> vec;
fill_n(back_inserter(vec),10,0);//通過back_inserter建立一個插入迭代器,然後可以向vec新增元素了
for (auto v = vec.cbegin(); v != vec.cend(); v++) //列印10個0
	cout << *v << endl;

copy()

  • 將引數1、2指定範圍的元素拷貝給引數3指定的容器
  • 引數:引數1、2為一個容器的範圍。引數3要接受拷貝的容器起始位置
  • 注意:引數3要有足夠的空間儲存拷貝的資料
  • 返回值:返回拷貝目的位置的迭代器值
int arr1[] = { 1,3 };
int arr2[sizeof(arr1)/sizeof(*arr1)];
 
auto ret = copy(begin(arr1),end(arr1),arr2); //將陣列1拷貝給陣列2。ret為arr2陣列最後元素的下一個位置
 
for (auto i = 0; i < sizeof(arr2) / sizeof(*arr2); i++) {
 cout << arr2[i]<< endl;
}

copy_backward

  • 該函式與copy的不同之處在於:
    • copy是從第一個元素開始拷貝,而copy_backward是從最後一個元素開始拷貝
    • copy的第3個引數是接受拷貝的容器起始位置,而copy_backward是目的序列的結束迭代器
  • 會複製前兩個迭代器引數指定的序列。第三個引數是目的序列的結束迭代器

C++演算法與泛型演算法(algorithm、numeric)

replace()

  • 將指定範圍內指定的元素替換為另一個值
  • 引數:1、2指定替換的範圍。3:目標值,4:替換後的值
vector<int> vec{ 1,5 };
replace(vec.begin(),66); //將vec中為0的全部替換為66
 
for (auto v = vec.cbegin(); v != vec.cend(); v++) {
	cout << *v << endl;
}

replace_copy()

  • 此函式會保留原容器不變,然後將替換後的結果儲存在另一個容器中
  • 引數:引數12位要替換的範圍。引數3位另一個容器的起始位置。引數4目標值。引數5替換後的值
vector<int> vec{ 1,5 };
vector<int> vec2;
 
replace_copy(vec.begin(),back_inserter(vec2),66); //vec的元素保持不變,vec2為替換後的值
	
for (auto v = vec2.cbegin(); v != vec2.cend(); v++) {
 cout << *v << endl;
}

next_permutation()

  • 功能:對一個迭代區間的數值進行全排列
  • 返回值:會根據前面next_permutation函式的呼叫,當操作的區間不存在下一個排列時,函式返回false;否則如果可以繼續進行全排列,那麼就返回true
//函式原型
#include <algorithm>
bool next_permutation(iterator start,iterator end)

演示案例:下面的程式每呼叫一次next_permutation就會對我們制定的迭代區間進行一次排列,直到得出全部排列之後,返回false

int *p = new int[3] {1,3};
do {
 for (int i = 0; i < 3; ++i){
  cout << p[i];
 }
 cout << endl;
} while (next_permutation(p,p + 3));

C++演算法與泛型演算法(algorithm、numeric)

prev_permutation()

  • 與next_permutation的功能顯示,區別就是prev_permutation是求當前排列的前一個排列,而next_permutation是求當前排列的下一個排列

五、重排元素演算法

sort()、unqie()、stable_sort()

因為會改變值,所以不能使用只讀迭代器(cbegin()、cend())

sort()

  • 將容器內的元素按照運算子“<”的規則排序,從小到大
  • 引數:一個容器的迭代器範圍
vector<int> vec{ 1,8,1 };
sort(vec.begin(),vec.end()); //將vec的值從小到大排序
 
for (auto v = vec.cbegin(); v != vec.cend(); v++) {
 cout << *v << endl;
}

unique()

  • 相鄰之間如果有重複相同的元素,則刪除重複的元素只保留一個
  • 引數:一個容器的迭代器範圍
  • 返回值:返回刪除重複元素之後的最後一個元素的後一個位置
  • 注意(重點):雖然刪除了元素,但是容器的大小依然沒有變,迭代器也沒有變。所有變為迭代器時一定要注意

C++演算法與泛型演算法(algorithm、numeric)

vector<int> vec{ 1,1 };
auto end_unique=unique(vec.begin(),vec.end());
 
//for迴圈時使用unique的返回值,如果使用vec.cend(),那麼會列印後面沒有元素位置的亂值
for (auto v = vec.cbegin(); v != end_unique; v++) { 
	cout << *v << endl;
}

stable_sort()

  • 也是排序
  • 如果非字串,就先按照數值的個數排序,個數相同時再按照大小排序
  • 如果是字串:按照長度排序,長度相同的按照字典排序
vector<int> vec{ 1,100,1 };
stable_sort(vec.begin(),vec.end());
 
//1,100
for (auto v = vec.cbegin(); v != vec.cend(); v++) {
 cout << *v << endl;
}

六、向演算法傳遞函式和lambda表示式

注意事項:

向演算法傳遞函式或者lambda表示式時要注意。該演算法支援呼叫一元謂詞(引數)還是多元謂詞(引數)

例如:find_if的第3個引數傳遞的函式/lambda只接受一個引數,如果是兩個引數的函式或lambda,那麼就不能呼叫(後面會介紹bind函式解決這個問題)

sort()

bool isMin(const int &s1,const int & s2) {
 return s1 < s2;
}
 
bool isMax(const int &s1,const int & s2) {
 return s1 > s2;
}
 
vector<int> vec{1,5};
sort(vec.begin(),isMin); //從小到大排序
sort(vec.begin(),isMax); //從大到小排序
 
//使用lambda表示式
sort(vec.begin(),[](const int &a,const int &b) {return a < b; });//從小到大排序
sort(vec.begin(),const int &b) {return a > b; });//從大到小排序

stable_sort()

C++演算法與泛型演算法(algorithm、numeric)

//按照長度排序,長度相同的按照字典排序
bool isShorter(const string &s1,const string & s2) {
	return s1.size() < s2.size();
}
 
vector<string> vec{"fox","jumps","over","quick","res","slow","the","turtle"};
stable_sort(vec.begin(),isShorter);
 
for (auto v = vec.cbegin(); v != vec.cend(); v++) {
	cout << *v << endl;
}

find_if()

  • 用來在指定範圍內查詢比指定值大/下的元素
  • 如果是大於(那麼就是最大的那個)。如果是小於(那麼就是比他小一個的那個)
  • 引數3:為一個函式指標或者lambda表示式
  • 返回值:存在就返回這個值,不存在返回尾迭代器
vector<int> vec{ 1,5 };
int sz = 4;
 
//在vec中尋找比sz大的數
auto wc=find_if(vec.begin(),[sz](const int &a) {return a > sz; }); //查詢比sz大的
auto wc2=find_if(vec.begin(),[sz](const int &a) {return a < sz; }); //查詢比sz小的
 
cout << *wc << endl; //5
cout << *wc2 << endl; //3

for_each()

  • 用來遍歷一個集合
  • 引數:引數12是一個容器的迭代器範圍。引數3lambda表示式
vector<int> vec{ 1,5 };
 
for_each(vec.begin(),[](const int&s) {cout << s << " "; });
cout << endl;
vector<string> vec{ "ABC","AB","A","sd" };
 
for_each(vec.begin(),[](const string&s) {cout << s << " "; });
cout << endl;

transform()

  • 引數:引數12為輸入序列。引數3為目的位置
  • 該演算法對輸入序列中每個元素呼叫課呼叫物件,並將結果寫到目的位置
  • 下面使用transform演算法和一個lambda表示式來將vector中的每個負數替換為絕對值。因為引數3位vec.begin(),所以就是將vec的全部元素取絕對值然後再寫入vec的開頭
vector<int> vec{ -1,-2,-3,4 };
//將vec的數全部取絕對值
transform(vec.begin(),vec.begin(),[](int i) {return i < 0 ? -i : i; });
 
for (auto v = vec.begin() ; v != vec.end(); ++v)
	cout <<*v << endl;

到此這篇關於C++演算法與泛型演算法(algorithm、numeric)的文章就介紹到這了,更多相關C++ algorithm numeric內容請搜素我們以前的文章或下面相關文章,希望大家以後多多支援我們!