第十四章 STL算法第二节
常用的算法
-
常用的查找算法:
adjacent_find()( adjacent 是邻近的意思) binary_search() count() count_if() equal_range() find() find_if() -
常用的排序算法:
merge() sort() random_shuffle()(shuffle是洗牌的意思) reverse() -
常用的拷贝和替换算法:
copy() replace() replace_if() swap() -
常用的算术和生成算法:
accumulate()( accumulate 是求和的意思) fill() -
常用的集合算法:
set_union() set_intersection() set_difference() -
常用的遍历算法:
for_each() transform()( transform 是变换的意思)
常用的遍历算法
- for_each()
- 作用:用指定函数依次对指定范围内所有元素进行迭代访问。
-
函数定义。For_each(begin, end, func);
template<class _InIt,class _Fn> inline _Fn for_each(_InIt _First, _InIt _Last, _Fn _Func) { // perform function for each element [_First, _Last) _Adl_verify_range(_First, _Last); auto _UFirst = _Get_unwrapped(_First); const auto _ULast = _Get_unwrapped(_Last); for (; _UFirst != _ULast; ++_UFirst) { _Func(*_UFirst); } return (_Func); }- 有3个参数
- _Func:可以是函数地址,或者函数对象
- 函数对象可以记录当前的调用状态,这是函数对象特有的功能
- 返回值是_Func,即返回值是传入的函数地址或者函数对象
-
代码举例:
void printVection(vector<int>&vx) { for (vector<int>::iterator it=vx.begin(); it != vx.end(); it++) { cout << *it << " "; } } void showv1test(const int &n) { cout << n << endl; } class ShowVector { public: ShowVector() { //初始化成员变量 callcount = 0; } void operator()(const int &x) { cout << x << " "; callcount++; } void printCallCount() { cout <<"调用次数---"<< callcount << endl; } private: //记录当前函数对象,调用的次数 int callcount; }; void tests1() { vector<int> v1; v1.push_back(1); v1.push_back(3); v1.push_back(5); //for循环 //printVection(v1); //第三个参数传函数地址 //for_each(v1.begin(), v1.end(), showv1test); //第三个函数传函数匿名对象 //for_each(v1.begin(), v1.end(), ShowVector()); ShowVector shovx; //这么调用打印次数为0,为什么呢? //for_each(v1.begin(), v1.end(), shovx); //这么调用才会打印实际的值3 shovx = for_each(v1.begin(), v1.end(), shovx); shovx.printCallCount(); }-
疑问:下面有什么不同呢?
//这么调用打印次数为0,为什么呢? //for_each(v1.begin(), v1.end(), shovx); //这么调用才会打印实际的值 shovx = for_each(v1.begin(), v1.end(), shovx); - 因为for_each函数参数传入shovx函数对象时,函数内部并非直接用的是外部的对象shovx,而是通过shovx拷贝一个新的函数对象,在函数内部使用
- 因此,外部的shovx对象仍然没有变化,真正变化的是内部拷贝的那个函数对象,而此时,内部拷贝的对象有作为函数返回值,返回出来了
- 所以要想拿到真正的调用结果,必须接收函数调用结果,才能获取到真正调用的值
- 总结:函数对象做参数:实参初始化形参本质会调用形参对应类的拷贝构造函数,初始化一个形参变量
-
- transform()
- 作用: 与for_each类似,遍历所有元素,但可对容器的元素进行修改
- transform()算法有两种形式:
transform(b1, e1, b2, op)transform(b1, e1, b2, b3, op)
-
声明:
template<class _InIt,class _OutIt,class _Fn1> inline _OutIt transform(_InIt _First, _InIt _Last, _OutIt _Dest, _Fn1 _Func) template<class _InIt1,class _InIt2,class _OutIt,class _Fn> inline _OutIt transform(const _InIt1 _First1, const _InIt1 _Last1, const _InIt2 _First2, _OutIt _Dest, _Fn _Func) - 使用情况:
- 例如:可以一个容器的元素,通过op(函数地址,或者函数对象),变换到另一个容器中(同一个容器中),也可以把两个容器的元素,通过op,变换到另一个容器中
- 使用注意:
- 如果目标与源相同,transform()就和for_each()一样
- 如果想以某值替换符合规则的元素,应使用replace()算法更高效
-
举例使用
void printlist(list<int> &listx) { for (list<int>::iterator it = listx.begin(); it != listx.end(); it++) { cout << *it << endl; } } int increase(int &x) { return x + 100; } void test2() { vector<int> v1; v1.push_back(1); v1.push_back(3); v1.push_back(5); //1. 同一个容器 //transform(v1.begin(), v1.end(), v1.begin(), increase); //printVection(v1); //使用预定义函数对象:negate:将元素变为负数 /*transform(v1.begin(), v1.end(), v1.begin(), negate<int>()); printVection(v1);*/ //2. 不同的容器 //使用函数适配器 //list<int> list1; //list1.resize(v1.size()); ////将v1中的每个元素都乘以10,放到容器list1中 //transform(v1.begin(), v1.end(), list1.begin(), bind2nd(multiplies<int>(),10)); //printlist(list1); //transform:也可以把运算结果放到屏幕上 //ostream_iterator需要包含:#include<iterator> transform(v1.begin(), v1.end(), ostream_iterator<int>(cout, " "), negate<int>()); }
- for_each()和transform()算法比较
- for_each()和transform()都需要传入一个函数对象,那么这两个可以共用一个函数对象吗?这两个函数对象有区别吗?
- for_each所使用的函数对象,参数是引用,没有返回值
- transform所使用的函数对象,参数一般不使用引用,而且还有返回值
for_each():速度快,不灵活transform()速度慢,非常灵活-
源码分析:
template<class _InIt,class _OutIt,class _Fn> inline _OutIt transform(const _InIt _First, const _InIt _Last, _OutIt _Dest, _Fn _Func){ // transform [_First, _Last) with _Func _Adl_verify_range(_First, _Last); auto _UFirst = _Get_unwrapped(_First); const auto _ULast = _Get_unwrapped(_Last); auto _UDest = _Get_unwrapped_n(_Dest, _Idl_distance<_InIt>(_UFirst, _ULast)); for (; _UFirst != _ULast; ++_UFirst, (void)++_UDest) { //从这里可以看出这个函数必须有返回值!!! *_UDest = _Func(*_UFirst); } _Seek_wrapped(_Dest, _UDest); return (_Dest); }
-
举例
//一般情况下:for_each所使用的函数对象,参数是引用,没有返回值 void mysquare(int &num) { num = num * num; } //transform所使用的函数对象,参数一般不使用引用,而且还有返回值 int mysquare2(int num) //结果的传出,必须是通过返回值 { return num = num * num; } void main_foreach_pk_tranform() { vector<int> v1; v1.push_back(1); v1.push_back(3); v1.push_back(5); vector<int>v2 = v1; for_each(v1.begin(), v1.end(), mysquare); printAA(v1); cout << endl; transform(v2.begin(), v2.end(), v2.begin(), mysquare2); printAA(v2); cout << endl; }
- for_each()和transform()都需要传入一个函数对象,那么这两个可以共用一个函数对象吗?这两个函数对象有区别吗?
常用的查找算法
- adjacent_find()
- 在iterator对标识元素范围内,查找一对相邻重复元素,找到则返回指向这对元素的第一个元素的迭代器。否则返回past-the-end
-
代码举例:
void test3() { vector<int> vecInt; vecInt.push_back(1); vecInt.push_back(2); vecInt.push_back(2); vecInt.push_back(4); vecInt.push_back(5); vecInt.push_back(5); //adjacent_find vector<int>::iterator it = adjacent_find(vecInt.begin(), vecInt.end()); if (it != vecInt.end()){ //*it == 2 cout << *it << endl; } else { cout << "no have" << endl; } //定位到位置 int index = distance(vecInt.begin(), it); //1 :第一个2 cout << index << endl; }
- binary_search
- 在有序序列中查找value,找到则返回true。(本质是二分法查找)
- 注意:在无序序列中,不可使用
-
举例:
void test4() { set<int> setInt; setInt.insert(3); setInt.insert(1); setInt.insert(7); setInt.insert(5); setInt.insert(9); bool bFind = binary_search(setInt.begin(), setInt.end(), 5); }
- count()
- 利用等于操作符,把标志范围内的元素与输入值比较,返回相等的个数
-
举例:
void test5() { vector<int> vecInt; vecInt.push_back(1); vecInt.push_back(2); vecInt.push_back(2); vecInt.push_back(4); vecInt.push_back(2); vecInt.push_back(5); int iCount = count(vecInt.begin(), vecInt.end(), 2); //iCount==3 }
- count_if()
- 假设vector
vecIntA,vecIntA包含1,3,5,7,9元素 -
举例:
//先定义比较函数 bool GreaterThree(int iNum){ if(iNum>=3){ return true; }else{ return false; } } int iCount = count_if(vecIntA.begin(), vecIntA.end(), GreaterThree); //此时iCount == 4
- 假设vector
- find()
- find: 利用底层元素的等于操作符,对指定范围内的元素与输入值进行比较。当匹配时,结束搜索,返回该元素的迭代器。
-
equal_range: 返回一对iterator,第一个表示lower_bound,第二个表示upper_bound。
-
举例:
vector<int> vecInt; vecInt.push_back(1); vecInt.push_back(3); vecInt.push_back(5); vecInt.push_back(7); vecInt.push_back(9); vector<int>::iterator it = find(vecInt.begin(), vecInt.end(), 5); //*it == 5
- find_if()
- find_if: 使用输入的函数代替等于操作符执行find。返回被找到的元素的迭代器。
- 假设vector
vecIntA,vecIntA包含1,3,5,3,9元素 vector<int>::it = find_if(vecInt.begin(),vecInt.end(),GreaterThree);- 此时
*it==3, *(it+1)==5, *(it+2)==3, *(it+3)==9
常用的排序算法
- merge()
- 以下是排序和通用算法:提供元素排序策略
- merge:合并两个有序序列,存放到另一个序列。
-
举例:
void test6() { vector<int> vec1; vec1.push_back(1); vec1.push_back(3); vec1.push_back(5); vector<int> vec2; vec2.push_back(2); vec2.push_back(4); vec2.push_back(6); vector<int> vec3; vec3.resize(vec1.size() + vec2.size()); merge(vec1.begin(), vec1.end(), vec2.begin(), vec2.end(), vec3.begin()); //123456 printVection(vec3); }
- sort()
- sort: 以默认升序的方式重新排列指定范围内的元素。若要改排序规则,可以输入比较函数。
-
举例:
//学生类 Class CStudent: { public: CStudent(int iID, string strName) { m_iID=iID; m_strName=strName; } public: int m_iID; string m_strName; } //学号比较函数 bool Compare(const CStudent &stuA,const CStudent &stuB) { return (stuA.m_iID<strB.m_iID); } void main() { vector<CStudent> vecStu; vecStu.push_back(CStudent(2,"老二")); vecStu.push_back(CStudent(1,"老大")); vecStu.push_back(CStudent(3,"老三")); vecStu.push_back(CStudent(4,"老四")); sort(vecStu.begin(),vecStu.end(),Compare); // 此时,vecStu容器包含了按顺序的"老大对象","老二对象","老三对象","老四对象" }
- random_shuffle()
random_shuffle:对指定范围内的元素随机调整次序。srand(time(0)); //设置随机种子-
举例:
vector<int> vecInt; vecInt.push_back(1); vecInt.push_back(3); vecInt.push_back(5); vecInt.push_back(7); vecInt.push_back(9); string str("itcastitcast "); random_shuffle(vecInt.begin(), vecInt.end()); //随机排序,结果比如:9,7,1,5,3 random_shuffle(str.begin(), str.end());//随机排序,结果比如:" itstcasticat "
- reverse()
-
举例:
vector<int> vecInt; vecInt.push_back(1); vecInt.push_back(3); vecInt.push_back(5); vecInt.push_back(7); vecInt.push_back(9); reverse(vecInt.begin(), vecInt.end()); //{9,7,5,3,1}
-
常用的拷贝和替换算法
- copy()
-
举例:
vector<int> vecIntA; vecIntA.push_back(1); vecIntA.push_back(3); vecIntA.push_back(5); vecIntA.push_back(7); vecIntA.push_back(9); vector<int> vecIntB; vecIntB.resize(5); //扩大空间 copy(vecIntA.begin(), vecIntA.end(), vecIntB.begin()); //vecIntB: {1,3,5,7,9}
-
- replace()
replace(beg,end,oldValue,newValue):将指定范围内的所有等于oldValue的元素替换成newValue。-
举例:
vector<int> vecIntA; vecIntA.push_back(1); vecIntA.push_back(3); vecIntA.push_back(5); vecIntA.push_back(3); vecIntA.push_back(9); replace(vecIntA.begin(), vecIntA.end(), 3, 8); //{1,8,5,8,9}
- replace_if()
- replace_if : 将指定范围内所有操作结果为true的元素用新值替换
- 用法举例:
replace_if(vecIntA.begin(),vecIntA.end(),GreaterThree,newVal)- 其中 vecIntA是用vector
声明的容器 - GreaterThree 函数的原型是 bool GreaterThree(int iNum)
-
举例
//把大于等于3的元素替换成8 vector<int> vecIntA; vecIntA.push_back(1); vecIntA.push_back(3); vecIntA.push_back(5); vecIntA.push_back(3); vecIntA.push_back(9); replace_if(vecIntA.begin(), vecIntA.end(), GreaterThree, 8); // GreaterThree的定义在上面。
- swap()
- swap: 交换两个容器的元素
-
举例:
vector<int> vecIntA; vecIntA.push_back(1); vecIntA.push_back(3); vecIntA.push_back(5); vector<int> vecIntB; vecIntB.push_back(2); vecIntB.push_back(4); swap(vecIntA, vecIntB); //交换
常用的算术和生成算法
- accumulate()
- accumulate: 对指定范围内的元素求和,然后结果再加上一个由val指定的初始值。
-
举例:
#include<numeric> vector<int> vecIntA; vecIntA.push_back(1); vecIntA.push_back(3); vecIntA.push_back(5); vecIntA.push_back(7); vecIntA.push_back(9); int iSum = accumulate(vecIntA.begin(), vecIntA.end(), 100); //iSum==125
- fill()
- fill: 将输入值赋给标志范围内的所有元素。
-
举例:
vector<int> vecIntA; vecIntA.push_back(1); vecIntA.push_back(3); vecIntA.push_back(5); vecIntA.push_back(7); vecIntA.push_back(9); fill(vecIntA.begin(), vecIntA.end(), 8); //8, 8, 8, 8, 8
常用的集合算法
- set_union(),set_intersection(),set_difference()
- set_union: 构造一个有序序列,包含两个有序序列的并集
- set_intersection: 构造一个有序序列,包含两个有序序列的交集。
- set_difference: 构造一个有序序列,该序列保留第一个有序序列中存在而第二个有序序列中不存在的元素
-
举例:
vector<int> vecIntA; vecIntA.push_back(1); vecIntA.push_back(3); vecIntA.push_back(5); vecIntA.push_back(7); vecIntA.push_back(9); vector<int> vecIntB; vecIntB.push_back(1); vecIntB.push_back(3); vecIntB.push_back(5); vecIntB.push_back(6); vecIntB.push_back(8); vector<int> vecIntC; vecIntC.resize(10); //并集 set_union(vecIntA.begin(), vecIntA.end(), vecIntB.begin(), vecIntB.end(), vecIntC.begin()); //vecIntC : {1,3,5,6,7,8,9,0,0,0} //交集 fill(vecIntC.begin(),vecIntC.end(),0); set_intersection(vecIntA.begin(), vecIntA.end(), vecIntB.begin(), vecIntB.end(), vecIntC.begin()); //vecIntC: {1,3,5,0,0,0,0,0,0,0} //差集 fill(vecIntC.begin(),vecIntC.end(),0); set_difference(vecIntA.begin(), vecIntA.end(), vecIntB.begin(), vecIntB.end(), vecIntC.begin()); //vecIntC: {7,9,0,0,0,0,0,0,0,0}
STL的案例举例
- 情景描述
- 某市举行一场演讲比赛( speech_contest ),共有24个人参加。比赛共三轮,前两轮为淘汰赛,第三轮为决赛。
- 比赛方式:分组比赛,每组6个人;选手每次要随机分组,进行比赛;
- 第一轮分为4个小组,每组6个人。比如100-105为一组,106-111为第二组,依次类推,每人分别按照抽签(draw)顺序演讲。当小组演讲完后,淘汰组内排名最后的三个选手,然后继续下一个小组的比赛。
- 第二轮分为2个小组,每组6人。比赛完毕,淘汰组内排名最后的三个选手,然后继续下一个小组的比赛。
- 第三轮只剩下6个人,本轮为决赛,选出前三名。
- 比赛评分:10个评委打分,去除最低、最高分,求平均分
- 每个选手演讲完由10个评委分别打分。该选手的最终得分是去掉一个最高分和一个最低分,求得剩下的8个成绩的平均分。
- 选手的名次按得分降序排列,若得分一样,按参赛号升序排名。
- 需求
- 请打印出所有选手的名字与参赛号,并以参赛号的升序排列。
- 打印每一轮比赛后,小组比赛成绩和小组晋级名单
- 打印决赛前三名,选手名称、成绩。
- 分析实现思路:
- 产生选手 ( ABCDEFGHIJKLMNOPQRSTUVWXYZ ) 姓名、得分;选手编号
- 第1轮 选手抽签 选手比赛 查看比赛结果
- 第2轮 选手抽签 选手比赛 查看比赛结果
- 第3轮 选手抽签 选手比赛 查看比赛结果
- 技术分析:
- 需要把选手信息、选手得分信息、选手比赛抽签信息、选手的晋级信息保存在容器中,需要涉及到各个容器的选型。
- 选手可以设计一个类Speaker(姓名和得分)
- 所有选手编号和选手信息,可以放在容器内:map<int, Speaker>
- 所有选手的编号信息,可以放在容器:vecter
v1中 - 第1轮晋级名单,可以放在容器vecter
v2中 - 第2轮晋级名单,可以放在容器vecter
v3中 - 第3轮前三名名单,可以放在容器vecter
v4中 - 每个小组的比赛得分信息,按照从小到大的顺序放在multimap<成绩, 编号, greater
> multmapGroup 也就是:multimap<int, int, greater > multmapGroup; - 每个选手的得分,可以放在容器deque
dscore; 方便去除最低最高分
-
代码实现
#include<iostream> #include "string" #include <vector> #include <list> #include "set" #include <algorithm> #include "functional" #include "iterator" #include<numeric> #include "map" #include "deque" using namespace std; class Speaker { public: string m_name; //姓名 int m_score[3];//用于记录3组得分 private: }; //产生所有比赛的选手 int getSpeakers(map<int, Speaker> &speakers, vector<int> &v) { string str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; //打乱顺序 random_shuffle(str.begin(), str.end()); //创建24个选手对象,并放入speakers容器中 for (int i = 0; i<24; i++) { Speaker tem; tem.m_name = "选手"; tem.m_name = tem.m_name + str[i]; //将选手编号放入map容器中 speakers.insert(pair<int, Speaker>(100+i, tem)); } //生成参赛名单(编号)放入v1容器中 for (int i = 0; i < 24; i++) { v.push_back(100 + i); } return 0; } //选手抽签 int speech_contest_daraw(vector<int> &v) { //直接将容器打乱顺序即可 random_shuffle(v.begin(), v.end()); return 0; } //选手比赛 int speech_contest(int index, vector<int> &v1, map<int,Speaker> &speaker, vector<int> &v2) { //小组比赛得分记录下来,求出前3名 //由于得分可以是重复的,因此采用multimap<得分,编号,排序方式> multimap<int, int, greater<int>> multimapGroups; //用于记录组 int tempCount = 0; //遍历参赛名单进行参赛 for (vector<int>::iterator it = v1.begin(); it != v1.end(); it++) { //10个评委打分 { tempCount++; //频繁的处理收尾元素,因此采用这个容器 deque<int> dscore; for (int j = 0; j < 10; j++) { int score = 50 + rand() % 50; dscore.push_back(score); } //评委分排序 sort(dscore.begin(), dscore.end()); //去除最低分,去除最高分 dscore.pop_back(); dscore.pop_front(); //求平均分 int scoresum = accumulate(dscore.begin(), dscore.end(), 0); int scoreavg = scoresum / dscore.size(); //将当前组的平均分赋值给对应的对象 speaker[*it].m_score[index] = scoreavg; //将当前选手得分放入小组容器中 multimapGroups.insert(pair<int, int>(scoreavg, *it)); } //分组:每6个为一组 if (tempCount%6==0) { cout<<"第"<<index+1 << "轮小组的比赛成绩====" << endl; for (multimap<int,int,greater<int>>::iterator it =multimapGroups.begin();it!=multimapGroups.end();it++) { //编号 姓名 得分 cout << it->second << "\t" << speaker[it->second].m_name << "\t" << it->first << endl; } //前3名晋级,放到v2中 while (multimapGroups.size()>3) { multimap<int, int, greater<int>>::iterator it = multimapGroups.begin(); v2.push_back(it->second); //前3名的编号放入 //删除 multimapGroups.erase(it); } //使用完清空,待下一轮使用 multimapGroups.clear(); } } return 0; } //查看比赛结果 int speech_contest_print(int index,vector<int> &v, map<int, Speaker> &speaker){ printf("第%d轮 晋级名单\n", index+1); for (vector<int>::iterator it = v.begin();it != v.end();it++){ cout << "参赛编号:" << *it << endl; cout << "姓名:" << speaker[*it].m_name << endl; cout << "分数:" << speaker[*it].m_score[index] << endl; } return 0; } int main() { //容器 map<int, Speaker> mapSpeakers; //所有参加比赛的选手 vector<int> v1; //第一轮比赛结果名单 vector<int> v2; //第二轮比赛结果名单 vector<int> v3; //第三轮比赛结果名单 vector<int> v4; //比赛结果名单 //生成所有的比赛选手 getSpeakers(mapSpeakers, v1); //产生选手,得到第一轮选手的名单 //第一轮 选手抽签、选手比赛、查看比赛结果 cout << "\n\n\n任意键开始第一轮比赛" << endl; cin.get(); speech_contest_daraw(v1); speech_contest(0, v1, mapSpeakers, v2); speech_contest_print(0,v2, mapSpeakers); //第二轮 选手抽签、选手比赛、查看比赛结果 cout << "\n\n\n任意键开始第二轮比赛" << endl; cin.get(); speech_contest_daraw(v2); speech_contest(1, v2, mapSpeakers, v3); speech_contest_print(1, v3, mapSpeakers); //第三轮 选手抽签、选手比赛、查看比赛结果 cout << "\n\n\n任意键开始第三轮比赛" << endl; cin.get(); speech_contest_daraw(v3); speech_contest(2, v3, mapSpeakers, v4); speech_contest_print(2, v4, mapSpeakers); getchar(); return 0; }