【小陈背八股-C++】Day05-为什么面试总爱问Vector、Map和List?

新星杯·14天创作挑战营·第18期 10w+人浏览 221人参与

前言

  • “Vector和List的根本区别是什么?”
  • “Map的底层是如何保证高效查找的?”
  • “海量数据场景下该如何选择容器?”

本文直面面试核心,系统梳理五大关键容器:从Vector的连续内存优势与扩容代价,到List的灵活插入与查找局限;从红黑树维持Map/Set有序性的平衡原理,到Deque分段结构的实现智慧。我们将穿透概念表象,直击面试官最关注的底层原理与设计取舍。

理解容器,就是理解C++性能优化的核心逻辑。让我们开始这场面向实战的深度解析。


主页介绍
在这里插入图片描述



1. list和vector有什么区别?

  • 底层数据结构

    • vector : 动态数组,在内存中是连续存储的
    • list : 双向链表,每一个元素可以存储在不连续的内存块中,每一个节点包括指向前一个元素和后一个元素的指针
  • 随机访问

    • vector :支持高效率的随机访问O(log1),因为通过索引计算地址常量时间
    • list:不支持随机访问,想要访问第n个元素,需要遍历链表
  • 插入和删除操作

    • vector:在尾部插入或删除O(log1)、在中间或者头部删除/插入元素O(logN)
    • list:在任意位置删除或插入O(1),但是找到插入/删除的位置需要的时间为O(n)
  • 迭代器

    • vector:随机访问迭代器,支持所有迭代器操作(包括加减整数等)
    • list:双向迭代器,只支持前移或者后移,不支持随机访问(如It+)
  • 使用场景

    • vector:当需要频繁随机访问时,或者主要在尾部添加或者删除元素时使用
    • list:当需要在容器中频繁地插入或删除元素,并且不需要随机访问时使用
  • 速度

    • vector:使用连续内存分配,能够很好地利用程序空间局部性的特点,提高访问速度。程序的空间局部性原理,当程序中的某一个数据被访问后,与之相邻的数据在不久后也可能被访问。而 std::vector 连续内存分布天然的符合这一特性。那么当访问vector中的某一个元素时,会将一整个内存数据块加载到高速缓存中,后续访问到同一行中的元素时,直接命中高速缓存
    • list:内存分布不是连续的,访问下一个元素时需要跳转指针,几乎每一次的访问都会缓存未命中

2. list是如何实现元素的插入和删除的?

  • list的底层是一个双链表,每一个结点都包含了指向前一个结点和指向后一个结点的指针,支持双向遍历
  • 插入和删除都是通过修改指针的指向即可

3. set的底层实现与map有什么不同

  • 相同点:

    • std::setstd::map 在C++标准库中都是基于平衡二叉树实现的(一般是红黑树)
  • 不同点:

    • std::set 仅储存键值,而 std::map 储存键值对
    • std::set 存储的键就是元素本身,而 std::map 存储的键值是键,值是与键相关联的数据

4. map、set、multimap、multiset有什么区别?

C++中的map、set、multimap、multiset都是标准库中的关联容器,都是基于红黑树实现的,下面是它们的详细区别:

  1. std::map

    • 存储的是键值对:<Key,T>,其中Key是键,T是与键相关联的值
    • 键的唯一性:每一个键在map中是唯一的,不允许出现重复的键
    • 排序:map中的元素根据键自动排序
    • 访问:通过键访问值,使用下标操作符operator[ ]直接访问或添加元素
  2. std::set

    • 仅存储键:也就是说键就是元素本身,并不存储值
    • 元素唯一性:每一个元素在set中也是为一个,不允许出现相同的元素
    • 排序:set中的元素根据元素值自动排序
    • 访问:不支持下标访问,只支持迭代器访问
  3. std::multimap

    • 键值对存储:与map类似
    • 键的重复性:允许多个元素有相同的键
    • 排序:multimap中的元素根据键自动排序,对于具有相同键的元素,它们内部也是有序的(通常是插入顺序)
    • 访问:通过键访问值,但是不能访问特定键的所有值,需要遍历
  4. std::multiset

    • 只存储键:只存储键,与set类似
    • 键的重复性:允许多个元素键相同
    • 排序:multiset中的元素根据键自动排序,对于具有相同键的元素,内部也是有序的(通常是插入顺序)
    • 访问:只能通过迭代器访问,不能使用下标访问

5. 如何在set和map查找元素?

5.1. std::map

  • find方法:
    • 直接使用find方法查找特定键的值
    • 如果找到了,返回一个指向该元素的迭代器;如果没有找到,返回一个指向end()的迭代器
std::map<int, std::string> myMap;
myMap[1] = "one";
myMap[2] = "two";

auto it = myMap.find(1);
if (it != myMap.end()) {
    std::cout << "Found: " << it->second << std::endl;
} else {
    std::cout << "Not found" << std::endl;
}
  • count方法:
    • 用于返回具有特定键值的元素数量,如果是std::map ,键是唯一的,所以要么返回0要么是1
