学而时习之:C++中的标准模板5.2

0 阅读9分钟

C++ STL 中的 List (链表)

C++ 中的 list 是一种序列容器,允许你按顺序一个接一个地存储元素。

  • 双向链表 的形式实现,同时维护 前端(front)后端(back) ,以便在两端进行快速操作
  • 数据存储在 非连续的内存 中,允许在链表的任何位置(开头、中间或结尾)进行快速的插入和删除
#include <iostream>
#include <list>
using namespace std;

int main(){
    list<int> myList;  // 声明一个整数链表

    myList.push_back(10);   // 尾部添加 10
    myList.push_back(20);   // 尾部添加 20
    myList.push_front(5);   // 头部添加 5

    cout << "链表元素:";
    for (int n : myList){
        cout << n << " ";
    }
    cout << endl;

    return 0;
}
链表元素:5 10 20

语法

List 定义为 <list> 头文件中的 std::list 类模板。

list<T> l;

其中:

符号含义
T链表中元素的类型
l为链表指定的名称

基本操作

1. 插入元素

  • insert():如果已知位置迭代器,可用于快速插入;否则需要先遍历链表到达该位置
  • push_front():用于在开头插入元素
  • push_back():用于在末尾插入元素
#include <iostream>
#include <list>
using namespace std;

int main(){
    list<int> l = {3, 2};  // 初始链表: [3, 2]

    l.push_back(5);        // 尾部插入 5: [3, 2, 5]
    l.push_front(1);       // 头部插入 1: [1, 3, 2, 5]
    
    auto it = l.begin();   // 获取指向开头的迭代器
    advance(it, 2);        // 迭代器前进 2 步,指向第 3 个元素 (2)
    l.insert(it, 4);       // 在 2 之前插入 4: [1, 3, 4, 2, 5]

    for (auto i : l)
        cout << i << " ";
    return 0;
}
1 3 4 2 5

2. 访问元素

链表不支持随机访问,因此要获取特定位置的元素,需要从头或从尾逐个遍历链表。

第一个和最后一个元素可以使用 front()back() 方法快速访问。

#include <iostream>
#include <list>
using namespace std;

int main(){
    list<int> l = {1, 3, 4, 2, 5};  // 链表: [1, 3, 4, 2, 5]

    cout << l.front() << endl;      // 访问第一个元素
    cout << l.back() << endl;       // 访问最后一个元素
    cout << *next(l.begin(), 2);    // 访问第 3 个元素(索引 2)

    return 0;
}
1
5
4

解释: next(l.begin(), 2) 从链表开头将迭代器向前移动两个位置,* 解引用该迭代器以打印索引 2 处的元素(即第三个元素)。

3. 更新元素

链表元素可以通过迭代器访问,并使用**赋值运算符(=)**设置新值来更新。

由于链表不支持随机访问,必须使用迭代器到达要更新的元素位置。

#include <iostream>
#include <list>
using namespace std;

int main(){
    list<int> l = {1, 3, 4, 2, 5};  // 初始链表: [1, 3, 4, 2, 5]

    l.front() = 11;                  // 更新第一个元素: [11, 3, 4, 2, 5]
    
    auto it = l.begin();             // 获取开头迭代器
    advance(it, 2);                  // 前进 2 步,指向第 3 个元素 (4)
    
    *it = 10;                        // 解引用并更新为 10: [11, 3, 10, 2, 5]

    for (auto i : l)
        cout << i << " ";
    return 0;
}
11 3 10 2 5

4. 查找元素

要在链表中查找元素,可以使用 <algorithm> 库中的 find() 函数。

find() 如果找到元素,则返回指向该元素的迭代器;如果未找到,则返回末尾迭代器(end())。

#include <iostream>
#include <list>
#include <algorithm>  // 需要包含此头文件使用 find()
using namespace std;

int main(){
    list<int> l = {1, 3, 4, 2, 5};  // 链表: [1, 3, 4, 2, 5]

    // 查找元素 4
    auto it = find(l.begin(), l.end(), 4);
    
    if (it != l.end())
        cout << *it;           // 找到,输出该元素
    else
        cout << "元素未找到!";  // 未找到
    
    return 0;
}
4

5. 遍历(Traversing)

链表可以使用 begin()end() 迭代器在循环中进行遍历。

begin() 开始,不断移动迭代器直到到达 end(),沿途访问每个元素。

#include <iostream>
#include <list>
using namespace std;

