STL容器、迭代器

前置知识:数组、字符串、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的迭代器练习为主,不要使用数组的[],要求使用迭代器的遍历方式。

P2453 数组元素的插入 - TopsCoding

P2454 删除数组的最小数 - TopsCoding

二、迭代器分类

常用的迭代器按功能强弱分为∶输入、输出、正向双向随机访问五种,这里只介绍常用的三种。

不同容器的迭代器,其功能强弱有所不同。例如,排序算法需要通过随机访问迭代器来访问容器中的元素,因此有的容器就不支持排序算法。

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练习

P4727 双向队列 - TopsCoding

链表(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练习:

P2818 set的插入和遍历 - TopsCoding

P2819 set的查询操作 - TopsCoding

P1159 明明的随机数 - TopsCoding

P2195 统计数对个数 - TopsCoding

P2814 安迪的第一个字典 - TopsCoding

映射(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练习:

P2816 map的查询操作 - TopsCoding

P2817 map的插入和遍历 - TopsCoding

P4728 整理抽屉 - TopsCoding

P4729 互斥的数 - TopsCoding

P1304 统计数字 - TopsCoding

P1189 找朋友 - TopsCoding

P2348 家谱 - TopsCoding

P2824 树木清单 - TopsCoding

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;
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

10247D

我会继续努力,信息技术会越好

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值