创作背景:
作者于寒假在家学习中国大学MOOC上的,来自北京大学郭炜老师的程序设计与算法的系列课程,为巩固学习成果,同时备赛蓝桥杯,将这一系列的学习笔记记录在CSDN上。
写在前面:
C++中的STL部分是一个高效的程序库,包括容器、迭代器、算法、仿函数、迭代适配器、空间配置器这六个组件,本文中,作者将仅仅介绍来自于程序设计与算法课程中老师讲解过的几个重要知识点,其它部分不做重点。
一、STL排序算法sort
1、sort函数的三个参数:
(1)排序数组的起始地址
(2)排序数组的结束地址
(3)排序的方法(sort可以从大到小排序,也可以从小到大排序,甚至还可以自己进行拓展,自行定义排序方式,如果第三个参数不说明的话,默认为从小到大排序)
模板:sort(start,end,排序方法)
2、sort的基本功能
(1)从小到大排序
#include<cstdio>
#include<string>
#include<iostream>
#include<algorithm>
using namespace std;
int main()
{
int arr[10] = { 12,32,43,45,67,7,1,78,14,4 }; //定义一个大小为10的数组,完全乱序
sort(arr, arr + 10); //使用sort进行从小到大排序
for (int i = 0; i < 10; i++)
cout << arr[i]<<' ';
cout << endl;
return 0;
}
//此时输出的结果已是排好序的了 arr[10] = { 1,4,7,12,14,32,43,45,67,78 };
(2)从大到小排序
在不表明第三个参数的情况下,sort默认从小到大排序,想要实现从大到小,只需添加第三个参数greater<数据类型>()即可。
#include<cstdio>
#include<string>
#include<iostream>
#include<algorithm>
using namespace std;
int main()
{
int arr[10] = { 12,32,43,45,67,7,1,78,14,4 }; //定义一个大小为10的数组,完全乱序
sort(arr, arr + 10, greater<int>()); //使用sort,并添加第三个参数greater<int>(),以此实现从大到小排序
for (int i = 0; i < 10; i++)
cout << arr[i]<<' ';
cout << endl;
return 0;
}
//此时输出的结果已是排好的从大到小的数组了 arr[10] = { 78,67,45,43,32,14,12,7,4,1 };
(3)用自己定义的规则,对数组进行排序
sort不仅可以向上边那样进行简单的排序,还可以实现其他更多功能的排序,关键在于第三个参数cmp,将想要实现的功能在cmp中进行设计,再将其添加到sort中。
a.在cmp中设计从大到小的排序
#include<cstdio>
#include<string>
#include<iostream>
#include<algorithm>
using namespace std;
bool cmp(int a, int b) //int为数据类型
{
return a > b; //降序排列,简单粗暴
}
int main()
{
int arr[10] = { 12,32,43,45,67,7,1,78,14,4 }; //定义一个大小为10的数组,完全乱序
sort(arr, arr + 10, cmp); //使用sort,并添加第三个我们自己定义的参数cmp,以此实现从大到小排序
for (int i = 0; i < 10; i++)
cout << arr[i]<<' ';
cout << endl;
return 0;
}
//此时输出的结果已是排好的从大到小的数组了 arr[10] = { 78,67,45,43,32,14,12,7,4,1 };
b.在cmp中实现对个位数大小从小到大的排序
#include<cstdio>
#include<string>
#include<iostream>
#include<algorithm>
using namespace std;
bool cmp(int a, int b) //int为数据类型
{
return a % 10 < b % 10; //计算出目标数的个位数大小,并进行升序排列
}
int main()
{
int arr[10] = { 19,32,43,47,67,96,1,78,14,55 }; //定义一个大小为10的数组,完全乱序
sort(arr, arr + 10, cmp); //使用sort,并添加第三个我们自己定义的参数cmp,以此实现个位数大小从小到大排序
for (int i = 0; i < 10; i++)
cout << arr[i]<<' ';
cout << endl;
return 0;
}
//此时输出的结果已是排好的从大到小的数组了 arr[10] = { 1,32,43,14,55,96,47,67,78,19 };
c. 在cmp中对结构体进行排序
举例说明:读入 n(>0)名学生的姓名、学号、成绩,以成绩从大到小的顺序输出他们的个人信息
#include<cstdio>
#include<string>
#include<iostream>
#include<algorithm>
using namespace std;
struct student //定义结构体,结构体中包括学生姓名,学号,分数
{
char name[20];
char id[20];
int score;
};
bool com(student a, student b)
{
return a.score > b.score; //定义参数com,按分数从大到小的顺序排序
}
int main()
{
int n;
cin >> n;
struct student stu[1000];
for (int i = 0; i < n; i++)
{
cin >> stu[i].name >> stu[i].id >> stu[i].score;
}
sort(stu, stu + n, com); //使用sort函数对结构体进行排序
for (int i = 0; i < n; i++)
{
cout << stu[i].name << ' ' << stu[i].id << ' ' << stu[i].score << endl;
}
return 0;
}
输入样例:
3
Joe Math990112 89
Mike CS991301 100
Mary EE990830 95
输出样例:
Mike CS991301 100
Mary EE990830 95
Joe Math990112 89
总结:sort并不是简单的快速排序,它对普通的快速排序进行了优化,此外,它还结合了插入排序和推排序。系统会根据你的数据形式和数据量自动选择合适的排序方法,这并不是说它每次排序只选择一种方法,它是在一次完整排序中不同的情况选用不同方法,比如给一个数据量较大的数组排序,开始采用快速排序,分段递归,分段之后每一段的数据量达到一个较小值后它就不继续往下递归,而是选择插入排序,如果递归的太深,他会选择推排序。其时间复杂度为n*log2n,比冒泡排序、选择排序这些算法快的多。
二、STL二分查找算法
1、binary_search的三个参数
(1)目标数组的起始位置
(2)目标数组的终止位置
(3)所要查找的目标值
模板:binary_search(start,end,目标值)
注:查找范围为[n1,n2),n2不在查找范围内。
在该区间内查找等于目标值的元素,找到返回true(1),没有找到返回false(0)。
等于的含义:a=b <=> a<b和b<a都不成立
binary_search的查找顺序必须和数组的排序顺序相同,否则查找出来的值是没有意义的。
2、binary_search的基本功能和使用方法
在排好序的数组中查找目标元素
#include<cstdio>
#include<string>
#include<iostream>
#include<algorithm>
using namespace std;
int main()
{
int arr[10] = { 4,3,6,9,2,8,1,7,5,10 };
sort(arr,arr+10);
cout << binary_search(arr, arr + 10, 8) << endl; //在数组arr中查找8这个目标值
cout << binary_search(arr, arr + 10, 0) << endl; //在数组arr中查找0这个目标值
return 0;
}
//第一组返回1,第二组返回2
同时,binary_search也可以在自定义的排序方式中查找目标元素,模板为binary_search(a,a+n,目标值,排序规则结构名())
3、lower_bound的基本功能和使用方法
(1) 在排好序的数组中查找目标元素,返回一个指针,*p是查找区间里下标最小的,大于或等于目标值的元素。如果找不到,则指向下标为n2的元素。
模板:* lower_ bound(a+n1,a+n2,目标值)
lower_ bound(a+n1,a+n2,目标值) - 数组名
#include<cstdio>
#include<string>
#include<iostream>
#include<algorithm>
using namespace std;
int main()
{
int arr[10] = { 4,3,6,9,2,8,1,7,5,10 };
sort(arr, arr + 10);
cout << *lower_bound(arr, arr + 10, 8) << endl; //在数组arr中查找第一个大于等于8的元素
cout << lower_bound(arr, arr + 10, 8) - arr << endl; //在数组arr中查找第一个大于等于8的元素所在的位置
cout << *lower_bound(arr, arr + 10, 11) << endl; //在数组arr中查找第一个大于等于11的元素
return 0;
}
//指针返回了目标数组中第一个大于等于8的值,即8,其位置在7
//指针返回了n2的位置,而n2的位置是越界的
同时,lower_binary也可以在自定义的排序方式中查找目标元素,模板为lower_binary(a,a+n,目标值,排序规则结构名())
4、upper_bound的基本功能和使用方法
(1) 在排好序的数组中查找目标元素,返回一个指针,*p是查找区间里下标最小的,大于目标值的元素。如果找不到,则指向下标为n2的元素。
模板:* upper_ bound(a+n1,a+n2,目标值)
upper_ bound(a+n1,a+n2,目标值) - 数组名
#include<cstdio>
#include<string>
#include<iostream>
#include<algorithm>
using namespace std;
int main()
{
int arr[10] = { 4,3,6,9,2,8,1,7,5,10 };
sort(arr, arr + 10);
cout << *upper_bound(arr, arr + 10, 8) << endl; //在数组arr中查找第一个大于8的元素
cout << upper_bound(arr, arr + 10, 8) - arr << endl; //在数组arr中查找第一个大于8的元素所在的位置
cout << *upper_bound(arr, arr + 10, 11) << endl; //在数组arr中查找第一个大于11的元素
return 0;
}
//指针返回了目标数组中第一个大于等于8的值,即9,其位置在8
//指针返回了n2的位置,而n2的位置是越界的
同时,upper_ bound也可以在自定义的排序方式中查找目标元素,模板为upper_ bound(a,a+n,目标值,排序规则结构名())
三、STL中的平衡二叉树结构multiset和set以及multimap和map
在需要进行大量数据添加和删除,同时还要对数据进行查找,这时用sort+二分查找是行不通的,需要用到平衡二叉树结构存放数据。
multiset用法:
定义一个multiset类型的变量:multiset<type> arr ;这样就定义了一个multiset类型的变量arr,里边可以存放type类型的数据,开始时arr里是空的。
接下来展示一下multiset的简单操作以及一些函数
成员方法 | 功能 |
---|---|
c.begin() | 返回指向容器中第一个(注意,是已排好序的第一个)元素的双向迭代器。如果 multiset 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。 |
c.end() | 返回指向容器最后一个元素(注意,是已排好序的最后一个)所在位置后一个位置的双向迭代器,通常和 begin() 结合使用。如果 multiset 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。 |
find(val) | 在 multiset 容器中查找值为 val 的元素,如果成功找到,则返回指向该元素的双向迭代器;反之,则返回和 end() 方法一样的迭代器。另外,如果 multiset 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。 |
lower_bound(val) | 返回一个指向当前 multiset 容器中第一个大于或等于 val 的元素的双向迭代器。如果 multiset 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。 |
upper_bound(val) | 返回一个指向当前 multiset 容器中第一个大于 val 的元素的迭代器。如果 multiset 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。 |
equal_range(val) | 该方法返回一个 pair 对象(包含 2 个双向迭代器),其中 pair.first 和 lower_bound() 方法的返回值等价,pair.second 和 upper_bound() 方法的返回值等价。也就是说,该方法将返回一个范围,该范围中包含所有值为 val 的元素。 |
empty() | 若容器为空,则返回 true;否则 false。 |
size() | 返回当前 multiset 容器中存有元素的个数。 |
max_size() | 返回 multiset 容器所能容纳元素的最大个数,不同的操作系统,其返回值亦不相同。 |
insert() | 向 multiset 容器中插入元素。 |
erase() | 删除 multiset 容器中存储的指定元素。 |
swap() | 交换 2 个 multiset 容器中存储的所有元素。这意味着,操作的 2 个 multiset 容器的类型必须相同。 |
clear() | 清空 multiset 容器中所有的元素,即令 multiset 容器的 size() 为 0。 |
emplace() | 在当前 multiset 容器中的指定位置直接构造新元素。其效果和 insert() 一样,但效率更高。 |
emplace_hint() | 本质上和 emplace() 在 multiset 容器中构造新元素的方式是一样的,不同之处在于,使用者必须为该方法提供一个指示新元素生成位置的迭代器,并作为该方法的第一个参数。 |
count(val) | 在当前 multiset 容器中,查找值为 val 的元素的个数,并返回。 |
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<set> //使用multiset和set时必须使用该头文件
using namespace std;
int main()
{
multiset<int> h; //创建一个multiset类型的变量h
for (int i = 0; i < 10; i++)
{
int x;
cin >> x;
h.insert(x); //insert是插入函数,即向h中插入元素
}
for (auto j = h.begin(); j != h.end(); ++j) //begin()返回排好序后的第一个元素,end()返回最后一个
{
cout << *j << ' ';
}
cout << endl;
return 0;
}
更多相关函数用法参考下文
原文链接:https://blog.csdn.net/sodacoco/article/details/84798621
set用法:
set和multiset的区别在于,set中不能有重复元素,因此set在插入元素时可能不成功。
同时,大多数函数二者之间也相通,因此在这里不再做过多解释,只提一下迭代器的用法。
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<set> //使用multiset和set时必须使用该头文件
using namespace std;
int main()
{
set<int> h; //创建一个set类型的变量h
for (int i = 0; i < 10; i++)
{
int x;
cin >> x;
h.insert(x); //insert是插入函数,即向h中插入元素
}
set<int>::iterator j; //创建迭代器,接下来使用迭代器进行操作
for (j = h.begin(); j != h.end(); ++j) //begin()返回排好序后的第一个元素,end()返回最后一个
{
cout << *j << ' ';
}
cout << endl;
return 0;
}
想了解更多的内容参考这篇文章
[C++ STL] set使用详解 - fengMisaka - 博客园 (cnblogs.com)
最后的multimap和map
map和multimap是一种容器,map容器有四种,每一种都是由类模板定义的。所有类型的map容器保存的都是键值对的元素。map容器的元素是pair<const K, T>类型的对象,这种对象封装了一个T类型的对象和一个与其关联的K类型的键,但在此处,我们只学习两种,即multimap与map。
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<map> //使用map与multimap需要使用该头文件
using namespace std;
int main()
{
multimap<char, int> h; //定义一个muitimap容器的一般方法
for (int i = 0; i < 10; i++)
{
char y; //键值的类型
int x; //所保存对象的类型
cin >> y >> x;
h.insert(pair<char, int>(y, x)); //读入数据
}
for (auto j = h.begin(); j != h.end(); ++j)
{
cout << j->first << ' ' << j->second << endl; //输出容器中数据的方式,first为键值对应的数据,second为键值保存的数据
}
return 0;
}
成员方法 | 功能 |
---|---|
begin() | 返回指向容器中第一个(注意,是已排好序的第一个)键值对的双向迭代器。如果 multimap 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。 |
end() | 返回指向容器最后一个元素(注意,是已排好序的最后一个)所在位置后一个位置的双向迭代器,通常和 begin() 结合使用。如果 multimap 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。 |
rbegin() | 返回指向最后一个(注意,是已排好序的最后一个)元素的反向双向迭代器。如果 multimap 容器用 const 限定,则该方法返回的是 const 类型的反向双向迭代器。 |
rend() | 返回指向第一个(注意,是已排好序的第一个)元素所在位置前一个位置的反向双向迭代器。如果 multimap 容器用 const 限定,则该方法返回的是 const 类型的反向双向迭代器。 |
cbegin() | 和 begin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的键值对。 |
cend() | 和 end() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的键值对。 |
crbegin() | 和 rbegin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的键值对。 |
crend() | 和 rend() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的键值对。 |
find(key) | 在 multimap 容器中查找首个键为 key 的键值对,如果成功找到,则返回指向该键值对的双向迭代器;反之,则返回和 end() 方法一样的迭代器。另外,如果 multimap 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。 |
lower_bound(key) | 返回一个指向当前 multimap 容器中第一个大于或等于 key 的键值对的双向迭代器。如果 multimap 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。 |
upper_bound(key) | 返回一个指向当前 multimap 容器中第一个大于 key 的键值对的迭代器。如果 multimap 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。 |
equal_range(key) | 该方法返回一个 pair 对象(包含 2 个双向迭代器),其中 pair.first 和 lower_bound() 方法的返回值等价,pair.second 和 upper_bound() 方法的返回值等价。也就是说,该方法将返回一个范围,该范围中包含的键为 key 的键值对。 |
empty() | 若容器为空,则返回 true;否则 false。 |
size() | 返回当前 multimap 容器中存有键值对的个数。 |
max_size() | 返回 multimap 容器所能容纳键值对的最大个数,不同的操作系统,其返回值亦不相同。 |
insert() | 向 multimap 容器中插入键值对。 |
erase() | 删除 multimap 容器指定位置、指定键(key)值或者指定区域内的键值对。 |
swap() | 交换 2 个 multimap 容器中存储的键值对,这意味着,操作的 2 个键值对的类型必须相同。 |
clear() | 清空 multimap 容器中所有的键值对,使 multimap 容器的 size() 为 0。 |
emplace() | 在当前 multimap 容器中的指定位置处构造新键值对。其效果和插入键值对一样,但效率更高。 |
emplace_hint() | 在本质上和 emplace() 在 multimap 容器中构造新键值对的方式是一样的,不同之处在于,使用者必须为该方法提供一个指示键值对生成位置的迭代器,并作为该方法的第一个参数。 |
count(key) | 在当前 multimap 容器中,查找键为 key 的键值对的个数并返回。 |
以上给出了multimap和map的基本用法和常用函数,同样的,map中不能有重复元素而multimap中可以有。其他更多内容,可以参考下面这篇文章。
https://blog.csdn.net/qq_28584889/article/details/83855734
接下来,通过一道例题来深化map和set的用法。
单词词频统计:输入大量单词,每个不超过20个字符。按出现次数从大到小的顺序输出这些单词,出现次数相同的,按照字典序的先后顺序输出。
输入样例: this is ok this plus that is plus plus
输出样例: plus 3 is 2 this 2 ok 1 that 1
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<map>
#include<set>
using namespace std;
struct Word{ //定义Word类型的数据
int times;
string wd;
};
struct Rule {
bool operator()(const Word & w1, const Word & w2)const //定义set的排序规则
{
if (w1.times != w2.times)
{
return w1.times > w2.times; //当出现次数不同时,按出现次数大小排序
}
else
{
return w1.wd < w2.wd; //当出现次数相同时,按首字母大小排序
}
}
};
int main()
{
string s;
set<Word, Rule> st;
map<string, int> mp;
while (cin >> s)
{
++mp[s];
}
for (map<string, int>::iterator i = mp.begin(); i != mp.end(); ++i) //定义迭代器
{
Word tmp;
tmp.wd = i->first;
tmp.times = i->second;
st.insert(tmp); //定义tmp将map中的所有数据带入set进行下一步的排序
}
for (set<Word, Rule>::iterator i = st.begin(); i != st.end(); ++i)
{
cout << i->wd << " " << i->times << endl; //最后通过set的迭代器将排好序的数据输出出来
}
return 0;
}
此题较好的总结了map与set的综合用法,通过做这道题可以总结梳理二者的关系和用法。map与set的确是比较难的知识点,但是却非常非常实用。
结语:
至此,关于c++的STL部分的知识点笔记就结束了,万字长文,长这么大从来没有写过这么长的文章。摆烂日记系列是我给我写的算法篇的播客起的名字,这篇正是该系列的第一篇文章,受寒假过年影响(其实是我自己想玩),预计每隔两三天会再发一篇笔记,全系列预计在六万字左右(打六万的时候手抖了一下)。