惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

TaoSecurity Blog
TaoSecurity Blog
博客园 - 司徒正美
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
博客园 - 【当耐特】
M
MIT News - Artificial intelligence
罗磊的独立博客
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Stack Overflow Blog
Stack Overflow Blog
The GitHub Blog
The GitHub Blog
Google DeepMind News
Google DeepMind News
Security Archives - TechRepublic
Security Archives - TechRepublic
宝玉的分享
宝玉的分享
N
News and Events Feed by Topic
The Hacker News
The Hacker News
Google DeepMind News
Google DeepMind News
C
CERT Recently Published Vulnerability Notes
F
Full Disclosure
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
S
Security @ Cisco Blogs
H
Hacker News: Front Page
L
LangChain Blog
Microsoft Security Blog
Microsoft Security Blog
Y
Y Combinator Blog
B
Blog RSS Feed
H
Heimdal Security Blog
Google Online Security Blog
Google Online Security Blog
Apple Machine Learning Research
Apple Machine Learning Research
博客园 - 三生石上(FineUI控件)
V2EX - 技术
V2EX - 技术
V
Vulnerabilities – Threatpost
Help Net Security
Help Net Security
Hacker News - Newest:
Hacker News - Newest: "LLM"
T
Tailwind CSS Blog
W
WeLiveSecurity
T
Tenable Blog
D
DataBreaches.Net
Martin Fowler
Martin Fowler
Cyberwarzone
Cyberwarzone
Cisco Talos Blog
Cisco Talos Blog
S
Secure Thoughts
O
OpenAI News
L
LINUX DO - 热门话题
Vercel News
Vercel News
阮一峰的网络日志
阮一峰的网络日志
Jina AI
Jina AI
J
Java Code Geeks
Know Your Adversary
Know Your Adversary
IT之家
IT之家
Latest news
Latest news
Cloudbric
Cloudbric

qlAD 的技术笔记

2026 年 6 月 7 日 -- 我有社交恐惧症,恐惧外面到处雷电风雨声(大学生活记录) 手把手带你实现哈希表(C/C++ 数据结构) 手把手带你实现图结构(C/C++ 数据结构) 手把手带你实现二叉树(C/C++ 数据结构) 手把手带你实现单/双链表(C/C++ 数据结构) 手把手带你实现动态数组(C/C++ 数据结构) 2026 年 1 月 2 日 -- 我不怕冷,因为我内心炽热 【2025 年度总结】(大学生活记录) 2025 年 12 月 2 日 -- 省电模式:大一 100 天耗电日志(大学生活记录) 蓝桥杯备赛第一期 —— 枚举与模拟 2025 年 11 月 3 日 -- 用爱发电、大爱无私(大学生活记录) 使用循环嵌套输出规则图形找规律讲解(嵌套循环-图形输出) 2025 年 10 月 1 日 -- 二字开头第一年的生日记录及感悟随笔(大学生活记录) Python 初级 04 -- 目前为止 Python 中你们做题可能会遇到的问题以及七七八八 2025 年 9 月 24 日 -- 开学后的第一课及军训汇演(大学生活记录) Python 初级 03 -- 程序设计的三种结构之选择结构和循环结构 Python 初级 02 -- 基础的数据类型和输入输出 2025 年 9 月 17 日 -- 大连 2025 国际大体联足球世界杯(大学生活记录) Python 初级 01 -- 初识编程、理解计算机语言及程序运行方式 适合大一新生学习编程的前一课 -- 编程先导(编程的本质) 2025 年 9 月 12 日 -- 三天军训感受以及一些七七八八(大学生活记录) 2025 年 9 月 9 日 -- 体检、军训前的准备(大学生活记录) 适合大一新生的计算机基础知识 -- 个人认为足够版 2025 年 9 月 7 日 -- 既来之、则安之(大学生活记录) C++ 程序的内存布局 —— 代码区、全局/静态区、栈区和堆区 联想小新 pro 16 2025 开箱记录:跳过联网、office 激活、更换 win 11 专业版 R 语言中的数学函数 计算机软件著作权申请过程记录 从聚类到回归:用 Python 解析鸢尾花数据集的完整数据科学流程 条件概率、全概率与贝叶斯公式 —— 一篇文章带你死磕概率公式 高中化学物质反应规则表 —— 方程式之间也有规律可循 集合元素性质、关系、子集公式、基本运算 —— 一篇文章带你全方面死磕集合 如何为你的项目选择合适的许可证 -- 全方位指南 解释 3:1 和 9:3:3:1 的详细来源 —— 建立减数分裂与孟德尔遗传学定律之间的联系 重新设置磁盘分区以及 Btrfs 子卷(subvolume)布局的无损方案 零基础实战:用 Docker 启动 dockermailserver 绕过 25 端口封锁搭建个人邮箱服务器 基于 GitHub Actions + OSS 的自托管 APT 仓库全面指南 如何在安装 Debian 时配置 Btrfs 子卷 | 详细分步指南 一个合格的个人网站站长应该知道哪些事? CSS 基础入门教程 —— 选择器、样式声明 HTML 基础入门 —— 基本结构和语法 给博客添加一个文章数据统计的页面 Git 学习笔记(命令备忘表) Anki 牌组选项详细解释与设置 手动编译带有 KernelSU 的 OnePlus 6 内核 安卓类原生系统补全计划 —— PixelExperience(一加 OnePlus) 如何使用 use-sound 为 React 应用添加声音效果 标准化项目的 GitHub Flow 工作流 如何搭建设计自己的博客网站? 使用安知鱼主题同款的 Twikoo 评论组件 配置 GitHub Actions 实现自动化提交百度收录 第00期 | 环境搭建 & 递归 (一) | 基本数列递归 逐字符讲解 C 语言 HelloWorld 程序 2023 年回顾 🤟 Anki:记忆神器,助你高效学习 Gentoo 学习笔记 美化你的 PixelExperience OS Markdown 语法指南 搭建 OneDrive 目录索引 为什么用 65 表示大写字母 A
手把手带你实现栈和队列(C/C++ 数据结构)
qlAD · 2026-02-09 · via qlAD 的技术笔记

