数组、向量与链表总结与对比

1. 数组(Array)

数组是一种基础的数据结构,用来存储固定大小的同类型元素的序列。在内存中,数组中的元素被分配在连续的内存空间上,每个元素都可以通过数组索引来访问,索引通常从 0 开始。 当您需要快速访问固定数量的数据项时,数组是一个很好的选择。例如,在数学运算、数据缓冲区、查找表等场景中常用。

  1. 固定大小:创建时需要指定数组的大小,之后无法更改。
  2. 随机访问:可以直接通过索引快速访问任何元素,时间复杂度为 O(1)。
  3. 高效的内存利用:由于连续的内存分配,数组在内存利用和访问速度方面非常高效。
  4. 插入/删除操作耗时:在数组中间插入或删除元素通常需要移动其他元素,因此这些操作的时间复杂度较高。
  5. 数组的限制:数组的大小在声明时必须确定,且不能改变,数组不提供检查越界的机制,访问无效索引可能导致未定义行为。
int arr[10]; // 声明一个整型数组,包含10个整数

// 初始化数组:在声明时赋初值
int arr[5] = {1, 2, 3, 4, 5}; // 初始化数组,包含5个整数
// 如果初始化时未填满,剩余的元素会被自动初始化为0
int arr[5] = {1, 2}; // 后三个元素自动初始化为0
//  可以省略数组长度,让编译器自动计算
int arr[] = {1, 2, 3, 4, 5}; // 编译器计算数组长度为5

// 访问数组元素
int value = arr[2]; // 访问第三个元素
arr[4] = 10; // 修改第五个元素的值

// 数组遍历
for(int i = 0; i < 5; ++i) {
    std::cout << arr[i] << std::endl;
}

C++ 标准库中的数组

// C++11 引入了 std::array,提供了固定大小的数组
#include <array>
std::array<int, 5> arr = {1, 2, 3, 4, 5};

//  std::array 提供了 .size() 方法获取大小,以及 .begin() 和 .end() 方法用于范围遍历:
for(auto it = arr.begin(); it != arr.end(); ++it) {
    std::cout << *it << std::endl;
}

2. 向量(Vector)

向量是数组的一个变体,通常在编程语言的标准库中实现。与数组不同,向量可以动态地调整其大小。当需要一个大小可变的数组时,向量是一个很好的选择。在不确定数据项数量的情况下收集数据、实现动态数据集合等场景中常用。

  1. 动态大小:向量可以在运行时根据需要扩展或缩减其大小。
  2. 随机访问:与数组一样,向量支持通过索引的快速随机访问。
  3. 自动管理内存:向量在内部管理内存,自动扩展和缩减存储空间。
  4. 可能的重新分配开销:当向量扩展到超过当前分配的内存时,它可能需要重新分配整个内存块来存储元素。

在 C++ 中,std::vector 是一个封装了动态大小数组的序列容器。它可以存储任何类型的元素,并且可以在运行时动态地更改其大小。std::vector 提供了灵活的动态数组功能,包括动态扩容和方便的元素添加/删除操作,但由于动态扩容,向量的内存使用可能不如静态数组高效, 在频繁扩容的情况下,性能可能受到影响。

// 在使用 std::vector 之前,需要包含头文件 <vector>
#include <vector>

// 声明并初始化空向量, 声明一个 int 类型的向量并初始化为空向量
std::vector<int> vec; 

// 使用初始化列表,初始化一个包含 5 个元素的向量
std::vector<int> vec = {1, 2, 3, 4, 5};

// 指定大小和初始值,包含10个初始化为0的元素
std::vector<int> vec(10, 0); 

// 添加元素
vec.push_back(6); // 在向量末尾添加一个元素

// 访问元素
int value = vec[2]; // 访问第三个元素
int value = vec.at(2); // 访问第三个元素,带有边界检查

// 遍历向量
for(int num : vec) {
    std::cout << num << std::endl;
}

// 使用 [] 运算符或 .at() 的方式修改元素
vec[2] = 100; // 修改第三个元素的值

// 获取向量大小
size_t size = vec.size(); // 获取向量中的元素数量

// 检查向量是否为空
bool isEmpty = vec.empty(); // 如果向量为空,返回 true

// 删除元素
vec.pop_back(); // 删除向量末尾的元素
// 删除指定位置的元素
vec.erase(myVector.begin() + 2); // 删除第三个元素