size_t count = myMap.count(1);
if (count > 0) {
    std::cout << "Found" << std::endl;
} else {
    std::cout << "Not found" << std::endl;
}
  • lower_boundupper_bound方法:
    • lower_bound返回不小于给定键的首个元素的迭代器
    • upper_bound返回大于给定键的首个元素的迭代器
auto lower = myMap.lower_bound(1);
auto upper = myMap.upper_bound(1);
if (lower != upper) {
    std::cout << "Found: " << lower->second << std::endl;
}
  • equal_range方法:
    equal_range返回一个迭代器对,第一个是lower_bound,第二个是upper_bound
auto range = myMap.equal_range(1);
if (range.first != range.second) {
    std::cout << "Found: " << range.first->second << std::endl;
}

5.2. std::set

  • find方法:
std::set<int> mySet;
mySet.insert(1);
mySet.insert(2);

auto it = mySet.find(1);
if (it != mySet.end()) {
    std::cout << "Found" << std::endl;
} else {
    std::cout << "Not found" << std::endl;
}
  • count方法:
size_t count = mySet.count(1);
if (count > 0) {
    std::cout << "Found" << std::endl;
} else {
    std::cout << "Not found" << std::endl;
}
  • lower_boundupper_bound方法:
auto lower = myMap.lower_bound(1);
auto upper = myMap.upper_bound(1);
if (lower != upper) {
    std::cout << "Found: " << lower->second << std::endl;
}
  • equal_range方法:
auto range = myMap.equal_range(1);
if (range.first != range.second) {
    std::cout << "Found: " << range.first->second << std::endl;
}

6. 容器map、deque、list的实现原理?

  • std::map :

    • 基于红黑树:保证按照元素的键的顺序进行排序。每个节点存储了键值对,根据键的比较结果进行自平衡。红黑树是一种自平衡二叉树,根据颜色和节点的特定排列保持平衡
    • 有序容器:同时是按照 < 运算符定义的顺序
    • 唯一键:每一个键都是为一的,不允许有重复的键
    • 时间复杂度:查找、插入和删除的时间复杂度都是O(logN)
    • 迭代器:由于map是基于树的,所以迭代器在遍历的时候是有序的
  • std::deque

    • 是一个双端队列,允许在容器的两端进行快速的插入和删除操作
    • 允许序列操作:可以快速地在队列的前端或末端添加/删除元素
    • 时间复杂度:队列前端或后端的插入/删除操作时间复杂度为常数,中间插入/删除时间为O(N)

在这里插入图片描述

### 解决 Node.js npm 命令无法识别的题 当安装 Node.js 后,`node` `npm` 命令在命令行中无法识别时,通常是因为环境变量未正确配置或安装过程中出现题。以下是详细的解决方法: #### 确认 Node.js npm 是否已正确安装 运行以下命令检查 Node.js npm 的版本: ```bash node -v ``` 如果返回了 Node.js 的版本号,则说明 Node.js 已成功安装[^1]。接着检查 npm: ```bash npm -v ``` 如果仍然提示“'npm'不是内部或外部命令”,则需要手动配置环境变量。 #### 配置环境变量 Node.js 安装后会将 `npm` 可执行文件放置在特定目录中,通常是 `C:\Program Files\nodejs` 或用户自定义的安装路径。以下是具体操作步骤: - **找到 Node.js 的安装路径** 默认情况下,Node.js 会安装在 `C:\Program Files\nodejs`。如果更改了安装路径,请记住实际路径。 - **打开系统环境变量设置** 路径为:控制面板 -> 系统安全 -> 系统 -> 高级系统设置 -> 环境变量。 - **编辑 PATH 变量** 在“系统变量”部分找到 `Path`,然后点击“编辑”。将 Node.js 的安装路径(例如 `C:\Program Files\nodejs`)添加到 `Path` 中。确保路径末尾没有多余的分号[^2]。 - **保存并测试** 完成上述操作后,重新打开命令行窗口,并再次运行以下命令: ```bash npm -v ``` 如果显示 npm 的版本号,则说明环境变量配置成功。 #### 其他可能的原因及解决方案 如果上述方法仍无法解决题,可以尝试以下方法: - **重启电脑** 有时环境变量的更改需要重启计算机才能生效。尝试重启后再运行 `npm -v` 命令[^3]。 - **检查 npm 文件是否存在** 进入 Node.js 的安装目录,检查是否包含 `npm` `npm.cmd` 文件。如果缺失,可能是安装过程中出现题,建议重新安装 Node.js。 - **重新安装 Node.js** 如果以上方法均无效,可以卸载当前的 Node.js 并重新安装最新稳定版本。下载地址为 [Node.js 官方网站](https://nodejs.org/)。 --- ### 示例代码 以下是一个简单的 npm 使用示例,验证环境是否正常: ```javascript // 创建一个简单的 package.json 文件 npm init -y // 安装一个依赖包 npm install lodash ``` 如果上述命令能够成功运行,则说明环境变量已正确配置。 ---
评论 5
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值