首页 > 其他分享 >深入剖析list

深入剖析list

时间:2024-09-26 13:49:55浏览次数:8  
标签:std const 迭代 元素 list 剖析 vector 深入

List

1、list概述

相较于vector的连续线性空间,list是一个环状双向链表。

每次插入或删除一个元素,就配置或释放一个元素空间,时间复杂度为常数。

2、list的节点

以下是STL list的节点(node)结构:

template<class T>
struct  __list_node{
    typedef void* void_pointer;
    void_pointer prev;
    void_pointer next;
    T data;
}

显然这是一个双向链表

3、list的迭代器

由于STL list是一个双向链表,迭代器必须具备前移、后移的能力,所以list提供的是Bidirectional Iterator

在C++中,**双向迭代器(Bidirectional Iterator)**是一种迭代器类型,允许在容器中双向移动,即不仅可以从前向后遍历(如前向迭代器),还可以从后向前遍历。这类迭代器提供了比输入迭代器、输出迭代器和前向迭代器更多的功能,通常用于双向链表(如 std::list)等容器。

list有一个重要性质:插入操作(insert)和接合操作(splice)都不会造成原有的list迭代器失效。这在vector是不成立。因为vector的插入操作可能造成记忆体重新配置,导致原有迭代器全部失效。甚至list的元素删除操作,也只有"指向被删除元素"的那个迭代器失效,其他迭代器不受任何影响。

4、list的数据结构

SGI list是一个环状双向链表,所以只需要一个指针,便可以完整表现整个链表:

template<class T, class Alloc = alloc>
class list{
protected:
    typedef __list_node<T> list_node;
public:
    typedef list_node* link_type;
    
protected:
    link_type node;		//只需要一个指针,便可完成整个环状环状双向链表
}

如果让指针node指向尾端的一个空白节点(也可以理解为头部的空白节点,因为这是一个环状双向链表),node便能符合STL对于“前闭后开”区间的原则,成为last迭代器

iterator begin() { return (link_type)(*node).next; }
iterator end() { return node; }
bool empty() { return node->next == node; }

5、list的常用API

构造函数

// 默认构造函数,构造一个空的list
list();

// 构造包含n个val元素的list
explicit list(size_type n, const T& val = T());

// 使用区间[first, last)构造list
template <class InputIterator>
list(InputIterator first, InputIterator last);

// 拷贝构造函数
list(const list& x);

// 移动构造函数
list(list&& x) noexcept;

// 列表初始化构造函数
list(std::initializer_list<T> ilist);

赋值操作符

// 赋值操作符
list& operator=(const list& x);

// 移动赋值操作符
list& operator=(list&& x) noexcept;

// 列表赋值操作符
list& operator=(std::initializer_list<T> ilist);

大小相关函数

// 返回list中的元素个数
size_type size() const noexcept;

// 判断list是否为空
bool empty() const noexcept;

// 返回list可以容纳的最大元素个数
size_type max_size() const noexcept;

访问元素

// 返回第一个元素的引用
T& front();
const T& front() const;

// 返回最后一个元素的引用
T& back();
const T& back() const;

修改list

// 在末尾插入元素
void push_back(const T& val);
void push_back(T&& val);

// 在开头插入元素
void push_front(const T& val);
void push_front(T&& val);

// 移除末尾元素
void pop_back();

// 移除开头元素
void pop_front();

// 清空所有元素
void clear() noexcept;

// 插入元素
iterator insert(const_iterator position, const T& val);
iterator insert(const_iterator position, T&& val);
iterator insert(const_iterator position, size_type n, const T& val);
template <class InputIterator>
iterator insert(const_iterator position, InputIterator first, InputIterator last);
iterator insert(const_iterator position, std::initializer_list<T> ilist);

// 删除指定位置的元素
iterator erase(const_iterator position);
iterator erase(const_iterator first, const_iterator last);

// 重置大小
void resize(size_type n);
void resize(size_type n, const T& val);

// 交换两个list
void swap(list& x) noexcept;

// 移除等于val的所有元素
void remove(const T& val);

// 移除满足谓词的所有元素
template <class Predicate>
void remove_if(Predicate pred);

迭代器

// 返回指向第一个元素的迭代器
iterator begin() noexcept;
const_iterator begin() const noexcept;
const_iterator cbegin() const noexcept;

// 返回指向末尾元素之后的迭代器
iterator end() noexcept;
const_iterator end() const noexcept;
const_iterator cend() const noexcept;

