嵌入式面试高频!!!C语言(十四) STL(嵌入式八股文)

一.List与vector

List 与 Vector 的区别

在编程中,List 和 Vector 是两种常见的数据结构,它们在内部实现、性能特性和适用场景上有显著差异。

List(链表)

  • 动态数据结构,元素通过指针连接,无需连续内存空间。
  • 插入和删除操作高效,时间复杂度为 O(1)(已知位置时)。
  • 随机访问效率低,时间复杂度为 O(n),需要遍历节点。
  • 典型实现:C++ 中的 std::list,Python 中的 list(实际为动态数组)。

Vector(动态数组)

  • 基于连续内存存储,支持快速随机访问,时间复杂度为 O(1)
  • 尾部插入/删除高效,但中间或头部操作需移动元素,时间复杂度为 O(n)
  • 内存预先分配,扩容时可能触发拷贝,但均摊时间复杂度仍为 O(1)
  • 典型实现:C++ 中的 std::vector,Java 中的 Vector(线程安全)。

性能对比

  • 访问速度

    Vector 的随机访问性能远优于 List,因内存连续性适合 CPU 缓存预取。

  • 插入/删除

    List 在任意位置插入/删除更快,而 Vector 仅在尾部高效。

  • 内存占用

    List 每个元素需额外存储指针,空间开销较大;Vector 内存利用率更高。

List:

cpp 复制代码
struct ListNode {
    T data;         // 存储的数据
    ListNode* prev; // 前驱节点指针
    ListNode* next; // 后继节点指针
};
 
gherkin 复制代码
+---------+    +---------+    +---------+
| Prev    |<---| Prev    |<---| Prev    |
| Data#1  |    | Data#2  |    | Data#3  |
| Next    |--->| Next    |--->| Next    |
+---------+    +---------+    +---------+
 

vector:

在 C++ 中,std::vector 是一个动态数组,其内存布局通常由三个关键指针管理:

  1. _M_start(或 begin:指向动态数组的起始位置。
  2. _M_finish(或 end:指向最后一个有效元素的下一个位置。
  3. _M_end_of_storage:指向动态数组分配的存储空间的末尾。
jboss-cli 复制代码
+-------------+-------------+-----+-------------+-------------+
| 元素1       | 元素2       | ... | 元素N       | 未使用空间   |
+-------------+-------------+-----+-------------+-------------+
^             ^                     ^             ^
|             |                     |             |
_M_start      _M_start + 1          _M_finish     _M_end_of_storage
 

二.deque:

deque(双端队列)是 C++ STL 中的一种序列容器,支持在头部和尾部高效地插入和删除元素。与 vector 相比,deque 在头部插入和删除的时间复杂度为 O(1),但随机访问的性能略低于 vector。

deque 的核心特性

  • 动态扩展:deque 由多个连续的存储块组成,可以动态扩展。
  • 双端操作 :支持 push_frontpop_frontpush_backpop_back 等操作。
  • 随机访问 :支持通过下标(operator[]at())访问元素,时间复杂度为 O(1),但比 vector 稍慢。

deque 的常用操作:

cpp 复制代码
#include <deque>
using namespace std;

// 初始化
deque<int> dq = {1, 2, 3};

// 头部插入
dq.push_front(0); // dq: {0, 1, 2, 3}

// 尾部插入
dq.push_back(4);  // dq: {0, 1, 2, 3, 4}

// 头部删除
dq.pop_front();   // dq: {1, 2, 3, 4}

// 尾部删除
dq.pop_back();    // dq: {1, 2, 3}

// 随机访问
int val = dq[1];   // val = 2
int val2 = dq.at(2); // val2 = 3
 

deque 的迭代器支持

deque 支持双向迭代器,可以使用 begin()end()rbegin()rend() 进行遍历:

cpp 复制代码
for (auto it = dq.begin(); it != dq.end(); ++it) {
    cout << *it << " ";
}
 

deque 的性能分析

  • 插入/删除
    • 头部或尾部操作:O(1)
    • 中间插入或删除:O(n)(需要移动元素)
  • 访问
    • 随机访问:O(1)(但比 vector 慢)
  • 内存占用
    • 由于分段存储,内存开销略高于 vector。

deque 的应用场景

  • 需要频繁在头部和尾部进行插入或删除操作。
  • 需要随机访问,但对性能要求不如 vector 严格。
  • 不适合频繁在中间位置进行插入或删除的场景。

deque 与 vector 的比较

特性 deque vector
头部插入/删除 O(1) O(n)
尾部插入/删除 O(1) O(1)(均摊)
随机访问 O(1)(稍慢) O(1)(更快)
中间插入/删除 O(n) O(n)
内存连续性 不连续 连续

三.STL底层数据结构

STL 容器分类与特性

STL(Standard Template Library)中的数据结构主要分为序列容器关联容器无序关联容器容器适配器四大类。以下为详细分类及特性说明:

序列容器

序列容器按线性顺序存储元素,支持特定位置的插入和访问。

vector
  • 动态数组,支持随机访问(O(1))。
  • 尾部插入/删除高效(O(1)),中间或头部操作需移动元素(O(n))。
  • 自动扩容,但可能引发内存重新分配。
  • 典型用途:需要频繁随机访问的场景。
deque
  • 双端队列,支持头尾高效插入/删除(O(1))。
  • 随机访问效率接近vector(O(1)),但实际略慢。
  • 内部由分段连续空间实现,扩容无vector的内存拷贝开销。
  • 典型用途:队列或栈的底层实现。
list
  • 双向链表,支持任意位置高效插入/删除(O(1))。
  • 不支持随机访问(需遍历,O(n))。
  • 提供splicemerge等链表专用操作。
  • 典型用途:频繁中间插入的场景。
forward_list
  • 单向链表 ,比list更节省空间。
  • 仅支持单向遍历,插入/删除需前置迭代器。
  • size()方法,需手动计数。
array
  • 固定大小数组,封装C风格数组,提供STL接口。
  • 不支持动态扩容,但安全性高于原生数组。
  • 典型用途:需编译期确定大小的场景。

关联容器

关联容器基于键(key)自动排序,使用红黑树实现,操作复杂度通常为O(log n)。

set
  • 唯一键集合,元素即键且不可重复。
  • 自动按升序排列(可通过比较函数自定义)。
multiset
  • 允许键重复的set,其余特性相同。
map
  • 键值对集合,键唯一且排序。
  • 通过键快速查找值(O(log n))。
multimap
  • 允许键重复的map,支持一键多值。

无序关联容器(C++11引入)

基于哈希表实现,元素无序,平均操作复杂度为O(1),最坏情况O(n)。

unordered_set
  • 哈希实现的set,键唯一但不排序。
unordered_multiset
  • 允许键重复的unordered_set
unordered_map
  • 哈希实现的map,查找效率高,但内存开销较大。
unordered_multimap
  • 允许键重复的unordered_map

容器适配器

基于其他容器封装,提供特定接口。

stack
  • 后进先出(LIFO) ,默认基于deque实现。
  • 支持pushpoptop操作。
queue
  • 先进先出(FIFO) ,默认基于deque实现。
  • 支持push(尾)、pop(头)操作。
priority_queue
  • 优先级队列 ,默认基于vector实现堆结构。
  • 元素按优先级出队(默认最大堆,可自定义比较函数)。

其他特殊容器

bitset
  • 固定大小的位集合,支持位操作(如与、或、移位)。
  • 非STL正式容器,但常与STL配合使用。
string
  • 专为字符串设计的容器,支持类似vector的操作。
  • 提供substrfind等字符串特有方法。

选择容器的准则

  • 随机访问需求vectordeque
  • 频繁插入/删除listforward_list
  • 有序存储与查找set/map
  • 极致查找速度(无顺序要求)unordered_*系列。
  • 特定数据结构需求 :适配器(如stackqueue)。

四:vector动态扩展的原理

内存分配策略

vector内部通过连续内存块存储元素。初始时分配一定容量(capacity),当元素数量(size)超过容量时触发扩容。扩容通常涉及申请更大的内存块(如原容量的1.5或2倍),具体倍数由实现决定。

cpp 复制代码
std::vector<int> v = {1, 2, 3};
int* old_ptr = v.data();
v.push_back(4); // 可能触发扩容
bool is_moved = (old_ptr != v.data()); // 检查指针是否变化
 
元素迁移过程

扩容时需要将旧内存中的元素按顺序拷贝到新内存,并释放旧内存块。此过程保证元素的严格顺序和迭代器有效性(但原有迭代器会失效)。

关键步骤伪代码

  1. 计算新容量:new_capacity = max(old_capacity * factor, new_size)
  2. 分配新内存:new_buffer = allocator::allocate(new_capacity)
  3. 拷贝元素:uninitialized_copy(old_buffer, old_buffer + size, new_buffer)
  4. 释放旧内存:allocator::deallocate(old_buffer, old_capacity)
优化策略
  • 预留空间 :通过reserve()预先分配足够容量避免多次扩容。
  • 移动语义:C++11后支持元素移动而非拷贝,提升对象迁移效率。
  • 小型缓冲区优化:某些实现对小规模数据使用栈内存避免动态分配。

五:prioriry_queue优先级队列的底层数据结构

优先级队列的底层数据结构

优先级队列(Priority Queue)的实现通常基于以下几种数据结构,每种结构在时间和空间复杂度上有不同的权衡:

二叉堆(Binary Heap)

二叉堆是最常见的优先级队列实现方式,分为最大堆和最小堆。二叉堆是一个完全二叉树,满足堆性质(父节点的优先级始终高于或低于子节点)。

  • 插入操作:元素插入到堆的末尾,通过"上浮"(swim)操作调整位置,时间复杂度为 O(\\log n)
  • 提取操作:移除堆顶元素后,将末尾元素移至堆顶,通过"下沉"(sink)操作调整,时间复杂度为 O(\\log n)
  • 空间复杂度O(n),通常用数组实现,无需额外指针开销。
斐波那契堆(Fibonacci Heap)

斐波那契堆是一种更高效但实现复杂的数据结构,适用于需要频繁插入和合并的场景。

  • 插入操作O(1) 时间复杂度,直接插入到根链表。
  • 提取操作O(\\log n) 均摊时间复杂度,需合并树并调整。
  • 空间复杂度:较高,因需维护多指针和标记位。
二项堆(Binomial Heap)

二项堆由一组二项树组成,支持高效合并。

  • 插入与合并O(\\log n) 时间复杂度。
  • 提取操作O(\\log n),需合并相同阶的二项树。
平衡二叉搜索树(Balanced BST)

如 AVL 树或红黑树,也可用于实现优先级队列。

  • 操作复杂度:插入、删除、查找均为 O(\\log n)
  • 优势:支持按优先级范围查询等扩展操作。
数组或链表(简单实现)

无序数组或链表可以通过以下方式实现,但效率较低:

  • 插入O(1)(直接追加)。
  • 提取O(n)(需遍历查找优先级最高的元素)。

选择依据

  • 默认选择 :二叉堆在大多数场景下综合性能最优,尤其是标准库实现(如 C++ std::priority_queue)。
  • 高级需求:斐波那契堆适合图算法(如 Dijkstra)中频繁修改优先级的场景。
  • 扩展功能:若需支持动态优先级修改或范围查询,平衡二叉搜索树更合适。
相关推荐
九年义务漏网鲨鱼2 小时前
【机器学习算法】面试中的ROC和AUC
算法·机器学习·面试
草莓熊Lotso2 小时前
《算法闯关指南:优选算法--位运算》--38.消失的两个数字
服务器·c++·算法·1024程序员节
白露与泡影4 小时前
面试:Spring中单例模式用的是哪种?
spring·单例模式·面试
编码追梦人8 小时前
从零入门嵌入式系统:核心概念 + 环境搭建 + 传感器实战
单片机·嵌入式硬件
Molesidy9 小时前
【VSCode】【Clangd】Win下的基于LLVM/Clangd+Clangd插件+MINGW+CMake的VSCode配置C/C++开发环境的详细教程
c++·ide·vscode·clangd·llvm
椰壳也可10 小时前
06_作业基于CubeMx实现按键控制LED灯(裸机)(立芯嵌入式笔记)
笔记·stm32·学习
CodeLongBear10 小时前
MySQL索引篇 -- 从数据页的角度看B+树
mysql·面试
Mr_WangAndy11 小时前
C++_chapter13_C++并发与多线程_多线程概念,死锁,unique_lock(),lock_guard()使用
c++·lock·死锁·并发与多线程·unlock·lock_guard·unique_lock
小欣加油11 小时前
leetcode 946 验证栈序列
c++·算法·leetcode·职场和发展