cover

一、回顾数组和链表

数组和链表都可以在任意位置进行增加和删除操作,拿 vectorlist 来说:

#include <iostream>
#include <vector>
#include <list>

int main() {
    std::vector<int> vec = {1, 2, 3};
    vec.insert(vec.begin() + 1, 4); // 在索引1位置插入4
    vec.erase(vec.begin() + 2); // 删除索引2位置的元素

    std::list<int> lst = {1, 2, 3};
    lst.push_back(4); // 在末尾添加4
    lst.pop_front(); // 删除头部元素

    return 0;
}

数组(如 vector)支持随机访问(O(1)),尾部增删效率高(均摊 O(1)),但中间位置增删需要移动元素(O(n));

链表(如 list)不支持随机访问(O(n)),但在已知节点指针的前提下,任意位置增删效率为 O(1)。

数组与链表

栈和队列是两种线性逻辑结构:栈遵循 “后进先出(LIFO)”,队列遵循 “先进先出(FIFO)”。

它们作为逻辑结构,既可以基于链表实现(链式栈 / 链式队列),也可以基于数组实现(顺序栈 / 循环队列)。

栈(Stack)是遵循后进先出(LIFO)的线性逻辑结构,当我们用链表作为底层存储实现栈时,相当于对链表的操作做限制——只保留在链表一端的插入/删除功能;

队列(Queue)是遵循先进先出(FIFO)的线性逻辑结构,当我们用链表作为底层存储实现队列时,相当于对链表的操作做限制——只保留在链表两端的插入/删除功能。

二、栈的实现

栈(Stack)的实现可以看作一种有限制的链表,链表可以在任意位置进行插入和删除,而栈只能在一端进行插入和删除操作,这一端被称为栈顶(Top)。

经常用来形容栈的结构是“盘子”,我们只能在盘子的一端进行操作,最后放上去的盘子最先被拿走。

栈的示意图