int main(){
    list<int> l = {1, 3, 4, 2, 5};  // 链表: [1, 3, 4, 2, 5]

    // 使用迭代器遍历
    for (auto it = l.begin(); it != l.end(); ++it)
        cout << *it << " ";
        
    return 0;
}
1 3 4 2 5

遍历方式对比:

方式代码示例适用场景
迭代器遍历for (auto it = l.begin(); it != l.end(); ++it)需要迭代器位置进行插入/删除
范围 for 循环for (auto i : l)只读访问,简洁写法
反向遍历for (auto it = l.rbegin(); it != l.rend(); ++it)从尾到头遍历

反向遍历示例:

#include <iostream>
#include <list>
using namespace std;

int main(){
    list<int> l = {1, 3, 4, 2, 5};

    // 反向遍历(从尾到头)
    cout << "反向: ";
    for (auto it = l.rbegin(); it != l.rend(); ++it)
        cout << *it << " ";  // 输出: 5 2 4 3 1

    // 正向遍历(从头到尾)
    cout << "\n正向: ";
    for (auto it = l.begin(); it != l.end(); ++it)
        cout << *it << " ";  // 输出: 1 3 4 2 5
        
    return 0;
}

6. 删除元素

  • erase():使用指向元素位置的迭代器从链表中删除该元素
  • pop_front()pop_back():快速删除链表的第一个和最后一个元素
#include <iostream>
#include <list>
using namespace std;

int main(){
    list<int> l = {1, 3, 4, 2, 5};  // 初始链表: [1, 3, 4, 2, 5]

    l.pop_back();                    // 删除尾部元素 5: [1, 3, 4, 2]
    l.pop_front();                   // 删除头部元素 1: [3, 4, 2]
    
    auto it = l.begin();             // 获取开头迭代器
    advance(it, 2);                  // 前进 2 步,指向第 3 个元素 (2)
    l.erase(it);                     // 删除元素 2: [3, 4]

    for (auto i : l)
        cout << i << " ";
    return 0;
}
3 4

List 的其他成员函数

函数描述
size()返回链表中元素的数量
empty()检查链表是否为空
rbegin()返回指向链表最后一个元素的反向迭代器
rend()返回指向第一个元素之前位置的反向迭代器
clear()移除链表中的所有元素

C++ STL 中的 Forward List (单向链表)

forward_list 容器提供了单向链表数据结构的实现。它将数据存储在非连续的内存中,每个元素只指向下一个元素。这使得在已知元素位置时,插入和删除操作更快。

语法

单向链表定义为 <forward_list> 头文件中的 std::forward_list 类模板。

forward_list<T> fl;

其中:

符号含义
T单向链表中元素的数据类型
fl为单向链表指定的名称

声明与初始化

单向链表可以通过多种方式声明和初始化,如下例所示:

#include <bits/stdc++.h>
using namespace std;

void printFL(forward_list<int>& fl) {
    for (auto i : fl)
        cout << i << " ";
    cout << '\n';
}

int main() {
    
    forward_list<int> fl1;           // 方式1:空链表
    forward_list<int> fl2(3, 4);     // 方式2:3个元素,每个都是4
    forward_list<int> fl3 = {1, 5, 3, 4};  // 方式3:初始化列表
    
    printFL(fl2);  // 输出: 4 4 4
    printFL(fl3);  // 输出: 1 5 3 4
    
    return 0;
}
4 4 4 
1 5 3 4 

解释:在上面的程序中,我们用三种简单方式初始化单向链表:

方式代码说明
1forward_list<int> fl1创建一个空的整数单向链表
2forward_list<int> fl2(3, 4)创建一个包含 3 个元素的链表,每个元素都是 4
3forward_list<int> fl3 = {1, 5, 3, 4}使用初始化列表创建并初始化链表

与 list(双向链表)的对比

特性forward_list(单向链表)list(双向链表)
头文件<forward_list><list>
指针方向只指向 next(下一个)指向 next 和 prev(上一个)
内存占用更少(只有一个指针)更多(两个指针)
反向遍历❌ 不支持✅ 支持
size() 方法❌ 没有(需手动计算)✅ 有
插入/删除只能向前遍历定位可以双向遍历定位

适用场景: 当只需要单向遍历且内存敏感时,优先使用 forward_list

基本操作

以下是可以在单向链表上执行的基本操作:

1. 访问元素

forward_list 不支持像数组或向量那样的索引访问。元素必须使用迭代器顺序访问(使用 next()advance()),只有第一个元素可以直接使用 front() 访问。

#include <bits/stdc++.h>
using namespace std;

