list
std::list 和 std::vector 是两种不同的数据结构,std::vector 是基于数组的动态数组,而 std::list 是基于双向链表的数据结构。
list适用于需要在序列中执行频繁插入和删除操作的场景。
list的特性
双向链表:list是双向链表,允许在序列的两端和中间执行搞笑的插入和删除操作。
不支持随机访问:与vector和deque不同,list不支持通过索引进行常量时间内的随机访问。要访问list的元素,必须通过迭代器进行。
动态内存管理:list的内部实现使用节点,每个节点都包含一个元素和指向前后节点的指针。这种结构使得list在执行插入和删除操作时能够更好地管理内存。
保持迭代器有效性: list在进行插入和删除操作时,能够更好地保持迭代器的有效性。这意味着在进行这些操作后,不会导致所有迭代器失效。
高效的插入和删除操作: 由于list是双向链表,插入和删除操作在两端和中间都是常量时间的,使其成为处理这类操作的理想容器。
list的性能考虑
插入和删除操作: 如果主要进行频繁的插入和删除操作,并且不需要随机访问元素,list可能比vector和deque更为高效。
随机访问: 如果需要通过索引进行随机访问元素,使用vector可能更为合适,因为它提供了常量时间的随机访问。
内存使用: 由于list使用了链表结构,可能引入一些额外的内存开销。在内存使用方面,vector和deque可能更为紧凑
C++标准库中list的基本用法
//要使用list,首先需要包含相关的头文件:
#include <list>
//接下来,可以声明一个list对象,并开始使用它:
std::list<int> myList;
//可以使用push_front和push_back在list的前端和后端插入元素:
myList.push_front(1);
myList.push_back(2);
//使用pop_front和pop_back从list的前端和后端删除元素:
myList.pop_front();
myList.pop_back();
list工作原理
蓝色矩形框:堆内存
红色矩形块:栈内存
红色箭头:next指针
蓝色箭头:prev指针
如图展示了一个list
从初始化到完成多次插入的流程。List
类也有一个控制结构, 包含head
, tail
, size
三个成员, 分别控制链表的头尾指针和大小, 让我们梳理其生命流程:
- 初始时刻, 其链表为空,
size=0
, 均为空指针, 只有这些控制结构存储在栈上 - 执行
push_back(1)
, 在堆内存中分配了一个Node
(参考后文实现, 其实就是个双向链表节点), 将head
,tail
指向这个节点, 更新size=1
- 执行
push_back(2)
, 在堆内存中分配了一个Node
, 需要注意的时, 这个新的节点不需要和旧的节点内存连续, 更新原tail
指向节点的next
, 将tail
指向这个节点, 并且将新的节点的prev
指向原来的tail
, 更新size=2
- 执行
push_back(3)
, 操作与3相同, 此过程完成后,size=3
- 执行
push_front(0)
, 在堆内存中分配了一个Node
, 需要注意的时, 这个新的节点不需要和旧的节点内存连续, 此时更新原head
指向节点的prev
, 将head
指向这个节点, 并且将新的节点的next
指向原来的head
, 更新size=4
实现list
#pragma once
#include <iostream>
#include <stdexcept>
#include <iostream>
#include <algorithm>
#include <sstream>
#include <string>
template <typename T>
class List
{
public:
public:
template <typename L>
friend std::ostream& operator<<(std::ostream& os, const List<L>& pt);
private:
//定义节点结构
struct Node
{
T data;
Node* next;//指向下一个节点的指针
Node* prev;//指向前一个节点的指针
//构造函数
Node(const T& value, Node* nextNode = nullptr, Node* prevNode = nullptr)
:data(value), next(nextNode), prev(prevNode) {}
};
Node *head;//头节点指针
Node* tail;//尾节点指针
size_t size;//链表中节点的数量
public:
//构造函数
List()
:head(nullptr)
,tail(nullptr)
,size(0)
{}
//析构函数
~List() {clear();}
//在链表末尾添加元素
void push_back(const T& value)
{
//创建新的节点
Node* newNode = new Node(value, nullptr, tail);
if (tail)
{
//如果链表非空,将尾节点的next指针指向新节点
tail->next = newNode;
}
else
{
//如果链表为空,新节点同时也是头节点
head = newNode;
}
//更新尾节点指针和链表大小
tail = newNode;
++size;
}
//在链表开头添加元素
void push_front(const T& value)
{
//创建新的节点
Node* newNode = new Node(value, head, nullptr);
if (head)
{
//如果链表非空,将头节点的prev 指针指向新节点
head->prev = nextNodel;
}
else
{
//如果链表为空,新节点同时也是尾节点
tail = newNode;
}
//更新头节点指针和链表大小
head = newNode;
++size;
}
//获取链表中节点的数量
size_t getSize() const { return size; }
//访问链表中的元素
T& operator[](size_t index)
{
//从头节点开始遍历链表,找到第index个节点
Node* current = head;
for (size_t i = 0; i < index; ++i)
{
if (!current)
{
//如果index 超出链表长度,则抛出异常
throw std::out_of_range("Index out of range");
}
current = current->next;
}
//返回节点中的数据
return = current->next;
}
//删除链表末尾的元素
void pop_back()
{
if (size > 0)
{
//获取尾节点的前一个节点
Node *newTail = tail->prev;
//删除尾节点
delete tail;
//更新尾节点指针和链表大小
tail = newTail;
if (tail)
{
tail->next = nullptr;
}
else
{
head = nullptr;//如果链表为空,头节点也置为空
}
--size;
}
}
//删除链表开头元素
void pop_front()
{
if (size > 0)
{
//获取头节点的下一个节点
Node* newHead = head->next;
//删除头节点
delete;
//更新头节点指针和链表大小
head = newHead;
if (head)
{
head->prev = nullptr;
}
else
{
tail = nullptr;//如果链表为空,尾节点也置为空
}
--size;
}
}
//获取指定节点
Node* getNode(const T &val)
{
Node* node = head;
while (node != nullptr && node->val != val)
{
node = node->next;
}
return node;
}
T* find(const T& val)
{
Node* node = getNode(val);
if (node == nullptr)
{
return nullptr;
}
return &node->val;
}
//删除指定节点
void remove(const T &val)
{
Node* node = head;
while (node != nullptr && node->val != val)
{
node = node->next;
}
if (node == nullptr)
{
//没有找到
return;
}
else if (node != head && node != tail)
{
//既不是头节点也不是尾节点
node->pre->next = node->next;
node->next->pre = node->pre;
}
else if (node == head && node == tail)
{
//既是头节点又是尾节点
head = nullptr;
node = nullptr;
}
else if (node == head)
{
//是头节点
head = node->next;
head->pre = nulllptr;
}
}
bool empty() { return size == 0; }
//清空链表
void clear()
{
while (head)
{
//从头结点开始,依次删除节点
Node* temp = head;
head = head->next;
delete temp;
}
//更新尾节点指针和指针大小
tail = nullptr;
size = 0;
}
//使用迭代器遍历链表的开始位置
Node* begin() { return head; }
//使用迭代器遍历链表的位置
Node* end() { return nullptr; }
// 使用迭代器遍历链表的开始位置(const版本)
const Node* begin() const { return head; }
// 使用迭代器遍历链表的结束位置(const版本)
const Node* end() const { return nullptr; }
// 打印链表中的元素
void printElements() const
{
for (Node* current = head; current; current = current->next)
{
std::cout << current->data << " ";
}
std::cout << std::endl;
}
};
// 重载 << 运算符
template <typename T>
std::ostream& operator<<(std::ostream& os, const List<T>& pt)
{
for (typename List<T>::Node* current = pt.head; current;
current = current->next)
{
os << " " << current->data;
}
os << std::endl;
return os;
}
int main() {
// 创建一个 List 对象
List<int> myList;
// N 行,每行包含一个命令,命令格式为 [operation] [parameters]
int N;
std::cin >> N;
// 读走回车
getchar();
std::string line;
// 接收命令
for (int i = 0; i < N; i++) {
std::getline(std::cin, line);
std::istringstream iss(line);
std::string command;
iss >> command;
int value;
if (command == "push_back") {
iss >> value;
myList.push_back(value);
}
if (command == "push_front") {
iss >> value;
myList.push_front(value);
}
if (command == "pop_back") {
myList.pop_back();
}
if (command == "pop_front") {
myList.pop_front();
}
if (command == "remove") {
iss >> value;
myList.remove(value);
}
if (command == "clear") {
myList.clear();
}
if (command == "size") {
std::cout << myList.getSize() << std::endl;
}
if (command == "get") {
iss >> value;
std::cout << myList[value] << std::endl;
}
if (command == "print") {
if (myList.getSize() == 0) {
std::cout << "empty" << std::endl;
}
else {
myList.printElements();
}
}
}
return 0;
}