栈遵循后进先出(LIFO)的原则。

也就是说我们可以屏蔽掉链表的其他功能,只保留在链表一端进行插入和删除的功能,这样就实现了栈。

1、基于自定义类实现栈

关键点:

  • 入栈:在链表头部(栈顶)添加元素,时间复杂度 O(1)
  • 出栈:在链表头部(栈顶)删除元素,时间复杂度 O(1)
#include <iostream>
#include <stdexcept>
using namespace std;

// 1. 定义链表节点结构体
template<typename T>
struct ListNode {
    T data_; // 存储数据
    ListNode *next_; // 指向下一个节点的指针

    ListNode(T x) : data_(x), next_(nullptr) {
    }
};

// 2. 基于自定义链表实现栈
template<typename T>
class MyCustomLinkedListStack {
private:
    ListNode<T> *topNode_; // 指向栈顶节点的指针
    int stackSize_; // 记录栈的大小

public:
    MyCustomLinkedListStack() : topNode_(nullptr), stackSize_(0) {
    }

    ~MyCustomLinkedListStack() {
        while (topNode_ != nullptr) {
            ListNode<T> *temp = topNode_;
            topNode_ = topNode_->next_;
            delete temp;
        }
    }

    // 入栈:在链表头部(栈顶)添加元素,时间复杂度 O(1)

    /*
    1、假设当前栈从栈顶到栈底的元素依次为 3 -> 2 -> 1,topNode_ 指向 3。
    2、当我们执行 push(4) 操作时,首先创建一个新节点 newNode,数据为 4。
    3、newNode->next_ = topNode_; 此时链表结构变为 4 -> 3 -> 2 -> 1,newNode 的 next 指向原来的栈顶节点 3。
    4、更新 topNode_ 指针指向 newNode(即 topNode_ = newNode;),此时栈顶元素变为 4。
    5、栈的大小 stackSize_ 增加 1。
    6、最终结果是栈顶元素为 4,栈的结构为 4 -> 3 -> 2 -> 1,栈的大小为原来加 1。
    */

    void push(T x) {
        ListNode<T> *newNode = new ListNode<T>(x);
        newNode->next_ = topNode_; // 新节点的 next 指向当前的栈顶节点
        topNode_ = newNode; // 更新栈顶指针指向新节点
        stackSize_++;
    }

    // 出栈:在链表头部(栈顶)删除元素,时间复杂度 O(1)

    /*
    1、假设当前栈从栈顶到栈底的元素依次为 4 -> 3 -> 2 -> 1,topNode_ 指向 4。
    2、当我们执行 pop() 操作时,首先检查栈是否为空,如果为空则抛出异常。
    3、保存当前栈顶节点的指针 temp = topNode_,以及栈顶元素的值 topVal = temp->data_ 也就是 4。
    4、更新 topNode_ 指针指向下一个节点(即 topNode_ = topNode_->next_;),此时栈顶元素变为 3。
    5、删除原来的栈顶节点 temp,释放内存。
    6、栈的大小 stackSize_ 减少 1。
    7、最终结果是返回被弹出的元素值 4,栈顶元素变为 3,栈的结构为 3 -> 2 -> 1,栈的大小为原来减 1。
    */

    T pop() {
        if (empty()) {
            throw runtime_error("Error: 栈为空,无法执行 pop 操作!");
        }
        ListNode<T> *temp = topNode_;
        T topVal = temp->data_;
        topNode_ = topNode_->next_;
        delete temp;
        stackSize_--;
        return topVal;
    }

    // 查看栈顶元素
    T peek() const {
        if (empty()) {
            throw runtime_error("Error: 栈为空,无法执行 peek 操作!");
        }
        return topNode_->data_;
    }

    // 判断栈是否为空
    bool empty() const {
        return topNode_ == nullptr;
    }

    // 获取栈的大小
    int size() const {
        return stackSize_;
    }
};

