【10】C++之泛型算法

2023-11-06 23:59
文章标签 算法 c++ 之泛

本文主要是介绍【10】C++之泛型算法,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!


对于顺序容器的其他操作:查找元素、替换或者删除一个特定值、重排元素顺序等。标准库并未给每个容器都定义成员函数来实现这些操作,而是定义了一组泛型算法(generic algorithm):称它们为“算法”,是因为它们实现了一些经典算法的公共接口。如排序和搜索;称它们是“泛型的”,是因为它们可以用不同类型的元素和多种容器类型(不仅包括标准库类型,如vector或list,还包括内置的数组类型)。

标准库提供了超过100个算法。此篇只是一个入门,更多的STL操作参考《STL标准库》。


参考文章:

[1] 《C++ Primer》读书笔记—第十章 泛型算法

[2] 《C++泛型算法》

[3] 《c++中常用的泛型算法》


1、初识泛型算法

1.1 只读算法

一些算法只会读取其输入范围内的元素,而不改变元素。

① find

头文件:

#include <algorithm>

find接收三个参数,前两个是表示元素范围的迭代器,第三个参数是一个值。find将范围中每一个元素与给定值进行比较。它返回指向第一个等与给定值的元素的迭代器。如果范围中无匹配元素,则find返回第二个参数来表示搜索失败。因此,可以通过比较返回值和第二个参数来判断搜索是否成功。

对于string来说,由于其没有迭代器,所以find的用法有所不同:

语法:

size_type find( const basic_string &str, size_type index );
size_type find( const char *str, size_type index );
size_type find( const char *str, size_type index, size_type length );
size_type find( char ch, size_type index );

find()函数:

  • 返回str在字符串中第一次出现的位置(从index开始查找)。如果没找到则返回string::npos,
  • 返回str在字符串中第一次出现的位置(从index开始查找,长度为length)。如果没找到就返回string::npos,
  • 返回字符ch在字符串中第一次出现的位置(从index开始查找)。如果没找到就返回string::npos

例如:

string str = "hello are you ok";
string s_str = "hello";
if(str.find(s_str,0) != string::npos)cout << "find ok" << endl;

输出:

find ok

find_if:

find_if() find() 一样,为在输入迭代器所定义的范围内查找单个对象的算法,它可以在前两个参数指定的范围内查找可以使第三个参数指定的谓词(见本博客下面章节)返回 true 的第一个对象。谓词不能修改传给它的对象。
find_if() 返回一个指向被找到对象的迭代器,如果没有找到对象,会返回这个 序列的结束迭代器(也就是第二个参数)。

vector<int> num = {1,2,3,4,5,6,7,8,9};
int c = 10;
if(find_if(num.begin(),num.end(),[c](int n)->int{return c < n;}) != num.end())cout << "find it!" << endl;

输出:

find it!

② count

  • count(first,last,value):first是容器的首迭代器,last是容器的末迭代器,value是询问的元素,整个函数返回int型。count函数的功能是:统计容器中等于value元素的个数。
  • count_if(first,last,comp): (在comp为true的情况下计数)或者count_if(first,last,value,comp) (这个是在comp为true的情况下统计容器中等于value的元素):first为首迭代器,last为末迭代器,value为要查询的元素,comp为比较bool函数,为true则计数,函数返回型是int。

注:此两个函数复杂度是线性的,适用于小规模运算。

#include <iostream>
#include <vector>
#include <algorithm>using namespace std;int main()
{vector<int > num{1,2,3,1,2,3,4,5,6,7,8,1};int numcount = count(num.cbegin(),num.cend(),1);cout << "1s = " << numcount << endl;return 0;
}

输出:

③ accumulate

头文件:

#include <numeric>

accumulate接收三个参数,前两个指出了需要求和的元素范围第三个参数是和的初值

accumulate的第三个参数的类型决定了函数中使用哪个加法运算符以及返回值的类型

#include <iostream>
#include <numeric>
#include <vector>using namespace std;int main()
{vector<int > num{1,2,3,4,5,6,7,8,9};vector<string> str{"123","456","789"};int num_sum = accumulate(num.cbegin(),num.cend(),0);string str_sum = accumulate(str.cbegin(),str.cend(),string(""));cout << "num_sum = " << num_sum << endl;cout << "str_sum = " << str_sum << endl;return 0;
}

Q:假设v是vector<double >,那么调用accumulate(v.cbegin(),v.cend(),0)是否正确?

错误

