C++——list链表


❀保持低旋律节奏->个人主页

专栏链接:《C++学习》《Linux学习》


文章目录

一、list介绍

list官方文档介绍

list容器使用双链表来实现,每个元素指针通过前驱和后续相关联。元素可以存储在不连续的不同位置。

  • 核心优势
    1.任意位置的插入 / 删除操作是常数时间(O (1)):只要有对应位置的迭代器,在列表的任何位置插入或删除元素都很高效
    2.支持双向迭代:可以向前或向后遍历元素。
  • 主要劣势
    1.不支持随机访问:无法像数组、vector 那样通过下标 [] 直接访问元素(例如访问第 6 个元素,必须从已知位置遍历,时间复杂度为 O (n))。
    2.内存开销大:每个元素需要额外存储前驱和后继指针,对于小元素、大规模列表,内存消耗会比较明显
  • 与其他容器相比
    1.对比 vector/array/deque:在 "任意位置的插入 / 删除" 场景下表现更好,适合排序等频繁操作元素位置的算法。
    2.对比 forward_list:forward_list 是单向链表,更小巧高效,但仅支持单向遍历;std::list 是双向链表,支持双向遍历。

双链表介绍

哨兵节点的介绍

在链表数据结构中,哨兵节点(Sentinel Node) 是一个不存储实际业务数据的虚拟节点,主要作用是统一链表操作的逻辑,消除边界条件的特殊处理,让代码更简洁、更不易出错。

场景 无哨兵实现 有哨兵实现
链表为空时 需要单独判断 head == nullptr 无需判断(哨兵的 next 自然是 nullptr)
删除头节点时 需要单独处理(头节点无前驱) 无需特殊处理(头节点的前驱是哨兵)
代码复杂度 多分支判断,逻辑分散 逻辑统一,无额外边界判断
出错概率 高(容易遗漏边界情况) 低(所有节点处理逻辑一致)

有无哨兵节点的对比------底层!

cpp 复制代码
//存在哨兵的代码
#include <list>
#include<iostream>
using namespace std;
template<class T>
class ListNode
{
public:
	//using T* = ListNode*; //T是一个具体的类ListNode是一个结构体
	//using node = ListNode<T>;  //ListNode<T> 是一个结构体 node 也是一个结构体
	T val;
	ListNode<T>* prev;
	ListNode<T>* next;
	ListNode(T val) : val(val), prev(nullptr), next(nullptr) {}
};


template<class T>

ListNode<T>* del(ListNode<T>*head,T target)
{
	//using node = ListNode<T>;
	//边界一:链表为空
	if (head == nullptr)return nullptr;
	
	//边界二:删除头节点
	if (head->val == target)
	{
		ListNode<T>* newHead = head->next;
		delete head;
		return newHead;
	}

	//情况三:处理非头节点

	ListNode<T>* prev = head;
	while (prev->next != nullptr)
	{
		if (prev->next->val == target)
		{
			ListNode<T>* toDelete = prev->next;
			// 同步更新后继节点的prev指针(双向链表关键)
			if (toDelete->next != nullptr) {
				toDelete->next->prev = prev;
			}
			prev->next = toDelete->next;
			delete toDelete;
			break;
		}
		prev = prev->next;
	}
	return head;
}
int main() {
	// 创建链表:1 <-> 2 <-> 3
	ListNode<int>* head = new ListNode<int>(1);
	head->next = new ListNode<int>(2);
	head->next->prev = head;  // 2的prev指向1
	head->next->next = new ListNode<int>(3);
	head->next->next->prev = head->next;  // 3的prev指向2

	// 删除值为2的节点
	head = del(head, 2);

	// 验证结果:1 <-> 3(正向和反向遍历)
	cout << "正向遍历:";
	ListNode<int>* cur = head;
	while (cur != nullptr) {
		cout << cur->val << " ";
		cur = cur->next;
	}
	// 输出:1 3

	cout << "\n反向遍历:";
	cur = head->next;  // 此时cur指向3
	while (cur != nullptr) {
		cout << cur->val << " ";
		cur = cur->prev;  // 反向遍历(依赖prev指针)
	}
	// 输出:3 1

	return 0;
}
cpp 复制代码
//无哨兵的代码
#include <list>
#include<iostream>
using namespace std;

template<class T>
class ListNode
{
public:
	T val;
	ListNode<T>* prev;
	ListNode<T>* next;
	ListNode(T val) : val(val), prev(nullptr), next(nullptr) {}
};


template<class T>
ListNode<T>* del(ListNode<T>* head, T target)
{
	// 创建哨兵节点(值无意义,仅作虚拟头节点)
	ListNode<T>* sentinel = new ListNode<T>(0);
	sentinel->next = head;  // 哨兵指向原头节点
	if (head != nullptr) {
		head->prev = sentinel;  // 原头节点的prev指向哨兵(双向链表维护)
	}

	// 从哨兵开始遍历(统一所有节点的前驱逻辑)
	ListNode<T>* prev = sentinel;
	while (prev->next != nullptr)
	{
		if (prev->next->val == target)
		{
			ListNode<T>* toDelete = prev->next;
			// 同步更新后继节点的prev指针
			if (toDelete->next != nullptr) {
				toDelete->next->prev = prev;
			}
			prev->next = toDelete->next;
			delete toDelete;
			break;
		}
		prev = prev->next;
	}

	// 恢复新头节点,释放哨兵
	ListNode<T>* newHead = sentinel->next;
	if (newHead != nullptr) {
		newHead->prev = nullptr;  // 新头节点prev置空(脱离哨兵)
	}
	delete sentinel;  // 哨兵完成使命,释放内存

	return newHead;  // 返回新头节点
}