int main() {
    forward_list<int> fl = {1, 5, 3, 4};  // 单向链表: [1, 5, 3, 4]

    // 访问第一个元素
    cout << fl.front() << endl;           // 输出: 1
    
    // 访问第三个元素
    auto it = next(fl.begin(), 2);        // 从开头前进 2 步
    cout << *it;                          // 解引用输出: 3
    
    return 0;
}
1
3

2. 插入元素

元素可以使用 insert_after() 函数插入到单向链表中。它需要指定一个迭代器,在该迭代器之后插入元素。此外,push_front() 方法支持在前端快速插入

#include <bits/stdc++.h>
using namespace std;

int main() {
    forward_list<int> fl = {5, 4};  // 初始: [5, 4]

    // 在前端插入元素
    fl.push_front(1);                // [1, 5, 4]
    
    // 在第二个元素之后插入 3
    auto it = fl.begin();              // 指向 1
    advance(it, 1);                    // 前进 1 步,指向 5
    fl.insert_after(it, 3);            // 在 5 之后插入 3: [1, 5, 3, 4]
    
    for (auto x: fl) cout << x << " ";
    return 0;
}
1 5 3 4 

3. 更新元素

现有元素的值可以通过访问它们并使用赋值运算符来赋新值从而改变。

#include <bits/stdc++.h>
using namespace std;

int main() {
    forward_list<int> fl = {1, 5, 3, 4};  // 初始: [1, 5, 3, 4]

    // 更新第一个元素
    fl.front() = 111;
    cout << fl.front() << endl;          // 输出: 111
    
    // 更新第三个元素
    auto it = next(fl.begin(), 2);       // 指向 3
    *it = 333;
    cout << *it;                         // 输出: 333
    
    return 0;
}
111
333

4. 查找元素

单向链表不提供任何成员函数来搜索元素,但我们可以使用 find() 算法来查找任何给定值。

#include <bits/stdc++.h>
using namespace std;

int main() {
    forward_list<int> fl = {1, 5, 3, 4};  // 链表: [1, 5, 3, 4]

    // 查找 3
    auto it = find(fl.begin(), fl.end(), 3);
    
    if (it != fl.end()) 
        cout << *it;          // 找到,输出: 3
    else 
        cout << "元素未找到";
    
    return 0;
}
3

5. 遍历

单向链表可以使用 begin()end() 迭代器配合循环进行遍历,但只能向前移动,不能向后移动

#include <bits/stdc++.h>
using namespace std;

int main() {
    forward_list<int> fl = {1, 5, 3, 4};  // 链表: [1, 5, 3, 4]
    
    // 使用基于范围的 for 循环遍历
    for(auto i : fl)
        cout << i << " ";
    cout << endl;
    
    return 0;
}
1 5 3 4 

6. 删除元素

在单向链表中,我们可以使用 erase_after() 方法删除给定位置的元素。该方法需要传入目标元素前一个位置的迭代器。使用 pop_front() 方法可以快速删除前端元素。

#include <bits/stdc++.h>
using namespace std;

int main() {
    forward_list<int> fl = {1, 5, 3, 4};  // 初始: [1, 5, 3, 4]

    // 删除第一个元素
    fl.pop_front();                      // [5, 3, 4]
    
    // 删除第三个元素(现在是 4)
    auto it = fl.begin();                // 指向 5
    advance(it, 1);                      // 前进 1 步,指向 3
    fl.erase_after(it);                  // 删除 3 之后的元素 4: [5, 3]
    
    for (auto x: fl) cout << x << " ";
    return 0;
}
5 3 

7. 获取单向链表的大小

forward_list 没有内置的 size() 函数。要获取其大小,我们需要通过循环手动遍历计数,或者使用 std::distance

#include <iostream>
#include <forward_list>
#include <iterator>
using namespace std;

int main() {

    forward_list<int> flist = {10, 20, 30, 40};  // 链表: [10, 20, 30, 40]

    int size = distance(flist.begin(), flist.end());  // 计算距离
    cout << "forward_list 的大小: " << size << endl;
    return 0;
}
forward_list 的大小: 4

8. empty()

用于检查单向链表是否为空。如果链表为空则返回 true,否则返回 false,可以快速验证容器是否没有数据。

#include <iostream>
#include <forward_list>

using namespace std;

int main() {
    forward_list<int> flist;  // 空链表

    if (flist.empty()) {
        cout << "forward_list 为空。" << endl;
    }

    flist.push_front(10);      // 添加元素

    if (!flist.empty()) {
        cout << "forward_list 不为空。" << endl;
    }

    return 0;
}
forward_list 为空。
forward_list 不为空。