#include <iostream>
#include <vector>
#include <algorithm>using namespace std;int main()
{vector<double > num{1.2,2.8,3.1};//使用(double)0将accumulate指明每个元素都是double类型的//否则就是1+2+3 = 6auto sum = accumulate(num.cbegin(),num.cend(),(double)0);cout << "sum = " << sum << endl;return 0;
}

④ 额外的操作

参考:C++(STL) all_of、any_of及none_of算法详解

1.2 写容器元素的算法、

一些算法将新值赋予序列中的元素。当我们使用这类算法时,必须注意确保序列原大小至少不小于我们要求写入的元素数目(原.size() >= 写入元素数目

① fill:将一个值赋给一个范围内的元素

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;int main()
{vector<int > num{1,1,2,2,3,3};fill(num.begin(),prev(num.end()),0);for(auto i : num)cout << i << endl;return 0;
}

注意:此处由于是对容器执行写操作,故不能使用num.cbegin()

输出:

② fill_n:接受一个单迭代器、一个计数值和一个值。它将给定的值赋予迭代器指向的元素开始的指定个元素。

vector<int > num = {1,1,2,2,3,3};
fill_n(num.begin()+1,1,0);
for(auto i : num)cout << i << endl;

输出:

向目的位置迭代器写入数据的算法假定目的位置足够大,能容纳要写入的元素。不能向空容器进行写入操作。

③ back_insert

头文件:

#include <iterator>

back_insert接受指向容器的引用,返回一个与该容器绑定的插入迭代器。当我们通过此迭代器赋值时,赋值运算符会调用push_back将一个具有给定值的元素添加到容器中。

vector<int > num = {1};
fill_n(back_inserter(num),2,2);
for(auto i : num)cout << i << endl;

输出:

④ copy

拷贝算法是另一个向目的位置迭代器指向的输出序列中的元素写入数据的算法。此算法接受三个迭代器,前两个表示一个输入范围,第三个表示目的序列的起始位置此算法将输入范围中的元素拷贝到目的序列中。传递给copy的目的序列至少要包含于输入序列一样多的元素。(目的序列.size() >= 输入序列元素个数

copy返回的是其目标位置迭代器递增以后的值。如下列中,ret指向的是numToCopy尾后迭代器,所以*prev(ret) = 3

vector<int> num = {1,1,2,2,3,3};
vector<int> numToCopy{0,0,0,0,0,0};
copy(num.begin(),num.end(),numToCopy.begin());
for(auto i : numToCopy)cout << i << endl;

1.3 重排容器元素的算法

① sort(按照ASCII升序进行排列)

sort接受两个迭代器,表示要排序的元素范围。

更多sort的操作参见之前的博客:

按照ASCII降序进行排列:

sort(num.begin(),num.end(),greater<int>());
vector<int > num = {1,3,3,5,2,4,6,6};
sort(num.begin(),num.end(),greater<int>());
for(auto i : num)cout << i << endl;

② unique

unique接受两个迭代器,表示要排序的元素范围。

unique算法重排输入序列,将相邻的重复元素消除掉,并返回一个指向不重复值范围末尾的迭代器。

vector<int > num = {1,3,3,5,2,4,6,6};
sort(num.begin(),num.end(),greater<int>());
auto ret = unique(num.begin(),num.end());
for(auto i : num)cout << i << endl;cout << "*prev(ret) =" << *prev(ret) << endl;

可以看出:

unique并未真正改变容器大小,它只是覆盖相邻的重复元素,使得不重复地元素出现在序列开始的地方。

unique_copy:它接受第三个迭代器,表示拷贝不重复元素的目的位置

2、定制操作

2.1 向算法传递函数

谓词:

一元谓词:它只接收单一参数

二元谓词:他们有两个参数

例如:如下,使用sort版本用这个谓词来代替 < 来比较元素。

bool isShorter(const string &s1,const string &s2){return s1.size() < s2.size();
}int main()
{vector<string> str{"are","youu","ok","hello"};sort(str.begin(),str.end(),isShorter);for(auto i : str)cout << i << endl;
}

stable_sort:保持等长元素间的字典序。

2.2 lambda表达式

lambda表达式:(使用尾置返回类型 -> 对于返回类型比较复杂的函数最为有效)

[capture list](parameter list) -> return type { function body};

 

capture list(捕获列表)是一个lambda所在函数中定义的局部变量的列表(通常为空);return type(返回类型)、parameter list(参数列表)和function body(函数体)与任何普通函数是一样的。

可以忽略参数列表返回类型,但必须永远包含捕获列表和函数体。如果忽略返回类型,lambda根据函数体的代码腿短出返回类型。如果函数体只是一个return语句,则返回类型从返回的表达式的类型推断而来。否则,返回类型为void。

auto f = [](int a,int b)->int{return a+b;};cout << "f() = " << f(1,1) << endl;

输出是:f() = 2

注意:

  1. lambda不能有默认参数,所以,lambda调用的实参数目永远与形参数目相等。
  2. lamda虽然是出现在一个函数中,但是它只能使用出现在捕获列表中的局部变量
//使用捕获列表
auto f = [c](int a,int b)->int{if(a+b > c)return a+b;elsereturn c;
};
cout << "f() = " << f(1,1) << endl;

输出:f() = 10

for_each算法:

template<class InputIterator, class Function>Function for_each(InputIterator first, InputIterator last, Function fn)
{while (first!=last) {fn (*first);++first;}return fn;      // or, since C++11: return move(fn);
}

例子:

vector<int> num = {1,2,3,4,5,6,7,8,9};
for_each(num.begin(),num.end(),[](int i)->void{cout << i << endl;});

2.3 lambda捕获和返回

① lambda捕获

lambda捕获列表
[]

空捕获列表。lambda不能使用所在函数中的变量。一个lambda只有捕获变量后才能使用他们

[names]names是一个逗号分割的名字列表,这些名字都是lambda所在函数的局部变量。默认情况下,捕获列表中的变量都被拷贝。名字前如果使用了&,则采用引用捕获方式
[&]隐式捕获列表。采用引用捕获方式。lambda体中所使用的来自所在函数的实体都采用引用方式使用
[=]隐式捕获列表,采用值捕获方式。lambda体将拷贝所使用的来自所在函数的实体的值
[&, identifer_list] 
[=, identifer_list] 

可变lambda:

case1:值捕获时,要想改变所捕获的值,就必须要在参数列表首加上关键字mutable。

case2:引用捕获时,变量是否可以修改依赖于次饮用指向的是一个const类型还是一个非const类型

② 指定lambda返回类型

  • 默认情况下,如果一个lambda体包含return之外的任何语句,则编译器假定此lambda返回void
  • 当我们需要为一个lambda定义返回类型时,必须使用尾置返回类型
#include <vector>
#include <iostream>
#include <algorithm>using namespace std;int main()
{vector<int> num = {-1,2,-3,4,-5};//转化为正数transform(num.begin(),num.end(),num.begin(),[](int i){return i>0?i:-i;});//转为负数transform(num.begin(),num.end(),num.begin(),[](int i)->int{if(i >= 0){return -i;}else{return i;}});for(auto i : num)cout << i;return 0;
}

2.4 参数绑定

 

3、再探迭代器

标准库头头文件iterator中还定义了额外几种迭代器。分别是插入迭代器、流迭代器、反向迭代器、移动迭代器

插入迭代器:这些迭代器被绑定到一个容器上,可用来向容器插入元素

流迭代器:这些迭代器被绑定到输入或输出流上,可用来遍历所关联的IO流。

反向迭代器:这些迭代器向后而不是向前移动,除了forward_list之外的标准库容器都有反向迭代器

移动迭代器:这些专用的迭代器不是拷贝其中的元素,而是移动他们。

3.1 插入迭代器

插入迭代器操作
it = t在it指定的当前位置插入值t。假定c是it绑定的容器,依赖于插入迭代器的不同种类,此值分别调用c.push_back(t)、c.push_front(t)或c.insert(t,p),其中p为传递给insert的迭代器位置
*it,++it,it++这些操作虽然是存在的,但不会对it做任何事情。每个操作都返回it

插入迭代器有三种类型,差异在于元素插入的位置:

  • back_inserter创建一个使用push_back的迭代器
  • front_inserter创建一个使用push_front的迭代器
  • inserter 创建一个使用insert的迭代器。此函数接受第二个参数,这个参数必须是一个指向给定容器的迭代器。元素将被插入到给定迭代器所表示的元素之前。

只有在容器支持push_front的情况下,我们才可以使用front_inserter。类似的,只有在容器支持push_back的情况下,我们才能使用back_inserter。

vector<int> num = {1};
auto it = inserter(num,num.begin());
*it = 3;
for(auto i : num)cout << i;

输出:32

而对于front_inserter,元素总是插入到第一个元素之前

list<int> num = {1};
for(int i = 0;i < 5;i++)*(front_inserter(num)) = i;
auto it = inserter(num,num.begin());
*it = 3;
for(auto i : num)cout << i;

输出:432101

对于back_inserter,相当于使用了push_back

list<int> num = {1};
for(int i = 0;i < 5;i++)*(back_inserter(num)) = i;    //使用back_inserter
auto it = inserter(num,num.begin());
*it = 3;
for(auto i : num)cout << i;

输出:

3.2 iostream迭代器

 

3.3 反向迭代器

反向迭代器就是在容器中从尾元素向首元素反向移动的迭代器。对于反向迭代器,递增(以及递减)操作的含义会颠倒过来。递增一个反向迭代器(++it)会移动到迁移费元素;递减一个迭代器(--it)会移动到下一个元素。可以通过rbegin、rend、crbegin和crend成员函数来获得反向迭代器。

除了forward_list之外,其他容器都支持反向迭代器。

4、泛型算法结构

4.1 类迭代器

 

4.2 算法形参模式

 

4.3算法命名规范

 

5、特定容器算法

 

 

这篇关于【10】C++之泛型算法的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/359868

相关文章

不懂推荐算法也能设计推荐系统

本文以商业化应用推荐为例,告诉我们不懂推荐算法的产品,也能从产品侧出发, 设计出一款不错的推荐系统。 相信很多新手产品,看到算法二字,多是懵圈的。 什么排序算法、最短路径等都是相对传统的算法(注:传统是指科班出身的产品都会接触过)。但对于推荐算法,多数产品对着网上搜到的资源,都会无从下手。特别当某些推荐算法 和 “AI”扯上关系后,更是加大了理解的难度。 但,不了解推荐算法,就无法做推荐系

康拓展开(hash算法中会用到)

康拓展开是一个全排列到一个自然数的双射(也就是某个全排列与某个自然数一一对应) 公式: X=a[n]*(n-1)!+a[n-1]*(n-2)!+...+a[i]*(i-1)!+...+a[1]*0! 其中,a[i]为整数,并且0<=a[i]<i,1<=i<=n。(a[i]在不同应用中的含义不同); 典型应用: 计算当前排列在所有由小到大全排列中的顺序,也就是说求当前排列是第

【C++ Primer Plus习题】13.4

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: main.cpp #include <iostream>#include "port.h"int main() {Port p1;Port p2("Abc", "Bcc", 30);std::cout <<

csu 1446 Problem J Modified LCS (扩展欧几里得算法的简单应用)

这是一道扩展欧几里得算法的简单应用题,这题是在湖南多校训练赛中队友ac的一道题,在比赛之后请教了队友,然后自己把它a掉 这也是自己独自做扩展欧几里得算法的题目 题意:把题意转变下就变成了:求d1*x - d2*y = f2 - f1的解,很明显用exgcd来解 下面介绍一下exgcd的一些知识点:求ax + by = c的解 一、首先求ax + by = gcd(a,b)的解 这个

C++包装器

包装器 在 C++ 中,“包装器”通常指的是一种设计模式或编程技巧,用于封装其他代码或对象,使其更易于使用、管理或扩展。包装器的概念在编程中非常普遍,可以用于函数、类、库等多个方面。下面是几个常见的 “包装器” 类型: 1. 函数包装器 函数包装器用于封装一个或多个函数,使其接口更统一或更便于调用。例如,std::function 是一个通用的函数包装器,它可以存储任意可调用对象(函数、函数

综合安防管理平台LntonAIServer视频监控汇聚抖动检测算法优势

LntonAIServer视频质量诊断功能中的抖动检测是一个专门针对视频稳定性进行分析的功能。抖动通常是指视频帧之间的不必要运动,这种运动可能是由于摄像机的移动、传输中的错误或编解码问题导致的。抖动检测对于确保视频内容的平滑性和观看体验至关重要。 优势 1. 提高图像质量 - 清晰度提升:减少抖动,提高图像的清晰度和细节表现力,使得监控画面更加真实可信。 - 细节增强:在低光条件下,抖

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�

【数据结构】——原来排序算法搞懂这些就行,轻松拿捏

前言:快速排序的实现最重要的是找基准值,下面让我们来了解如何实现找基准值 基准值的注释:在快排的过程中,每一次我们要取一个元素作为枢纽值,以这个数字来将序列划分为两部分。 在此我们采用三数取中法,也就是取左端、中间、右端三个数,然后进行排序,将中间数作为枢纽值。 快速排序实现主框架: //快速排序 void QuickSort(int* arr, int left, int rig

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

06 C++Lambda表达式

lambda表达式的定义 没有显式模版形参的lambda表达式 [捕获] 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 有显式模版形参的lambda表达式 [捕获] <模版形参> 模版约束 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 含义 捕获:包含零个或者多个捕获符的逗号分隔列表 模板形参:用于泛型lambda提供个模板形参的名