// 测试代码
int main() {
    MyCustomLinkedListStack<int> stack;

    // 入栈操作
    stack.push(1);
    stack.push(2);
    stack.push(3);

    // 查看栈顶和大小
    cout << "栈顶元素: " << stack.peek() << endl; // 输出 3
    cout << "栈大小: " << stack.size() << endl; // 输出 3

    // 出栈操作
    cout << "弹出元素: " << stack.pop() << endl; // 输出 3
    cout << "栈顶元素: " << stack.peek() << endl; // 输出 2
    cout << "栈大小: " << stack.size() << endl; // 输出 2

    // 继续出栈
    stack.pop(); // 弹出 2
    stack.pop(); // 弹出 1

    cout << "栈现在" << (stack.empty() ? "为" : "不为") << "空" << endl;

    return 0;
}

栈的入栈相当于在链表头部添加元素,出栈相当于在链表头部删除元素。所以时间复杂度都是 O(1)。

2、基于 list 实现栈

我们可以直接使用 std::list 来实现栈:

  • 方案1:用 push_back/pop_back 实现,此时 list 的尾部对应栈的栈顶
  • 方案2:用 push_front/pop_front 实现,此时 list 的头部对应栈的栈顶

两种方案效果一致(都满足栈的LIFO特性),因为栈只要求在“一端”操作,具体对应list的头部/尾部仅为物理实现差异。

#include <iostream>
#include <list>
#include <stdexcept>

template<typename T>
class MyLinkedListStack {
private:
    std::list<T> stack_;
public:
    void push(T x) {
        stack_.push_back(x);
    }

    T pop() {
        if (empty()) {
            throw std::runtime_error("Error: 栈为空,无法执行 pop 操作!");
        }
        T topElement = stack_.back();
        stack_.pop_back();
        return topElement;
    }

    T peek() const {
        if (empty()) {
            throw std::runtime_error("Error: 栈为空,无法执行 peek 操作!");
        }
        return stack_.back();
    }

    bool empty() const {
        return stack_.empty();
    }

    int size() const {
        return stack_.size();
    }
};

int main() {
    MyLinkedListStack<int> stack;
    stack.push(1);
    stack.push(2);
    stack.push(3);
    std::cout << "栈顶元素: " << stack.peek() << std::endl; // 输出 3
    std::cout << "弹出元素: " << stack.pop() << std::endl; // 输出 3
    std::cout << "栈顶元素: " << stack.peek() << std::endl; // 输出 2
    std::cout << "栈大小: " << stack.size() << std::endl; // 输出 2

    // 测试空栈操作
    stack.pop(); // 弹出 2
    stack.pop(); // 弹出 1
    std::cout << "栈是否为空: " << (stack.empty() ? "是" : "否") << std::endl; // 输出 是

    return 0;
}

3、时间复杂度分析

操作描述时间复杂度
push()元素入栈(添加至栈顶)O(1)
pop()元素出栈(删除栈顶元素)O(1)
peek()查看栈顶元素O(1)

三、队列的实现

队列(Queue)的实现也可以看作一种有限制的链表,链表可以在任意位置进行插入和删除,而队列只能在两端进行操作:一端进行插入操作,称为队尾(Rear);另一端进行删除操作,称为队头(Front)。

经常用来形容队列的结构是“排队”,我们只能在队尾进行入队操作,在队头进行出队操作,最先进入队列的元素最先被处理。

队列的示意图

队列遵循先进先出(FIFO)的原则。

也就是说我们可以屏蔽掉链表的其他功能,只保留在链表两端进行插入和删除的功能,这样就实现了队列。

1、基于自定义类实现队列

关键点:

  • 入队:在链表尾部(队尾)添加元素,时间复杂度 O(1)
  • 出队:在链表头部(队头)删除元素,时间复杂度 O(1)
#include <iostream>
#include <stdexcept>
using namespace std;

// 1. 定义链表节点结构体
template<typename T>
struct ListNode {
    T data_;
    ListNode *next_;

    ListNode(T x) : data_(x), next_(nullptr) {
    }
};