特殊操作

// 合并另一个已排序的list到当前list
void merge(list& x);
template <class Compare>
void merge(list& x, Compare comp);

// 反转list中元素的顺序
void reverse() noexcept;

// 移除重复的元素
void unique();
template <class BinaryPredicate>
void unique(BinaryPredicate binary_pred);

// 对list中的元素进行排序
void sort();
template <class Compare>
void sort(Compare comp);

6、与vector进行比较

1. 底层实现

  • std::list:双向链表,每个元素都存储有前后两个指针,指向前一个和后一个元素。
  • std::vector:动态数组,内存是连续的,元素按顺序排列在一块连续的内存空间中。

2. 内存分配

  • std::list:由于链表的性质,std::list 中的元素分散存储,每个元素都有指向下一个和上一个节点的指针,内存不连续,存储效率较低。每次插入和删除时,可能需要动态分配或释放内存。
  • std::vectorstd::vector 使用连续的内存块存储数据,内存效率更高,但如果 vector 扩展空间时需要重新分配内存,可能会导致性能下降。

3. 元素访问

  • std::list:由于是链表结构,无法通过下标直接访问元素。访问某个元素必须通过迭代器从头或尾遍历,时间复杂度为 O(n)。
  • std::vector:支持随机访问,可以通过下标直接访问任何元素,时间复杂度为 O(1)。

4. 插入和删除操作

  • std::list:在中间或两端插入或删除元素的时间复杂度为 O(1),因为只需要调整指针即可。插入或删除操作非常高效,适合频繁插入删除的场景。
  • std::vector:插入和删除操作在两端(末尾)的时间复杂度为 O(1),在中间的插入删除操作需要移动大量元素,时间复杂度为 O(n)。

5. 迭代器的稳定性

  • std::listlist 的迭代器在插入或删除其他元素时仍然有效(除非是删除该迭代器指向的元素)。即,迭代器是稳定的。
  • std::vectorvector 的迭代器在重新分配内存(如扩容)时会失效,即迭代器在发生插入或删除操作后可能会变得无效。

6. 适用场景

  • std::list:适用于需要频繁在中间插入和删除元素的场景,如实现队列、双端队列等。同时适合大小不定的场景,因为不需要频繁分配和移动内存。
  • std::vector:适用于需要随机访问和频繁查询的场景,如实现堆栈、数组等。由于支持快速随机访问,vector 更适合需要频繁读取的情况。

7. 性能比较

  • 内存开销std::list 由于每个元素存储了两个指针(前驱和后继),因此在内存上开销较大。std::vector 的内存开销较小,因为它只存储元素本身,且内存是连续的。
  • 插入删除std::list 插入和删除的性能通常优于 std::vector,特别是在中间位置插入删除时。std::vector 插入和删除的性能会因内存搬移和重新分配而下降。
  • 遍历和访问std::vector 在遍历和随机访问时具有更好的性能,因为内存是连续的,能更好地利用缓存。std::list 的遍历速度较慢,特别是随机访问时性能较差。

8. 扩展性

  • std::list:每次插入或删除操作都只需调整相关节点的指针,不需要像 vector 那样重新分配或移动大块内存。因此,list 适合不确定大小的动态操作。
  • std::vector:当容量不足时,vector 会一次性分配更多的内存,并将现有元素复制到新内存中,这样可以减少频繁的内存重新分配。因此,vector 的扩展策略是一种渐进式的优化方式。

9. 常见操作的时间复杂度

操作std::list 时间复杂度std::vector 时间复杂度
访问元素O(n)O(1)
插入/删除(中间)O(1)O(n)
插入/删除(末尾)O(1)O(1)
插入/删除(开头)O(1)O(n)
迭代器遍历O(n)O(n)

结论:

  • 使用 std::list:当你需要频繁在容器中间插入或删除元素时,std::list 是更好的选择,因为它能够在常数时间内完成这些操作。
  • 使用 std::vector:当你需要频繁访问元素,特别是随机访问时,std::vector 是更好的选择,它能够在常数时间内完成随机访问操作,并且在遍历时性能更好。

选择建议:

  • 如果你的应用程序中更多是读取操作(比如通过下标访问元素),并且插入和删除操作相对较少,使用 std::vector
  • 如果你的应用程序中需要频繁地在容器中插入和删除元素,尤其是在中间位置操作,使用 std::list

