圖解STL中演算法的分類、簡介及其Demo
STL中包含演算法標頭檔案<algorithm>
就可以使用其中的演算法了,使用這些通用的演算法可以使得程式碼更加簡單、易讀、通用。但是這些演算法有哪些呢?以及這些演算法的職能又是什麼?其實這些東西,候捷大師在他的《STL原始碼剖析》中都有列舉,且FluentCPP有一篇文章105 STL Algorithms in Less Than an Hour,他也給STL的105個演算法分了七個大類。我這裡總結了下他們的分類,自己按STL演算法所設計的方面進行了一個大致分類,其中很多演算法都同時在幾個類中。
- 不改變序列的操作
- 改變序列的操作
- 劃分操作
- 排序操作
- 二分操作
- 合併操作
- 比較操作
- 集合操作
- 堆操作
- 最大最小值操作
- 數值操作
- 未初始化記憶體上的操作
這裡不說具體怎麼使用每個演算法,但是使用的Demo會給出來,使用方法都可以在https://en.cppreference.com查到;這裡也不說每個演算法的實現原理,實現原理後面還會寫部落格說的。
先說明下,演算法中帶_if
字尾的表示可自定義條件,帶_copy
字尾的表示把結果放入新的序列中,帶_n
字尾的表示操作相同元素n
次。
*_copy
:
*_if
:
*_n
:
這些都是標準庫的帶字尾的例子,在下面的博文中,不會對這些內容再過多贅述。
不改變序列的操作
all_of
any_of
none_of
count
count_if
find
find_if
find_if_not
adjacent_find
search
search_n
(all / any / none)_of
這是C++11新增的三個演算法,分別表示序列上所有的元素全部都是……,存在一個是……,沒有一個是……。
find / count / search
count
對序列中指定元素進行計數,加了_if
的版本可以自定義計數條件;
find
在序列中順序查詢一個元素,同理,加了_if
_if_not
(C++11)是_if
的方面,把查詢條件取反;
search
在序列中查詢一段連續子序列,加了_n
表示在序列中查詢一段連續相同的子序列。
adjacent_find
在序列中找到第一個挨在一起的兩個一樣值的指定元素,看圖就明白了。
Demo
void NonModifySequenceDemo()
{
std::vector<int> result {2, 0, 4, 8, 6};
std::cout << "{2, 0, 4, 8, 6} all_of even " << std::boolalpha <<
std::all_of(std::begin(result), std::end(result), [] (const int & ele)
{
return ele % 2 == 0;
}) << std::endl;
for_each(std::begin(result), std::end(result), [] (int & ele)
{
--ele;
});
std::cout << "{1, -1, 3, 7, 5} any_of even " << std::boolalpha <<
std::any_of(std::begin(result), std::end(result), [] (const int & ele)
{
return ele % 2 == 0;
}) << std::endl;
std::cout << "{1, -1, 3, 7, 5} none_of even " << std::boolalpha <<
std::none_of(std::begin(result), std::end(result), [] (const int & ele)
{
return ele % 2 == 0;
}) << std::endl;
std::cout << "{1, -1, 3, 7, 5} has " <<
std::count(std::begin(result), std::end(result), 1) << " numbers of 1" << std::endl;
std::cout << "{1, -1, 3, 7, 5} has " <<
std::count_if(std::begin(result), std::end(result), [] (const int & ele)
{
return ele % 2;
}) << " numbers of odd" << std::endl;
std::cout << "{1, -1, 3, 7, 5} the first equal 3 at " <<
std::find(std::begin(result), std::end(result), 3) - std::begin(result) <<
" index" << std::endl;
std::cout << "{1, -1, 3, 7, 5} the first greater 6 at " <<
std::find_if(std::begin(result), std::end(result), [] (const int & ele)
{
return ele > 6;
}) - std::begin(result) << " index" << std::endl;
std::cout << "{1, -1, 3, 7, 5} the first not greater 0 at " <<
std::find_if_not(std::begin(result), std::end(result), [] (const int & ele)
{
return ele > 0;
}) - std::begin(result) << " index" << std::endl;
result[3] = 3;
std::cout << "{1, -1, 3, 3, 5} adjacent equal value is " <<
*std::adjacent_find(std::begin(result), std::end(result)) <<
std::endl;
result.clear();
result = {4, 3, 6, 6, 6, 6, 9, 1, 2, 4, 10};
std::vector<int> tag {1, 2, 4};
std::cout << "{4, 3, 6, 6, 6, 6, 9, 1, 2, 4, 10} include {1, 2, 4} at " <<
std::search(std::begin(result), std::end(result),
std::begin(tag), std::end(tag)) - std::begin(result) <<
" index" << std::endl;
std::cout << "{4, 3, 6, 6, 6, 6, 9, 1, 2, 4, 10} include 4 numbers of six at " <<
std::search_n(std::begin(result), std::end(result),
4, 6) - std::begin(result) <<
" index" << std::endl;
}
改變序列的操作
copy / copy_backward
copy_if / copy_n
(C++11)move / move_backward
(C++11)fill / fill_n
transform
generate / generate_n
remove / remove_if / remove_copy / remove_copy_if
replace / replace_if / replace_copy / replace_copy_if
reverse / reverse_copy
rotate / rotate_copy
unique / unique_copy
swap / iter_swap / swap_ranges
shuffle
(C++11)sample
(C++17)
改變序列的演算法最多了,相應地,這些操作都提供了幾個待字尾的函式。
先來講幾個常見的同時也是用得比較多的。
copy
複製。
move
這個move
並不是取得一個值的右值引用,而是取得整個序列的右值引用。
下面是我自己做的圖:
swap_ranges
交換兩個序列的內容。
類似的還有swap
,這個用得最多;iter_swap
可以交換指標或者迭代器指向記憶體的內容。
fill
類似memset
。
generate
這個和fill
挺像,只不過generate
使用一個函式往序列中填充元素,也就是說你可以定製這個序列。
iota
iota
的功能是在一段區間上填上遞增的數字。
下面是我繪製的圖:
remove
你以為remove
的作用類似下圖嗎?刪掉指定的元素。
不,它只是把要刪除的元素移動到了序列尾。
unique
刪除序列中連續重複的內容,和remove
類似,只是把冗餘的元素移動過了序列尾。
注意,上圖中並沒有刪除最後一個99,如果想要刪除序列中重複的內容,需要先對序列呼叫std::sort
使冗餘的元素聚集在一起。
transform
對序列中的每個元素都執行函式f
,可以對序列本身沒有影響,也可以改變序列本身。看看transform
的宣告就知道了。
template< class InputIt, class OutputIt, class UnaryOperation >
OutputIt transform( InputIt first1, InputIt last1, OutputIt d_first,
UnaryOperation unary_op );
for_each
和transform
有點像,它也是對序列執行函式f
。只不過for_each
在本序列上進行操作,它沒有transform
的靈活性,因此對序列產生副作用的機率很大。也看看這個演算法的宣告:
template< class InputIt, class UnaryFunction >
UnaryFunction for_each( InputIt first, InputIt last, UnaryFunction f );
rotate
在序列中交換其中的兩部分的順序。
reverse
反轉序列。
shuffle
正如下圖所示,有一個骰子,對一個序列呼叫shuffle
會隨機打亂這個序列元素之間的順序。
sample
這是C++17的內容,作用是從序列中隨機挑選n
個元素出來組成新的序列,每個元素只會被選擇一次,如果n
大於序列的長度,那麼整個序列都會被選出來。
Demo
void ModifySequenceDemo()
{
static const int LEN = 10;
std::vector<int> res(5), aux(LEN);
std::iota(res.begin(), res.end(), 1);
std::copy(res.begin(), res.end(), aux.begin());
std::cout << "copy {1, 2, 3, 4, 5} to aux(10) : {";
for (auto it = aux.begin(); it != aux.end(); std::cout << *it <<
std::array<std::string, 2>{", ", "}\n"}[it + 1 == aux.end()], ++it) {}
aux.clear(), aux.resize(LEN, 0);
std::copy_backward(res.begin(), res.end(), aux.end());
std::cout << "copy_backward {1, 2, 3, 4, 5} to aux(10) : {";
for (auto it = aux.begin(); it != aux.end(); std::cout << *it <<
std::array<std::string, 2>{", ", "}\n"}[it + 1 == aux.end()], ++it) {}
aux.clear(), aux.resize(LEN, 0);
std::copy_if(res.begin(), res.end(), aux.begin(), [] (const int & ele)
{
return ele % 2;
});
std::cout << "copy_if {1, 2, 3, 4, 5} to aux(10) : {";
for (auto it = aux.begin(); it != aux.end(); std::cout << *it <<
std::array<std::string, 2>{", ", "}\n"}[it + 1 == aux.end()], ++it) {}
aux.clear(), aux.resize(LEN, 0);
std::copy_n(res.begin(), 3, aux.end());
std::cout << "copy_n(3) {1, 2, 3, 4, 5} to aux(10) : {";
for (auto it = aux.begin(); it != aux.end(); std::cout << *it <<
std::array<std::string, 2>{", ", "}\n"}[it + 1 == aux.end()], ++it) {}
std::fill_n(res.begin(), 3, 3);
std::cout << "fill_n(3) {1, 2, 3, 4, 5} : {";
for (auto it = res.begin(); it != res.end(); std::cout << *it <<
std::array<std::string, 2>{", ", "}\n"}[it + 1 == res.end()], ++it) {}
res.clear(), res.resize(LEN >> 1, 0);
std::fill(res.begin(), res.end(), 0);
std::cout << "fill {} by 0 : {";
for (auto it = res.begin(); it != res.end(); std::cout << *it <<
std::array<std::string, 2>{", ", "}\n"}[it + 1 == res.end()], ++it) {}
// res.clear(), res.resize(LEN >> 1, 0);
std::transform(res.begin(), res.end(), res.begin(), [] (const int & ele)
{
return ele + 1;
});
std::cout << "transform {0, 0, 0, 0, 0} to {1, 1, 1, 1, 1} : {";
for (auto it = res.begin(); it != res.end(); std::cout << *it <<
std::array<std::string, 2>{", ", "}\n"}[it + 1 == res.end()], ++it) {}
res.clear(), res.resize(LEN >> 1, 0);
std::generate(res.begin(), res.end(), [] ()
{
static int cnt = 1;
return cnt++;
});
std::cout << "generate {} : {";
for (auto it = res.begin(); it != res.end(); std::cout << *it <<
std::array<std::string, 2>{", ", "}\n"}[it + 1 == res.end()], ++it) {}
res.clear(), res.resize(LEN >> 1, 0);