大家好啊,一周没见了。我们也闲话少说,直接发车,今天来聊聊C++中的list !
1. list的介绍
在C++标准库中,std::list
是一个双向链表容器,它提供了高效的插入和删除操作,但访问元素的效率较低。std::list
的特点包括:
- 元素存储在链表中,插入和删除操作的时间复杂度为O(1);
- 不支持随机访问,需要通过迭代器进行遍历;
- 支持在任何位置插入和删除元素,不会造成内存的重新分配。
演示代码
下面是一个简单的示例代码,演示如何使用std::list
:
#include <iostream>
#include <list>
int main() {
// 创建一个空的list
std::list<int> mylist;
// 在list末尾插入元素
mylist.push_back(1);
mylist.push_back(2);
mylist.push_back(3);
// 在list开头插入元素
mylist.push_front(0);
// 遍历list并输出元素
for (auto it = mylist.begin(); it != mylist.end(); ++it) {
std::cout << *it << " ";
}
return 0;
}
在上面的代码中,我们首先创建了一个空的std::list
对象mylist
,然后使用push_back
在list的末尾插入了三个元素,使用push_front
在list的开头插入了一个元素。最后,我们通过迭代器it
遍历list,并输出元素的值。
std::list
是一个非常灵活的容器,适合在需要频繁插入和删除操作的场景中使用。
1.2 list的使用
创建list并插入元素
#include <iostream>
#include <list>
int main() {
// 创建一个空的list
std::list<int> mylist;
// 在list末尾插入元素
mylist.push_back(1);
mylist.push_back(2);
mylist.push_back(3);
// 在list开头插入元素
mylist.push_front(0);
// 遍历list并输出元素
for (auto it = mylist.begin(); it != mylist.end(); ++it) {
std::cout << *it << " ";
}
return 0;
}
在上面的示例中,我们创建了一个空的std::list
对象mylist
,然后使用push_back
在list的末尾插入了三个元素,使用push_front
在list的开头插入了一个元素。最后,通过迭代器遍历list并输出元素的值。
删除元素
#include <iostream>
#include <list>
int main() {
std::list<int> mylist = {1, 2, 3, 4, 5};
// 删除第一个元素
mylist.pop_front();
// 删除最后一个元素
mylist.pop_back();
// 删除指定位置的元素
auto it = std::find(mylist.begin(), mylist.end(), 3);
if (it != mylist.end()) {
mylist.erase(it);
}
// 遍历list并输出元素
for (auto num : mylist) {
std::cout << num << " ";
}
return 0;
}
在上面的代码中,我们演示了如何使用pop_front
和pop_back
分别删除第一个元素和最后一个元素,以及如何使用erase
删除指定位置的元素。
其他常用操作
除了插入和删除操作外,std::list
还提供了许多其他常用操作,如size()
获取元素个数、empty()
判断是否为空、clear()
清空所有元素等。
通过灵活运用这些操作,可以更好地利用std::list
的特性,完成各种需求。
2.1 模拟实现list
在本节中,我们将尝试模拟实现一个简单的双向链表,类似于std::list
的基本功能。我们将实现链表节点类和链表类,并添加常见的插入、删除、遍历等操作。
链表节点类的实现
首先,我们定义链表节点类Node
,包含数据成员和指向前后节点的指针。
#include <iostream>
template <typename T>
struct Node {
T data;
Node* prev;
Node* next;
Node(const T& value) : data(value), prev(nullptr), next(nullptr) {}
};
链表类的实现
接下来,我们定义链表类MyList
,包含头尾指针和链表大小,并实现插入、删除、遍历等操作。
template <typename T>
class MyList {
private:
Node<T>* head;
Node<T>* tail;
size_t size;
public:
MyList() : head(nullptr), tail(nullptr), size(0) {}
void push_back(const T& value) {
Node<T>* newNode = new Node<T>(value);
if (head == nullptr) {
head = tail = newNode;
} else {
tail->next = newNode;
newNode->prev = tail;
tail = newNode;
}
size++;
}
void pop_back() {
if (size == 0) {
return;
}
Node<T>* temp = tail;
tail = tail->prev;
delete temp;
size--;
}
void print() {
Node<T>* current = head;
while (current != nullptr) {
std::cout << current->data << " ";
current = current->next;
}
std::cout << std::endl;
}
size_t getSize() const {
return size;
}
~MyList() {
while (head != nullptr) {
Node<T>* temp = head;
head = head->next;
delete temp;
}
}
};
演示代码
int main() {
MyList<int> myList;
myList.push_back(1);
myList.push_back(2);
myList.push_back(3);
myList.print(); // 输出: 1 2 3
myList.pop_back();
myList.print(); // 输出: 1 2
std::cout << "Size: " << myList.getSize() << std::endl; // 输出: Size: 2
return 0;
}
在上面的示例中,我们模拟实现了一个简单的双向链表类MyList
,包括了push_back
插入元素、pop_back
删除尾部元素、print
打印链表内容、getSize
获取链表大小等操作。
2.2 list的反向迭代器
在本节中,我们将讨论C++标准库中std::list
提供的反向迭代器,以及如何使用它来逆向遍历链表中的元素。
反向迭代器的概念
std::list
提供了rbegin()
和rend()
成员函数,分别返回指向最后一个元素和指向第一个元素前一个位置的反向迭代器。通过反向迭代器,我们可以逆向遍历链表,实现从尾部到头部的遍历。
演示代码
下面是一个简单的示例代码,演示如何使用反向迭代器逆向遍历std::list
:
#include <iostream>
#include <list>
int main() {
std::list<int> myList = {1, 2, 3, 4, 5};
// 使用正向迭代器正向遍历
std::cout << "正向遍历:" << std::endl;
for (auto it = myList.begin(); it != myList.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
// 使用反向迭代器逆向遍历
std::cout << "反向遍历:" << std::endl;
for (auto rit = myList.rbegin(); rit != myList.rend(); ++rit) {
std::cout << *rit << " ";
}
std::cout << std::endl;
return 0;
}
在上面的示例中,我们首先创建了一个std::list
并初始化了一些整数元素。然后,我们使用正向迭代器和反向迭代器分别进行正向和逆向遍历,并输出遍历结果。
详细解释
rbegin()
返回指向最后一个元素的反向迭代器,rend()
返回指向第一个元素前一个位置的反向迭代器。- 逆向遍历时,需要使用
rbegin()
作为起始迭代器,rend()
作为结束迭代器,并通过++rit
递增迭代器来遍历元素。
通过反向迭代器,我们可以方便地实现链表的逆向遍历操作,提高了代码的可读性和灵活性。
3. list与vector的对比
在本节中,我们将比较C++标准库中的std::list
和std::vector
两种容器,分析它们的特点、优缺点以及适用场景。
3.1 list与vector的特点
-
std::list:
- 底层实现为双向链表,支持高效的插入和删除操作。
- 不支持随机访问,访问元素的时间复杂度为O(n)。
- 适用于频繁的插入和删除操作,不需要随机访问元素的场景。
-
std::vector:
- 底层实现为动态数组,支持随机访问和快速的尾部插入删除操作。
- 支持通过下标快速访问元素,访问时间复杂度为O(1)。
- 适用于需要频繁访问元素、尾部插入删除操作和内存连续存储的场景。
3.2 比较示例
下面是一个简单的示例代码,演示了std::list
和std::vector
的对比:
#include <iostream>
#include <list>
#include <vector>
int main() {
// 使用std::list
std::list<int> myList;
for (int i = 0; i < 5; ++i) {
myList.push_back(i);
}
// 使用std::vector
std::vector<int> myVector;
for (int i = 0; i < 5; ++i) {
myVector.push_back(i);
}
// 访问元素比较
std::cout << "std::list元素访问:" << std::endl;
for (auto it = myList.begin(); it != myList.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
std::cout << "std::vector元素访问:" << std::endl;
for (int i = 0; i < myVector.size(); ++i) {
std::cout << myVector[i] << " ";
}
std::cout << std::endl;
return 0;
}
在上面的示例中,我们分别使用std::list
和std::vector
存储相同的元素,并比较它们的元素访问方式。
详细解释
std::list
适用于频繁的插入删除操作,不支持随机访问,访问元素需要通过迭代器。std::vector
适用于需要随机访问、尾部插入删除操作的场景,可以通过下标直接访问元素。
根据具体的需求和场景选择合适的容器,可以提高代码的效率和性能。
4. 相关OJ题
在本节中,我们将通过一些相关的OJ题目来帮助大家更好地理解和运用C++中的std::list
容器。
OJ题目1:反转链表
问题描述: 给定一个单向链表,将其反转并返回新的头节点。
示例输入:
1 -> 2 -> 3 -> 4 -> 5
示例输出:
5 -> 4 -> 3 -> 2 -> 1
解题思路: 使用std::list
可以方便地实现链表的反转操作。
#include <iostream>
#include <list>
struct ListNode {
int val;
ListNode* next;
ListNode(int x) : val(x), next(nullptr) {}
};
ListNode* reverseList(ListNode* head) {
std::list<int> tempList;
ListNode* curr = head;
while (curr) {
tempList.push_front(curr->val);
curr = curr->next;
}
ListNode* newHead = new ListNode(0);
ListNode* temp = newHead;
for (int val : tempList) {
temp->next = new ListNode(val);
temp = temp->next;
}
return newHead->next;
}
int main() {
ListNode* head = new ListNode(1);
head->next = new ListNode(2);
head->next->next = new ListNode(3);
head->next->next->next = new ListNode(4);
head->next->next->next->next = new ListNode(5);
ListNode* reversedHead = reverseList(head);
while (reversedHead) {
std::cout << reversedHead->val << " ";
reversedHead = reversedHead->next;
}
return 0;
}
在上面的示例中,我们通过std::list
实现了链表的反转操作,将链表元素存储在std::list
中,然后倒序构建新的链表。
OJ题目2:合并两个有序链表
问题描述: 给定两个有序单向链表,将它们合并为一个有序链表并返回。
示例输入:
List1: 1 -> 3 -> 5
List2: 2 -> 4 -> 6
示例输出:
1 -> 2 -> 3 -> 4 -> 5 -> 6
解题思路: 使用std::list
的排序功能可以方便地合并两个有序链表。
#include <iostream>
#include <list>
struct ListNode {
int val;
ListNode* next;
ListNode(int x) : val(x), next(nullptr) {}
};
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
std::list<int> tempList;
while (l1) {
tempList.push_back(l1->val);
l1 = l1->next;
}
while (l2) {
tempList.push_back(l2->val);
l2 = l2->next;
}
tempList.sort();
ListNode* newHead = new ListNode(0);
ListNode* temp = newHead;
for (int val : tempList) {
temp->next = new ListNode(val);
temp = temp->next;
}
return newHead->next;
}
int main() {
ListNode* l1 = new ListNode(1);
l1->next = new ListNode(3);
l1->next->next = new ListNode(5);
ListNode* l2 = new ListNode(2);
l2->next = new ListNode(4);
l2->next->next = new ListNode(6);
ListNode* mergedList = mergeTwoLists(l1, l2);
while (mergedList) {
std::cout << mergedList->val << " ";
mergedList = mergedList->next;
}
return 0;
}
在上面的示例中,我们通过std::list
的排序功能实现了两个有序链表的合并操作,将两个链表元素存储在std::list
中,然后进行排序构建新的有序链表。
好了,感谢大家看到这,如果觉得本篇文章对你有帮助的话,还请点个赞支持一下,有什么问题也可以评论区留言,那么我们下次再见了,Peace~