C++之STL容器(高频考点)

60 阅读7分钟

之前为了准备华为机试,系统地学习了一遍STL容器,现整理笔记内容出来供大家日常使用。STL容器也是面试高频考点,平时我也会使用零碎的时间来回顾这些知识点,常学常新~

vector(动态数组)

首先vector是什么?

vector就是一个是动态数组。与数组有相似的地方,可以和数组一样用下标的方式访问,可以和数组一样的方式进行初始化。不同的地方在于vector是不定长的,运行期间在堆空间分配内存,但是数组是固定长度的,在编译期间栈空间分配内存。

特点

  • 动态数组,支持自动扩容,底层内存分配在堆上。
  • 元素通过下标访问,与普通数组类似,但大小不固定。

定义方式

  1. 定义空向量
#include <vector>

vector<int> a;
//使用自定义结构体
struct node {
    string name;
    int score;
};
vector<node> n;
  1. 定义并初始化
vector<int> b = {1, 2, 3, 4, 5};
vector<int> c(10, 1);   // 10 个元素,值为 1
vector<int> d(10);      // 10 个元素,值为默认值 0
  1. 从已有向量或数组创建
vector<int> e(b);               // 拷贝构造,写vector<int> e = b也可以
vector<int> f(b.begin(), b.end());   // 范围初始化
int arr[5] = {1, 2, 3, 4, 5};
vector<int> g(arr, arr + 5);    // 从数组创建

常用操作

访问和修改元素

vector<int> vec = {1, 2, 3};
cout << vec[0];  // 输出 1
vec[1] = 10;     // 修改元素值

遍历向量

  1. 使用下标遍历
vector<int> a(10, 1);
for (int i = 0; i < a.size(); i++) {
    cout << a[i] << " ";
}
  1. 使用迭代器的方式进行遍历
vector<int>::iterator it;                   // it就是迭代器
for (it = a.begin(); it != a.end(); ++it) { //++it == it=it+1; it==a.end();
    cout << *it << " ";
}
  1. 使用范围循环(C++11)
for (auto &val : vec) {
   cout << val << " ";
}

常用函数

对于一个函数也可以在本地IDE查看源码,查看方法体、参数、返回值

vec.size();        // 获取元素个数
vec.empty();       // 检查是否为空
vec.front();       // 返回第一个元素,时间复杂度O(1)
vec.back();        // 返回最后一个元素,时间复杂度是O(1)
vec.push_back(10); // 末尾添加元素
vec.pop_back();    // 删除末尾元素
vec.clear();       // 清空向量
vec.insert(vec.begin() + 1, 20); // 指定位置插入
vec.erase(vec.begin() + 1);      // 指定位置删除
vec.resize(5, 0); // 改变大小,多出的元素初始化为 0
sort(vec.begin(), vec.end()); // 排序
  • vec.front() 返回第一个数据 时间复杂度O(1)
  • vec.back() 返回最后一个数据 时间复杂度是O(1)
  • push_back(int value) 在vector最后面插入一个新的元素
vector<int>a(10,1);
a.push_back(2);  
  • pop_back() 删除从向量最后一个元素,注意没有参数。如果说向量已经为空,再使用pop_back()会出错,需要进行判断
vector<int> a(10);
a.push_back(2); // 删除元素
if (!a.empty()) {// 向量非空
    a.pop_back();
}
  • vec.empty() 查看向量是否为空;若向量为空,则返回true; 不为空,返回false
vector<int> a(10);
a.push_back(2);
if (a.empty()) { // empty()判断函数
    cout << "数组为空";
} else {
    cout << "数组不为空";
}
  • vec.size()返回向量长度
vector<int> vec;
vec.size(); // 长度函数
  • vec.begin() 返回首元素的迭代器,通俗地说是返回首元素的地址

  • vec.end()返回最后一个元素的后一个位置的迭代器(地址)

  • sort() 排序函数

#include <algorithm>
sort(c.begin(), c.end());
//如果要对指定区间进行排序,可以对sort()里面的参数进行改动
vector<int> a(n + 1);
sort(a.begin() + 1, a.end()); // 对[1, n]区间进行从小到大排序

其他函数

  • c.clear() 用于清空向量中的全部元素
  • a.erase(iterator first, iterator last)删除自定义区间内的值
//删除容器中从第二个元素开始(索引为 1)到末尾的所有元素。
a.erase(a.begin()+1, a.end())
  • c.resize(n, 1) 改变向量大小为 n,若原来向量大于 n,则进行裁剪;若原来向量小于 n,多的部分数值赋为1,如果没有默认赋值为0 。
  • c.insert(it, x) 向任意迭代器it插入一个元素x ,O ( N )
c.insert(c.begin() + 2,-1)  // 将-1插入c[2]的位置  

queue(队列)

队列的首元素是队头,最后的元素是队尾。

在队列中,只能访问它的队头和队尾,使用头指针访问队头元素,使用尾指针访问队尾元素,队列使用这两个指针完成队列的增删改查。

特点

  • 先进先出,只能操作队头和队尾。

定义

//头文件
#include <queue>
//定义一个queue变量
queue<int> q;

