1. 程式人生 > >圖解STL中演算法的分類、簡介及其Demo

圖解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);