// 2. 基于自定义链表实现队列
template<typename T>
class MyCustomLinkedListQueue {
private:
    ListNode<T> *frontNode_;
    ListNode<T> *rearNode_;
    int queueSize_;

public:
    MyCustomLinkedListQueue() : frontNode_(nullptr), rearNode_(nullptr), queueSize_(0) {
    }

    ~MyCustomLinkedListQueue() {
        while (frontNode_ != nullptr) {
            ListNode<T> *temp = frontNode_;
            frontNode_ = frontNode_->next_;
            delete temp;
        }
    }

    // 入队:在链表尾部(队尾)添加元素

    /*
    1、假设当前队列从队头到队尾的元素依次为 1 -> 2 -> 3,frontNode_ 指向 1,rearNode_ 指向 3。
    2、当我们执行 enqueue(4) 操作时,首先创建一个新节点 newNode,数据为 4。
    3、如果 rearNode_ 为 nullptr,说明队列为空,此时 frontNode_ 和 rearNode_ 都指向 newNode,队列结构变为 4。
    4、如果 rearNode_ 不为 nullptr,说明队列不为空,此时将 rearNode_->next_ 指向 newNode。
    5、更新 rearNode_ 指针指向 newNode,此时队尾元素变为 4。
    6、队列的大小 queueSize_ 增加 1。
    7、最终结果是队尾元素为 4,队列的结构为 1 -> 2 -> 3 -> 4,队列的大小为原来加 1。
    */

    void enqueue(T x) {
        ListNode<T> *newNode = new ListNode<T>(x);
        if (rearNode_ == nullptr) {
            frontNode_ = rearNode_ = newNode;
        } else {
            rearNode_->next_ = newNode;
            rearNode_ = newNode;
        }
        queueSize_++;
    }

    // 出队:在链表头部(队头)删除元素

    /*
    1、假设当前队列从队头到队尾的元素依次为 1 -> 2 -> 3,frontNode_ 指向 1,rearNode_ 指向 3。
    2、当我们执行 dequeue() 操作时,首先检查队列是否为空,如果为空则抛出异常。
    3、保存当前队头节点的指针 temp = frontNode_,以及队头元素的值 frontVal = temp->data_ 也就是 1。
    4、更新 frontNode_ 指针指向下一个节点(即 frontNode_ = frontNode_->next_;),此时队头元素变为 2。
    5、如果更新后的 frontNode_ 为 nullptr,说明队列已经变空,此时将 rearNode_ 也设置为 nullptr。
    6、删除原来的队头节点 temp,释放内存。
    7、队列的大小 queueSize_ 减少 1。
    8、最终结果是返回被出队的元素值 1,队头元素变为 2,队列的结构为 2 -> 3,队列的大小为原来减 1。
    */

    T dequeue() {
        if (empty()) {
            throw runtime_error("Error: 队列为空,无法执行 dequeue 操作!");
        }
        ListNode<T> *temp = frontNode_;
        T frontVal = temp->data_;
        frontNode_ = frontNode_->next_;
        if (frontNode_ == nullptr) {
            rearNode_ = nullptr;
        }
        delete temp;
        queueSize_--;
        return frontVal;
    }

    // 查看队头元素
    T peek() const {
        if (empty()) {
            throw runtime_error("Error: 队列为空,无法执行 peek 操作!");
        }
        return frontNode_->data_;
    }

    // 判断队列是否为空
    bool empty() const {
        return frontNode_ == nullptr;
    }

    // 获取队列的大小
    int size() const {
        return queueSize_;
    }
};

// 测试代码
int main() {
    MyCustomLinkedListQueue<int> queue;
    // 入队操作
    queue.enqueue(1);
    queue.enqueue(2);
    queue.enqueue(3);
    // 查看队头和大小
    cout << "队头元素: " << queue.peek() << endl; // 输出 1
    cout << "队列大小: " << queue.size() << endl; // 输出 3
    // 出队操作
    cout << "出队元素: " << queue.dequeue() << endl; // 输出 1
    cout << "队头元素: " << queue.peek() << endl; // 输出 2
    cout << "队列大小: " << queue.size() << endl; // 输出 2
    // 继续出队
    queue.dequeue(); // 出队 2
    queue.dequeue(); // 出队 3
    cout << "队列现在" << (queue.empty() ? "为空" : "不为空") << endl;
    return 0;
}