注意:初始时,队列为空

访问队列元素

queue在概念中已经讲过,只能通过头指针和尾指针访问队列的元素,不能通过下标的方式进行访问

常用函数

  1. 入队和出队
  • push(/*元素*/) 入队的操作和vector很相似,都是在尾部插入一个元素,但是和vector函数名不同
  • pop() 删除一个元素,这里和vector不同,队列是从头部进行删除元素
q.push(1);  // 入队
q.pop();    // 出队
  1. 访问队头和队尾
int value1 = q.front();  // 返回队头元素
int value2 = q.back();   // 返回队尾元素
  1. 检查是否为空
  • empty() 用来检测队列是否为空的,为空就返回true
if (q.empty()) {
    cout << "队列为空";
}
  1. 清空队列
while (!q.empty()) {
    q.pop();
}
  1. size() 返回队列长度

set(集合)

set容器的元素不会重复,并且set容器里的元素自动从小到大排序。六个字总结:不重复且有序

特点

  • 不重复元素,自动排序。

定义

#include <set>
set<int> s;

常用操作

遍历集合

  1. 迭代器遍历
set<int>::iterator it;
for (it = s.begin(); it != s.end(); it++) { // 观察上下代码
    cout << *it;
}
  1. 智能指针遍历
for (auto i : s) {
    cout << i;
}

访问单个元素

  1. 访问首元素
int value = *(s.begin());
  1. 访问尾元素的值
int value = *(s.end()-1)
  1. 使用迭代器访问
//定义迭代器
set<int>::iterator it; 
it = s.begin();
it++;
cout<<*it;//访问第二个元素

常用函数

  1. 插入和删除元素
s.insert(5); // 插入
s.erase(5);  // 删除
  1. 访问元素
int value = *(s.begin());//地址通过 * 可以去访问该地址的值
for (auto it = s.begin(); it != s.end(); ++it) {
    cout << *it << " ";
}
  1. 查找元素
if (s.find(5) != s.end()) {//如果未找到,find 返回 s.end()
    cout << "元素 5 存在";
}

其他一些函数如下

image.png

其他set

集合特点
unordered_set非有序,存储数据的时候是杂乱无序的
multiset元素可以重复,且元素有序
unordered_multiset元素无序,且可重复

map (映射)

map是一个映射,就是一个元素对应另一个元素,映射类似于函数的对应关系,每个x对应一个y,而map是每个键对应一个值。

特点

  • 每个键(key)对应一个值(value)。
  • 键值对按照键自动排序。
  • key不可以重复,value可以重复

定义

#include <map>
//map<关键字key数据类型, 值value的数据类型> 变量名
map<int, string> mp;

map会按照键的顺序从小到大自动排序,所以key的数据类型必须可以比较大小

常用操作

添加元素

方式一:使用类似数组的方式添加元素

mp[1] = "one" // 目前 mp{1->"one"}
mp[2] = "two" // 目前 mp{1->"one",2->"two"}

方式二:使用map内置的函数插入元素(需要构造键值对)

mp.insert({3, "three"});
mp.insert(pair<int, string>(4, "four"));
mp.insert(make_pair(5, "five"));

注意:使用insert插入map元素时,需要构造键值对

访问元素

cout << mp[1];  // 通过键访问

遍历

  1. 使用迭代器
// map的遍历:使用迭代器
map<string, int>::iterator it;
for (it = mp.begin(); it != mp.end(); it++) {
    cout << it->first << " " << it->second << endl;
}

使用迭代器 it 有两种访问元素的方式:

// it是结构体指针访问所以要用 -> 访问
cout << it->first << " " << it->second << "\n";
//*it是结构体变量 访问要用 . 访问
cout << (*it).first << " " << (*it).second;
  1. 智能指针
for (auto i : mp) {
    cout << i.first << " " << i.second << endl; // 键,值
}

常用函数

image.png

特别说明:

查找元素在容器中的具体地址,使用 mp.find()

查找元素是否存在,可以使用 mp.count()

auto it = mp.find(1);
if (it != mp.end()) {
    cout << it->first << ": " << it->second;
}

unordered_map

与map的区别:

  • map内部使用红黑树实现,具有自动排序(按键从小到大)功能
  • unordered_map内部元素杂乱无序

结语

关于竞赛和笔试的一点小tips

一、如果在考场上忘记某个函数的头文件

  1. 系统自动提示:在IDE中,输入容器名称(如 vec),点击系统提示,会自动补全所需的头文件。
  2. 使用通用头文件:如果不确定某个容器的头文件,直接包含所有常用头文件
#include <bits/stdc++.h>

二、如果忘记某个函数方法

  1. 函数提示:在线系统中,输入变量名加点号(如 vec.),可以看到与该变量相关的函数列表,随机尝试一个即可。
  2. 源码查找:在本地 IDE 中,按住 Ctrl 键,点击容器类型(如 vector),可以跳转到源码,查看支持的函数。

但并不是所有函数都能用上面的方法寻找到,还是需要自行记住一部分,例如数学函数:pow(), sqrt();字符处理:tolower();STL 算法: sort(), transform()等等.