前置知识:数组、字符串、vector
为了保证学习效果,请保证已经掌握前置知识之后,再来学习本章节!
迭代器
- 迭代器iterator
- 迭代器分类
一、迭代器iterator
-
迭代器(iterator):用来指向、遍历、修改容器元素的变量,类似指针。(注:没学过指针的同学,可以近似理解为数组的下标)
操作 效果 * 返回当前位置的元素值。 ++ 将迭代器移动至下一元素。 ==和!= 判断两个迭代器是否指向同一位置。 = 为迭代器赋值 -
迭代器(iterator)函数
操作 效果 begin() 返回一个迭代器,指向第一个元素。 end() 返回一个迭代器,指向最后一个元素的后一个位置。
【示例】
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> v = {7, 5, 16, 8};
vector<int>::iterator it;
for(it = v.begin(); it != v.end(); it++)
{
cout << *it << " ";
}
}
Copy
迭代器练习
说明:主要以vector的迭代器练习为主,不要使用数组的[],要求使用迭代器的遍历方式。
二、迭代器分类
常用的迭代器按功能强弱分为∶输入、输出、正向、双向、随机访问五种,这里只介绍常用的三种。
不同容器的迭代器,其功能强弱有所不同。例如,排序算法需要通过随机访问迭代器来访问容器中的元素,因此有的容器就不支持排序算法。
1、正向迭代器
-
假设 p 是一个正向迭代器,则 p 支持以下操作∶++p,p++,*p。
-
此外,两个正向迭代器可以互相赋值,还可以用==和!=运算符进行比较。
2、双向迭代器
- 双向迭代器具有正向迭代器的全部功能。
- 双向迭代器p支持--p和 p--,使得 p朝着和++p 相反的方向移动。
3、随机访问迭代器
- 随机访问迭代器具有双向迭代器的全部功能。
- 随机访问迭代器p 还支持以下操作∶
- p+=i∶使得 p 往后移动 i个元素。
- p-=i∶使得 p往前移动 i 个元素。
- p+i∶返回 p 后面第 i 个元素的迭代器。
- p-i∶返回 p 前面第 i个元素的迭代器。
- p[i]∶返回 p 后面第 i 个元素的引用。
- 两个随机访问迭代器 p1、p2 还可以用<、>、<=、>= 运算符进行比较。 p1 < p2 的含义是∶p1 经过若干次(至少一次)++操作后,就会等于 p2。
- 表达式 p2-pl表示迭代器 p2 所指向元素和迭代器p1 所指向元素的序号差(p2 和 pl 之间的元素个数减一)。
不同容器支持的迭代器
容器 | 迭代器类别 |
---|---|
vector | 随机 |
deque | 随机 |
list | 双向 |
set/multiset | 双向 |
map/multimap | 双向 |
stack | 不支持迭代器 |
queue | 不支持迭代器 |
priority_queue | 不支持迭代器 |
双向队列(deque)
deque 也是顺序容器的一种,也是一个可变长数组。要使用 deque,需要包含头文件 deque。所有适用于 vector 的操作都适用于 deque,deque在头尾增删元素性能较好。
1、deque 的特点
-
deque 和 vector 有很多类似的地方。在 deque 中,随机存取任何元素都能在常数时间内完成(但慢于 vector)。
-
它相比于 vector 的优点是,vector 在头部删除或添加元素的速度很慢,在尾部添加元素的性能较好,而 deque 在头尾增删元素都具有较好的性能(大多数情况下都能在常数时间内完成)。
2、deque 有两种 vector 没有的成员函数
- void push_front(const T& val); //将 val 插入容器的头部
- void pop_front(); //删除容器头部的元素
3、deque使用注意
- deque 支持随机存取
- deque 支持在头部和尾部存储数据
- deque 不支持 capacity 和 reserve 操作
【deque 示例】
#include <iostream>
#include <deque>
using namespace std;
void print(deque<int> dq)
{
for(auto it = dq.begin(); it != dq.end(); it++)
cout << *it << " ";
cout<<endl;
}
int main()
{
deque<int> dq = {20, 10, 30};
print(dq);
dq.push_front(15);
print(dq);
dq.pop_front();
print(dq);
}
Copy
deque练习
链表(list) *
选学。在竞赛中,用数组模拟链表更好,STL中的链表反而没那么好用。
什么是链表
list∶是一个线性双向链表结构,它的数据由若干个节点构成,每一个节点都包括一个信息块(即实际存储的数据)、一个前驱指针和一个后驱指针。
它无需分配指定的内存大小目可以任意伸缩,这是因为它存储在非连续的内存空间中,并且由指针将有序的元素链接起来。
单链表的结构如下:
双向链表结构如下:
特点
- list 随机检索的性能非常的不好,因为它不像 vector 那样直接找到元素的地址,而是要从头一个一个的顺序查找。
- 但是它可以迅速地在任何节点进行插入和删除操作,因为 list 的每个节点保存着它在链表中的位置,插入或删除一个元素仅对最多三个元素有所影响。
- list 支持双向迭代器,没有下标,必须使用迭代器遍历list。(由于不支持随机迭代器,不能写迭代器+x,迭代器-x,不能用 sort()函数,但list拥有 sort 成员函数)
【注意】∶推荐引入 #include<list>
头文件。
顺序存储结构的优缺点分析
优点
A、无需为表示结点间的逻辑关系而增加额外的存储空间;
B、可方便地随机存取表中的任一元素。
缺点
A、插入或删除平均需要移动一半的结点;
B、顺序表要求占用连续的存储空间。
list相关函数
函数名 | 函数说明 |
---|---|
push_back(元素) | 往链表尾添加一个元素 |
push_front(元素) | 往链表头添加一个元素 |
pop_back() | 在链表尾删除元素 |
pop_front() | 在链表头删除元素 |
insert(迭代器的位置,元素) | 在迭代器的位置之前插入一个元素 |
begin()、end() | 获取链表的头、尾迭代器 |
size() | 获取链表元素的个数 |
remove(元素) | 在链表中删除元素 |
【list 示例】
#include <algorithm>
#include <iostream>
#include <list>
using namespace std;
int main()
{
// 创建含整数的 list
list<int> l = { 7, 5, 16, 8 };
// 添加整数到 list 开头
l.push_front(25);
// 添加整数到 list 结尾
l.push_back(13);
// 删除 list 头的元素
l.pop_front();
// 以搜索插入 16 前的值
list<int>::iterator it = find(l.begin(), l.end(), 16);
if (it != l.end()) {
l.insert(it, 42);
}
// 删除 值为5 的元素
l.remove(5);
// 迭代并打印 list 的值
for (auto it = l.begin(); it != l.end(); it++) {
cout << *it << ' ';
}
}
Copy
集合(set)
什么是set
set 是关联容器的一种,是排序好的集合(元素已经进行了排序),set 中不能有重复的元素。
实现原理:采用红黑树结构实现的。所以查找、插入、删除元素的时间复杂度都是O(logn)O(logn)。
注意∶
- 不能直接修改 set 容器中元素的值。因为元素被修改后,容器并不会自动重新调整顺序,于是容器的有序性就会被破坏,再在其上进行查找等操作就会得到错误的结果。因此,如果要修改 set 容器中某个元素的值,正确的做法是先删除该元素,再插入新元素。
- multiset 容器就像set 容器,但它可以保存重复的元素。
- set 支持双向迭代器(不支持随机迭代器),在插入和删除时,要特别注意。
- 在 STL中使用结构体,需要对特定要求的运算符进行重载;STL 默认使用小于号来排序,因此,默认重载小于号;(如果使用greater<>比较器就需重载大于号),且要注意让比较函数对相同元素返回 false。
set相关函数
函数名 | 函数说明 |
---|---|
begin()、end() | 获取 set 容器的起始地址、结束地址 |
insert(迭代器的位置,元素)、insert(元素) | 在迭代器的位置之前插入一个元素 |
erase(开始位置,结束位置) | 删除指定位置范围内的元素 |
find() | 查找匹配的元素迭代器,若不存在则返回end() |
size() | 获取 set 元素的个数 |
empty() | 判空 |
注意∶如果要使用 set,请引入<set>
头文件,如果要引入 greater<T>
和 less<T>
比较器,请引入<functional>
头文件。
【比较器示例】
#include <bits/stdc++.h>
using namespace std;
int main()
{
int a[] = {30, 10, 30, 40, 20};
//显式设置从小到大排序
sort(a, a + 5, less<int>());
for(int i = 0; i < 5; i++) cout << a[i] << " ";
cout << endl;
//显式设置从大到小排序
sort(a, a + 5, greater<int>());
for(int i = 0; i < 5; i++) cout << a[i] << " ";
}
Copy
【set 示例1】:使用相关函数
#include <bits/stdc++.h>
using namespace std;
int main()
{
//set的两个特性:自动去重、自动排序
//默认从小到大
set<int> st = {30, 10, 30, 40, 20, 10};
//显式设置为从大到小排序
set<int, greater<int> > st2 = {30, 10, 30, 40, 20, 10};
//插入到特定位置意义不大,因为会自动排序
st.insert(st.begin(), 70);
st.insert(80);//插入元素80
//删除st中的第一个元素
st.erase(st.begin());
//删除st中值为40的元素
st.erase(40);
//查找函数
set<int>::iterator it = st.find(100);// *it = 100;//是错误的写法,不能直接修改
if(it != st.end()) cout<<*it<<"存在";
else cout<<"100不存在";
cout<<endl;
//迭代器遍历
cout << "st中的元素:";
for(it = st.begin(); it != st.end(); it++)
cout<< *it << " ";
cout << endl << "st2中的元素:";
for(it = st2.begin(); it != st2.end(); it++)
cout<< *it << " ";
}
Copy
【set 示例2】:存储结构体,重载运算符
#include <bits/stdc++.h>
using namespace std;
struct student
{
int num;
string name;
int score;
//重载operator <运算符
// 参数里面加const是为了不修改原来的对象,另外这里用引用避免了对实参的拷贝,提高效率
// 函数最后加了const后缀,表示此函数不修改成员变量,如果在函数里修改了则编译报错
bool operator <(const student &s) const
{
//规则:按照分数降序,分数相同按照学号升序
if(score != s.score)
{
return score > s.score;
}
return num < s.num;
}
//也可以重载operator >运算符
bool operator >(const student &s) const
{
//规则:按照分数降序,分数相同按照学号升序
if(score != s.score)
{
return score > s.score;
}
return num < s.num;
}
};
int main()
{
//前两种声明st的方式只需要重载<运算符
//第1种方式声明
set<student> st;
//第2种方式声明
//set<student, less<student> > st;
//第3种方式声明,显式设置st的greater比较方式,必须要重载>运算符
//set<student, greater<student> > st;
//插入结构体变量
st.insert({1, "zhao", 99});
st.insert({2, "qian", 97});
st.insert({3, "sun", 99});
st.insert({4, "li", 92});
//迭代器遍历st
set<student>::iterator it;
for(it = st.begin(); it != st.end(); it++)
cout << it->num << " " << it->name << " " << it->score << endl;
}
Copy
set练习:
映射(map)
学习map之前我们先要知道pair,因为可以简单的把 map 理解为 pair 数组。
什么是pair
pair 是将2 个数据组合成一组数据,当需要这样的需求时就可以使用pair,pair 的实现是一个结构体,主要的两个成员变量是 first 和 second。
1、初始化
pair<T1,T2> p; //创建一个空的 pair 对象(使用默认构造)
pair<T1,T2> p(v1,v2); //创建一个pair对象,使用值v1和v2初始化。
make_pair(v1,v2); // 以v1和v2的值创建一个新的 pair对象
Copy
2、比较
p1 < p2; //两个pair对象间的小于运算,先比较两者的first,再比较两者的second
p1 == p2; //如果两个对象的first 和 second依次相等,则这两个对象相等;
p1 > p2; //两个pair对象间的大于运算
Copy
3、访问成员变量
p1.first; // 返回对象 p1中名为 first 的公有数据成员
p1.second; // 返回对象 p1中名为 second 的公有数据成员
Copy
【pair示例】
#include <bits/stdc++.h>
using namespace std;
int main()
{
// 声明之后赋值
pair<int, string> zs;
zs.first = 10001;
zs.second = "zhangsan";
// 声明的同时赋值
pair<int, string> ls(10002,"lisi");
// 使用make_pair赋值
pair<int, string> ww;
ww = make_pair(10003, "wangwu");
// 使用typedef起别名
typedef pair<int, string> stu;
stu zl(10004, "zhaoliu");
stu qq;
qq.first = 10005;
qq.second = "qianqi";
cout << qq.first << " " << qq.second << endl;
// 比较两个pair,先比较first,再比较second
cout << "zhangsan<lisi:" << (zs < ls) << endl;
pair<int, string> p1(1001, "haha");
pair<int, string> p2(1001, "xixi");
cout << "haha>xixi:" << (p1 > p2) << endl;
}
Copy
什么是map
map∶是关联容器的一种,map 的每个元素都分为关键字和值两部分,容器中的元素是按关键字排序的,并且不允许有多个元素的关键字相同(multimap 允许存储相同键的元素,感兴趣的同学自行了解)。
map 使用注意:
(1)第一个可以称为关键字(key),每个关键字只能在 map 中出现一次,第二个可能称为该关键字的值(value);
例如∶map<string,int> a;可以将字符串映射为整数,map<double,int> a;可以将双精度浮点数映射为整数。
(2)map中的元素是一对数据∶<关键字,数值>,可以简单的把 map 理解为 pair 数组,但 map不是数组,是STL中的关联容器。
(3)不能直接修改 map 容器中的关键字。因为 map 中的元素是按照关键字排序的,当关键字被修改后,容器并不会自动重新调整顺序,于是容器的有序性就会被破坏,再在其上进行查找等操作就会得到错误的结果。
map 常用函数
函数名 | 函数说明 |
---|---|
find(关键字) | 返回指定关键字元素的位置迭代器,如果不存在返回 map.end()。 |
count(关键字) | 统计指定关键字元素的个数,由于map每个元素的关键字都不相同,count 结果只能是1或者0。 |
insert(元素) | 插入元素到map 中,元素一般是make_pair(关键字, 值)。 |
erase(关键字/迭代器) | 删除 map 指定位置或者指定关键字的元素。 |
clear() | 清除 map 所有元素,size()变0。 |
运算符[] | 取/赋值map 的指定关键字的对应值,类似数组的下标运算。 |
begin() | map 的第一个元素(最小)元素的位置,返回第一个元素迭代器(指针) |
end() | map的结束位置。注意∶返回的迭代器是最后一个元素的后面位置,不是最后一个元素的迭代器。 |
size() | 返回map中已有元素的个数。 |
empty() | 判断map 是否为空,等价于size()是否为0。 |
【map 示例】
#include <bits/stdc++.h>
using namespace std;
int main()
{
// 声明一个map,默认根据key升序排序
// 此处可以理解为<姓名, 分数>
map<string, int> mp {{"zhangsan", 99}, {"lisi", 100}, {"wangwu", 98}};
// 当然也可以显式声明为greater
map<string, int, greater<string> > mp1;
// 可以直接使用 操作符[] 为 map 添加一个键值对
mp["zhaoliu"] = 97;
// 创建 pair 插入到map中
pair<string, int> p("zhaoba", 98);
mp.insert(p);
// 利用 make_pair 创建 pair 插入到map中
mp.insert(make_pair("qianqi", 99));
// 获取关键字key为"lisi"的值
cout << mp["lisi"] << endl;
// 使用map的erase删除元素
mp.erase("wangwu");
// map支持双向迭代器,但是不能用 +x 或 -x
mp.erase(mp.begin());
// 使用find函数查找
map<string, int>::iterator it;
it = mp.find("qianqi");
if(it != mp.end())
cout << it->first << " " << it->second << endl;
else cout<<"not find qianqi" << endl;
// count函数判断元素在不在
if(mp.count("wangwu")) cout << "wangwu is exist" << endl;
else cout << "wangwu is not exist" << endl;
//迭代器遍历
for(it = mp.begin(); it != mp.end(); it++)
{
cout << it->first << " " << it->second << endl;
//这里的->等价于先* 再 . 也就是等同于下面的写法
//cout << (*it).first << " " << (*it).second << endl;
}
}
Copy
map练习:
C++11的新特性*
选学。现在的NOI支持到C++14。C++11的新特性也可以用在竞赛中了!
auto关键字
在C++11标准的语法中,auto被定义为自动推断变量的类型。例如:
#include <bits/stdc++.h>
using namespace std;
int main()
{
auto x=5.2;//这里的x被auto推断为double类型
map<int,int> m;
for(auto it=m.begin();it!=m.end();++it)//这里it被auto推断为map<int,int>::iterator类型
{
}
}
Copy
新式for循环
set举例:
#include <bits/stdc++.h>
using namespace std;
int main()
{
set<int> st = {30, 10, 30, 40, 20, 10};
for(auto i : st)//注意这里的i是int类型,不是迭代器类型
cout << i << " ";
}
Copy
map举例:
#include <bits/stdc++.h>
using namespace std;
int main()
{
map<string, int> mp {{"zhangsan", 99}, {"lisi", 100}, {"wangwu", 98}};
for(auto i : mp)//这里的 i 是pair<string, int> 类型
cout << i.first << " " << i.second << endl;
cout << "==========我是分割线==========" << endl;
//前提:你要对容器和迭代器类型非常熟练
//当然你不想让电脑费劲推导,也可以这样写。
for(pair<string, int> i : mp)
cout << i.first << " " << i.second << endl;
}