队列的入队相当于在链表尾部添加元素,出队相当于在链表头部删除元素。所以时间复杂度都是 O(1)。

2、基于 list 实现队列

#include <iostream>
#include <list>
#include <stdexcept>

template<typename T>
class MyLinkedListQueue {
private:
    std::list<T> queue_;
public:
    void enqueue(T x) {
        queue_.push_back(x);
    }

    T dequeue() {
        if (empty()) {
            throw std::runtime_error("Error: 队列为空,无法执行 dequeue 操作!");
        }
        T frontElement = queue_.front();
        queue_.pop_front();
        return frontElement;
    }

    T peek() const {
        if (empty()) {
            throw std::runtime_error("Error: 队列为空,无法执行 peek 操作!");
        }
        return queue_.front();
    }

    bool empty() const {
        return queue_.empty();
    }

    int size() const {
        return queue_.size();
    }
};

int main() {
    MyLinkedListQueue<int> queue;
    queue.enqueue(1);
    queue.enqueue(2);
    queue.enqueue(3);
    std::cout << "队头元素: " << queue.peek() << std::endl; // 输出 1
    std::cout << "出队元素: " << queue.dequeue() << std::endl; // 输出 1
    std::cout << "队头元素: " << queue.peek() << std::endl; // 输出 2
    std::cout << "队列大小: " << queue.size() << std::endl; // 输出 2

    // 测试空队列操作
    queue.dequeue(); // 出队 2
    queue.dequeue(); // 出队 3
    std::cout << "队列是否为空: " << (queue.empty() ? "是" : "否") << std::endl; // 输出 是

    return 0;
}

3、时间复杂度分析

操作描述时间复杂度
enqueue()元素入队(添加至队尾)O(1)
dequeue()元素出队(删除队头元素)O(1)
peek()查看队头元素O(1)

四、STL 中的 stack 和 queue

C++ STL 中提供了 stackqueue 两个容器适配器,分别用于实现栈和队列的数据结构。

STL 的 stack 和 queue 是容器适配器,默认底层容器为 deque(双端队列),也可指定为 list/vector 等其他容器。

1、STL 中的 stack

#include <iostream>
#include <stack>

int main() {
    std::stack<int> s;

    // 向栈中添加元素
    s.push(10);
    s.push(20);
    s.push(30);

    // 打印栈顶元素
    std::cout << "Top element is: " << s.top() << std::endl; // 输出: Top element is: 30

    // 移除栈顶元素
    s.pop();
    std::cout << "After popping, top element is: " << s.top() << std::endl; // 输出: After popping, top element is: 20

    // 检查栈是否为空
    if (!s.empty()) {
        std::cout << "Stack is not empty." << std::endl; // 输出: Stack is not empty.
    }

    // 打印栈的大小
    std::cout << "Size of stack: " << s.size() << std::endl; // 输出: Size of stack: 2

    // 继续移除元素
    s.pop();
    s.pop();

    // 检查栈是否为空
    if (s.empty()) {
        std::cout << "Stack is empty." << std::endl; // 输出: Stack is empty.
    }

    return 0;
}

2、STL 中的 queue

#include <iostream>
#include <queue>

int main() {
    // 创建一个整数队列
    std::queue<int> q;

    // 向队列中添加元素
    q.push(10);
    q.push(20);
    q.push(30);

    // 打印队列中的元素数量
    std::cout << "队列中的元素数量: " << q.size() << std::endl;

    // 打印队首元素
    std::cout << "队首元素: " << q.front() << std::endl;

    // 打印队尾元素
    std::cout << "队尾元素: " << q.back() << std::endl;

    // 移除队首元素
    q.pop();
    std::cout << "移除队首元素后,队首元素: " << q.front() << std::endl;

    // 再次打印队列中的元素数量
    std::cout << "队列中的元素数量: " << q.size() << std::endl;

    return 0;
}