标签:std,const,迭代,元素,list,剖析,vector,深入
From: https://blog.csdn.net/2404_87273268/article/details/142554094

相关文章

  • 深入解析:Unicode 与 UTF-8 在 Python 中的秘密武器
    引言字符编码是计算机科学中的一个重要领域,它定义了如何将人类可读的文字转换为机器能够理解的形式。随着互联网的发展,不同的语言和符号需要在全球范围内共享,这就对字符编码提出了更高的要求。Unicode标准就是为了满足这种需求而诞生的,它提供了一套统一的字符集,几乎涵盖了所有现代......
  • 深入理解 Nuxt.js 中的 app:created 钩子
    title:深入理解Nuxt中的appcreated钩子date:2024/9/26updated:2024/9/26author:cmdragonexcerpt:摘要:本文深入介绍了Nuxt.js中的app:created钩子,包括其触发时机、用途及使用方法。通过创建Nuxt项目、编写插件实现钩子、注册全局组件和配置,展示了在应用初始......
  • 深入理解 JSX:构建 React 用户界面的利器
    目录一、JSX介绍1.JSX概念2.为什么使用JSX,JSX有什么好处?二、JSX基本语法1.基本元素: 2.嵌套元素:3.组件:4.属性: 5.表达式6.条件渲染:7.样式:三、JSX语法规则四、JSX编译过程五、JSX小案例1.待办事项列表2.计时器应用六、总结一、JSX介绍1.JSX概念    ......
  • 鸿蒙应用开发——Scroll/List组件无法触发滑动,检查子组件的高度是否被固定/是否内嵌了
    鸿蒙应用开发——Scroll/List组件无法触发滑动一、检查子组件的高度是否被固定若Scroll/List组件的子组件的高度超出了Scroll/List组件高度则能够滚动,此时子组件的高度固定且不超过Scroll/List组件的高度的话,就无法滚动。这种情况直接取消子组件的固定高度即可,例如去掉height:'1......
  • 降准降息一揽子措施点燃 A 股激情,4% 大涨之后趋势深度剖析
    文章目录牛回速归原因分析引爆点情绪和信心一根大阳线,千军万马来相见阴霾是否一扫而空还未可知流动性和增量潜在隐患等待经济复苏配套政策期待中美关系进展短期内趋势分析空军短期内仍有余力如何看待第2日的回撤外围趋势分析结论短期内可能仍有波折中长期会是一波大......
  • 并发处理的利器:深入探讨锁分离设计+6大分离场景(高并发篇)
    锁分离设计的本质在于将对共享资源的访问操作根据其类型或性质区分开来,并为每种操作提供独立的锁。这种设计背景通常源于对高并发系统的需求,其中多个线程或进程需要频繁地对共享资源进行读写或其他操作。在传统的锁机制中,所有操作都可能使用同一把锁,这在高并发环境下会导致......
  • 【Java】JVM垃圾收集器深入解析:原理与实践
    目录一、判断对象是否存活1.引用计数算法2.可达性计数算法3.Java中的四种引用 3.1强引用(StrongReference)3.2软引用(SoftReference)3.3弱引用(WeakReference)3.4虚引用(PhantomReference)3.5小结二、垃圾收集算法1.分代收集理论1.1分代存储1.2分......
  • 10 列表 List 公共功能
    1、len获取列表长度。#获取长度users=["李邵奇","奇航",99]val=len(users)print(val)#32、索引获取一个字符。#索引users=["李邵奇","奇航",99]val=users[0]#"李邵奇"print(val)3、切片获取一段字符串(子序列)。users=["李邵奇",&q......
  • 【C++】面向对象编程的三大特性:深入解析继承机制
    C++语法相关知识点可以通过点击以下链接进行学习一起加油!命名空间缺省参数与函数重载C++相关特性类和对象-上篇类和对象-中篇类和对象-下篇日期类C/C++内存管理模板初阶String使用String模拟实现Vector使用及其模拟实现List使用及其模拟实现容器适配器Stack与QueuePriority......
  • 「漏洞复现」用友U8 CRM config/relobjreportlist.php SQL注入漏洞
    0x01 免责声明请勿利用文章内的相关技术从事非法测试,由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,作者不为此承担任何责任。工具来自网络,安全性自测,如有侵权请联系删除。本次测试仅供学习使用,如若非法他用,与平台和本文作者无关,需......