// 清空向量
vec.clear(); // 删除向量中的所有元素

3. 链表(LinkedList)

链表是一种由节点组成的数据结构,每个节点包含数据和指向下一个节点的指针,链表中的元素不必在内存中连续存储,这种情况下频繁插入和删除时,链表会更加高效,但是不支持随机访问,访问特定索引的元素需要从头开始遍历,效率较低。

  1. 动态大小:链表的大小可以根据需要动态变化。
  2. 高效的插入和删除:可以在任何位置快速地插入或删除节点,不需要移动其他元素。
  3. 无随机访问:访问链表中的元素需要从头开始遍历,时间复杂度为 O(n)。
  4. 额外的内存开销:每个节点需要额外的存储空间来存储指针。

C++ 标准库提供了两种链表类型:std::list:双向链表 和 std::forward_list:单向链表

// 头文件
#include <list> // 对于 std::list

// 声明和初始化链表
std::list<int> myList = {1, 2, 3, 4, 5}; // 双向链表
std::forward_list<int> myForwardList = {1, 2, 3, 4, 5}; // 单向链表

// 添加元素
myList.push_back(6); // 在链表末尾添加元素
myList.push_front(0); // 在链表头部添加元素

// 删除元素
myList.pop_back(); // 删除链表末尾元素
myList.pop_front(); // 删除链表头部元素

// 遍历元素
for (int num : myList) {
    std::cout << num << std::endl;
}

std::forward_list 提供了单向链表的实现,适合于需要频繁在头部插入或删除元素的场景。由于其单向特性,它在内存使用上比 std::list 更高效,但也牺牲了一些灵活性,如无法直接访问前一个元素。

#include <forward_list>

std::forward_list<int> myList; // 空的单向链表
std::forward_list<int> myList = {1, 2, 3, 4, 5}; // 初始化列表

// 在链表头部添加元素
myFowardList.push_front(0);

// 删除头部元素
myFowardList.pop_front();

// 使用迭代器遍历元素
for (auto it = myFowardList.begin(); it != myFowardList.end(); ++it) {
    std::cout << *it << std::endl;
}

// 使用迭代器删除元素
auto it = myFowardList.before_begin(); // 获取头部之前的位置
myFowardList.insert_after(it, 10); // 在头部插入10
myFowardList.erase_after(it); // 删除头部元素

4. 总结

4.1 各结构优缺点及使用场景

  1. 数组(Array)随机访问快,时间复杂度为 O(1),插入/删除操作慢,特别是在数组的中间,因为可能需要移动元素,内存静态分配,大小在编译时确定,无法改变。 适用场景:需要频繁访问元素,且元素数量固定不变的场景,在进行大量数学计算,需要高效访问连续内存的场景。
  2. 向量(Vector,如 std::vector)随机访问快,时间复杂度为 O(1),插入/删除操作慢,在末尾快,但在中间或开始较慢,内存动态分配,可以根据需要改变大小,但可能涉及复制整个数组到新的内存位置。适用场景:当需要动态数组,即数组大小可以改变的场景,需要随机访问,但也会有在末尾添加或删除元素的操作场景。
  3. 链表(LinkedList,如 std::list 和 std::forward_list) 随机访问慢,需要遍历链表,时间复杂度为 O(n),插入/删除操作快,特别是在已知节点的情况下,时间复杂度为 O(1),内存动态分配:每个新元素通常都会进行一次内存分配。适用场景: 当元素的插入和删除操作频繁,且不需要快速随机访问的场景;在实现某些数据结构如队列和栈时,链表提供了自然的优势。

4.2 各特点对比

  1. 内存使用:数组使用连续内存,而向量和链表使用非连续内存,链表为每个元素分配额外内存(存储指针)
  2. 访问速度:数组和向量提供快速随机访问,链表则不支持高效的随机访问。
  3. 插入/删除效率:链表在插入和删除操作上最为高效。数组在这方面最不高效,向量则在末尾操作时较高效。
  4. 灵活性:向量和链表在大小上更为灵活,数组在声明后大小固定。

欢迎来公众号交流,共同学习,共同进步,在这里有 C++、Golang、数据结构等面试考点,持续更新!
在这里插入图片描述

  • 6
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Coder567

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值