int main() {
	// 创建链表:1 <-> 2 <-> 3
	ListNode<int>* head = new ListNode<int>(1);
	head->next = new ListNode<int>(2);
	head->next->prev = head;  // 2的prev指向1
	head->next->next = new ListNode<int>(3);
	head->next->next->prev = head->next;  // 3的prev指向2

	// 删除值为2的节点
	head = del(head, 2);

	// 验证结果:1 <-> 3(正向和反向遍历)
	cout << "正向遍历:";
	ListNode<int>* cur = head;
	while (cur != nullptr) {
		cout << cur->val << " ";
		cur = cur->next;
	}
	// 输出:1 3

	cout << "\n反向遍历:";
	cur = head->next;  // 此时cur指向3
	while (cur != nullptr) {
		cout << cur->val << " ";
		cur = cur->prev;  // 反向遍历(依赖prev指针)
	}
	// 输出:3 1

	return 0;
}

二、list使用

list中的接口比较多,此处类似,只需要掌握如何正确的使用,然后再去深入研究背后的原理,已

达到可扩展的能力。以下为list中一些常见的重要接口。

2.1list构造

常见的构造 list构造

构造函数( (constructor)) 接口说明
list (size_type n, const value_type& val =value_type()) 造的list中包含n个值为val的元素
list() 构建空list
list(const list&x) 拷贝构造
list (InputIterator first, InputIterator last) 用[first, last)区间中的元素构造
2.2list iterator的使用

list官方文档介绍

此处,大家可暂时将迭代器理解成一个指针,该指针指向list中的某个节点。

1.begin与end为正向迭代器,对迭代器执行++操作,迭代器向后移动

2.rbegin(end)与rend(begin)为反向迭代器,对迭代器执行++操作,迭代器向前移动

2.3list capacity

list官方文档介绍

2.4 ❀list element access
函数声明 接口使用
front 返回list的第一个节点中值的引用
back 返回list最后一个节点中值的引用
2.5list modifiers

list官方文档介绍

迭代器失效

前面说过,此处大家可将迭代器暂时理解成类似于指针,迭代器失效即迭代器所指向的节点的无效,即该节点被删除了。因为list的底层结构为带头结点的双向循环链表,因此在list中进行插入时是不会导致list的迭代器失效的,只有在删除时才会失效,并且失效的只是指向被删除节点的迭代器,其他迭代器不会受到影响。

类型 insert erase
string 1.若插入后未触发扩容:插入位置及之后的迭代器全部失效(元素后移,位置改变)。2.若插入后触发扩容:所有迭代器、指针、引用全部失效(内存地址整体改变)。 删除位置及之后的迭代器全部失效(元素前移,位置改变)。未删除的元素(删除位置之前)的迭代器仍然有效。
vector 与string 同理 与string同理
list 所有迭代器都不会失效(插入的新元素不会改变原有元素的地址和指针关系)。 只有被删除元素的迭代器失效,其他所有迭代器(包括前驱和后继)仍然有效。

三、list和vector对比

vector list
底层结构 动态顺序表,一段连续空间 带头结点的双向循环链表
随机访问 支持随机访问,访问某个元素效率O(1) 不支持随机访问,访问某个元素效率O(N)
插入和删除 任意位置插入和删除效率低,需要搬移元素,时间复杂度为O(N),插入时有可能需要增容,增容:开辟新空间,拷贝元素,释放旧空间,导致效率更低 任意位置插入和删除效率高,不需要搬移元素,时间复杂度为O(1)
空间利用率 底层为连续空间,不容易造成内存碎片,空间利用率高,缓存利用率高 底层节点动态开辟,小节点容易造成内存碎片,空间利用率低,缓存利用率低
迭代器 原生态指针 对原生态指针(节点指针)进行封装
迭代器失效 在插入元素时,要给所有的迭代器重新赋值,因为插入元素有可能会导致重新扩容,致使原来迭代器失效,删除时,当前迭代器需要重新赋值否则会失效 插入元素不会导致迭代器失效,删除元素时,只会导致当前迭代器失效,其他迭代器不受影响
使用场景 需要高效存储,支持随机访问,不关心插入删除效率 大量插入和删除操作,不关心随机访问

四、对list的一些补充

list::sort

list库里面存在一个单独的sortlist::sort

algorithm 里面也有sortalgorithm::sort

  • 那么两者有什么区别?
    algorithm里面的sort使用的是快速排序,我们都知道快速排序在排序算法里面已经是最快的了
    而list里面的sort使用的是归并排序

在进行大量数据排序比较时,使用list::sort就会花费很大的代价。因此不建议使用list::sort

list 底层迭代器的实现

相关推荐
2401_841495645 小时前
【数据结构】基于Floyd算法的最短路径求解
java·数据结构·c++·python·算法··floyd
.YM.Z5 小时前
数据结构——链表(二)
数据结构·链表
纵有疾風起6 小时前
C++模版:模板初阶及STL简介
开发语言·c++·经验分享·开源
QT 小鲜肉6 小时前
【个人成长笔记】Qt Creator快捷键终极指南:从入门到精通
开发语言·c++·笔记·qt·学习·学习方法
勇闯逆流河6 小时前
【C++】用红黑树封装map与set
java·开发语言·数据结构·c++
实心儿儿6 小时前
C++——内存管理
c++
山,离天三尺三6 小时前
深度拷贝详解
开发语言·c++·算法
我狸才不是赔钱货6 小时前
容器:软件世界的标准集装箱
linux·运维·c++·docker·容器
云知谷6 小时前
【嵌入式基本功】单片机嵌入式学习路线
linux·c语言·c++·单片机·嵌入式硬件