C++双端队列知识点+习题

news2025/3/14 11:51:33

        在C++中,双端队列(Deque,发音为“deck”)是标准模板库(STL)中的一种容器适配器,其全称为Double-Ended Queue它结合了队列和栈的特点,允许在容器的两端(前端和后端)进行高效的插入和删除操作。std::deque是C++ STL中实现双端队列的具体类模板,它提供了丰富的成员函数和操作接口,使其在多种场景下都非常有用。

一、 C++双端队列知识点

1. std::deque的基本特性

  • 两端操作:可以在容器的前端(front)和后端(back)进行插入、删除和访问操作。

  • 随机访问:支持通过索引访问元素,类似于数组或std::vector

  • 动态扩展:与std::vector类似,std::deque可以根据需要动态扩展内存,但它的扩展机制更复杂,因为它需要同时管理两端的内存。

  • 内存管理std::deque内部通常使用多个固定大小的内存块(称为“缓冲区”)来存储元素。这种设计使得两端操作的效率很高,但随机访问的效率略低于std::vector

2. std::deque的构造和初始化

std::deque可以通过多种方式构造:

  • 默认构造:创建一个空的双端队列。

  • 指定大小和默认值:创建一个具有指定大小的双端队列,并用默认值填充。

  • 初始化列表:使用初始化列表直接构造双端队列。

  • 从另一个容器复制或移动构造。

#include <deque>
#include <iostream>

int main() {
    // 默认构造
    std::deque<int> dq1;

    // 指定大小和默认值
    std::deque<int> dq2(5, 10); // 5个元素,每个值为10

    // 初始化列表
    std::deque<int> dq3 = {1, 2, 3, 4, 5};

    // 从另一个容器复制构造
    std::deque<int> dq4(dq3);

    std::cout << "dq1 size: " << dq1.size() << std::endl;
    std::cout << "dq2: ";
    for (int x : dq2) {
        std::cout << x << " ";
    }
    std::cout << "\ndq3: ";
    for (int x : dq3) {
        std::cout << x << " ";
    }
    std::cout << "\ndq4: ";
    for (int x : dq4) {
        std::cout << x << " ";
    }
    std::cout << std::endl;

    return 0;
}

3. 常用操作

3.1 插入操作
  • 前端插入push_front(),在容器前端插入一个元素。

  • 后端插入push_back(),在容器后端插入一个元素。

  • 指定位置插入insert(),可以在任意位置插入一个或多个元素。

#include <deque>
#include <iostream>

int main() {
    std::deque<int> dq = {1, 2, 3};

    // 前端插入
    dq.push_front(0);
    // 后端插入
    dq.push_back(4);

    std::cout << "Deque after insertions: ";
    for (int x : dq) {
        std::cout << x << " ";
    }
    std::cout << std::endl;

    // 指定位置插入
    auto it = dq.begin() + 2; // 指向索引2的位置
    dq.insert(it, 10);        // 在索引2的位置插入10

    std::cout << "Deque after insertion at position: ";
    for (int x : dq) {
        std::cout << x << " ";
    }
    std::cout << std::endl;

    return 0;
}
3.2 删除操作
  • 前端删除pop_front(),删除容器前端的元素。

  • 后端删除pop_back(),删除容器后端的元素。

  • 指定位置删除erase(),删除任意位置的元素。

#include <deque>
#include <iostream>

int main() {
    std::deque<int> dq = {0, 1, 10, 2, 3, 4};

    // 前端删除
    dq.pop_front();
    // 后端删除
    dq.pop_back();

    std::cout << "Deque after deletions: ";
    for (int x : dq) {
        std::cout << x << " ";
    }
    std::cout << std::endl;

    // 指定位置删除
    auto it = dq.begin() + 2; // 指向索引2的位置
    dq.erase(it);             // 删除索引2的元素

    std::cout << "Deque after erasing at position: ";
    for (int x : dq) {
        std::cout << x << " ";
    }
    std::cout << std::endl;

    return 0;
}
3.3 访问操作
  • 访问前端元素front(),返回前端元素的引用。

  • 访问后端元素back(),返回后端元素的引用。

  • 通过索引访问operator[]at(),支持随机访问。

#include <deque>
#include <iostream>

int main() {
    std::deque<int> dq = {0, 1, 2, 3, 4};

    std::cout << "Front element: " << dq.front() << std::endl;
    std::cout << "Back element: " << dq.back() << std::endl;

    // 通过索引访问
    std::cout << "Element at index 2: " << dq[2] << std::endl;
    std::cout << "Element at index 3 (using at()): " << dq.at(3) << std::endl;

    return 0;
}
3.4 其他操作
  • 清空容器clear(),删除所有元素。

  • 检查是否为空empty(),判断容器是否为空。

  • 获取大小size(),返回容器中元素的数量。

  • 交换内容swap(),与另一个std::deque交换内容。

#include <deque>
#include <iostream>

int main() {
    std::deque<int> dq = {0, 1, 2, 3, 4};

    std::cout << "Is deque empty? " << (dq.empty() ? "Yes" : "No") << std::endl;
    std::cout << "Size of deque: " << dq.size() << std::endl;

    // 清空容器
    dq.clear();
    std::cout << "Deque size after clear: " << dq.size() << std::endl;

    // 交换内容
    std::deque<int> dq2 = {10, 20, 30};
    dq.swap(dq2);

    std::cout << "Deque after swap: ";
    for (int x : dq) {
        std::cout << x << " ";
    }
    std::cout << std::endl;

    return 0;
}

4. 迭代器支持

std::deque支持双向迭代器,可以使用begin()end()rbegin()rend()等方法获取迭代器,并通过迭代器进行遍历、插入和删除操作。

#include <deque>
#include <iostream>

int main() {
    std::deque<int> dq = {0, 1, 2, 3, 4};

    // 使用迭代器遍历
    for (auto it = dq.begin(); it != dq.end(); ++it) {
        std::cout << *it << " ";
    }
    std::cout << std::endl;

    // 使用范围for循环
    for (int x : dq) {
        std::cout << x << " ";
    }
    std::cout << std::endl;

    // 使用反向迭代器
    for (auto it = dq.rbegin(); it != dq.rend(); ++it) {
        std::cout << *it << " ";
    }
    std::cout << std::endl;

    return 0;
}

5. 内存管理

std::deque的内存管理机制使其在两端操作时非常高效:

  • 内部使用多个固定大小的内存块(缓冲区)来存储元素

  • 每个缓冲区的大小通常与机器的页面大小(如4KB)对齐,以减少内存碎片。

  • 插入和删除操作通常只需要在缓冲区的头部或尾部进行,而不需要像std::vector那样频繁地重新分配整个内存。

这种设计使得std::deque在以下场景中非常有用:

  1. 需要频繁在两端插入和删除元素如滑动窗口问题、任务调度等。

  2. 需要随机访问但又不希望像std::vector那样频繁重新分配内存

6. 使用场景

6.1 滑动窗口问题

std::deque常用于解决滑动窗口的最大值或最小值问题。通过维护一个单调队列,可以在O(1)时间内获取窗口内的最大值或最小值。

#include <deque>
#include <iostream>
#include <vector>

std::vector<int> maxSlidingWindow(const std::vector<int>& nums, int k) {
    std::deque<int> dq; // 存储索引
    std::vector<int> result;

    for (int i = 0; i < nums.size(); ++i) {
        // 移除超出窗口范围的元素
        while (!dq.empty() && dq.front() <= i - k) {
            dq.pop_front();
        }

        // 移除所有小于当前元素的索引
        while (!dq.empty() && nums[dq.back()] < nums[i]) {
            dq.pop_back();
        }

        dq.push_back(i);

        // 当窗口形成后,记录最大值
        if (i >= k - 1) {
            result.push_back(nums[dq.front()]);
        }
    }

    return result;
}

int main() {
    std::vector<int> nums = {1, 3, -1, -3, 5, 3, 6, 7};
    int k = 3;
    std::vector<int> result = maxSlidingWindow(nums, k);

    std::cout << "Max sliding window: ";
    for (int x : result) {
        std::cout << x << " ";
    }
    std::cout << std::endl;

    return 0;
}
6.2 回文检查

std::deque可以用于检查字符串或序列是否为回文。通过从两端同时读取元素,可以快速判断是否对称。

#include <deque>
#include <iostream>

bool isPalindrome(const std::string& str) {
    std::deque<char> dq(str.begin(), str.end());

    while (dq.size() > 1) {
        if (dq.front() != dq.back()) {
            return false;
        }
        dq.pop_front();
        dq.pop_back();
    }
    return true;
}

int main() {
    std::string str = "racecar";
    std::cout << str << " is palindrome? " << (isPalindrome(str) ? "Yes" : "No") << std::endl;

    return 0;
}

7. 性能对比

  • std::vector对比

    • std::vector更适合随机访问,但在两端插入和删除时效率较低(尤其是前端操作)。

    • std::deque在两端操作时效率更高,但随机访问的效率略低于std::vector

  • std::list对比

    • std::list支持在任意位置高效插入和删除,但不支持随机访问。

    • std::deque支持随机访问,且在两端操作时效率更高。

8. 总结

std::deque是C++ STL中一个非常强大且灵活的容器,适用于需要在两端频繁操作的场景。它结合了队列和栈的特点,同时支持随机访问,具有较高的通用性和效率。通过合理使用std::deque,可以简化代码逻辑并提高程序性能。

二、练习 

例一、Problem - 6375

#include <deque>
#include <cstdio>
#include <iostream>
#include <algorithm>

using namespace std;

int main()
{
    int n, q; // n表示队列的数量,q表示操作的数量
    
    while (scanf("%d%d", &n, &q) != EOF) // 读取n和q,直到文件结束
    {
        deque<int> deq[n + 1]; // 创建n+1个双端队列(索引从0到n)
        
        for (int i = 0; i < q; i++) // 对每个操作进行循环
        {
            int a, u, w;
            scanf("%d%d%d", &a, &u, &w); // 读取操作类型a,以及参数u和w
            
            if (a == 1) // 如果操作类型是1,则执行插入操作
            {
                int value;
                scanf("%d", &value); // 读取要插入的值
                
                if (w == 0) // 根据w的值决定在队首还是队尾插入
                {
                    deq[u].push_front(value); // 在队首插入
                }
                else 
                {
                    deq[u].push_back(value); // 在队尾插入
                }
            }
            else if (a == 2) // 如果操作类型是2,则执行查询并删除操作
            {
                if (!deq[u].empty()) // 确保队列非空
                {
                    if (w == 0) // 根据w的值决定查询并删除队首还是队尾元素
                    {
                        printf("%d\n", deq[u][0]); // 打印队首元素
                        deq[u].pop_front(); // 删除队首元素
                    }
                    else
                    {
                        printf("%d\n", deq[u].back()); // 打印队尾元素
                        deq[u].pop_back(); // 删除队尾元素
                    }
                }
                else 
                {
                    printf("-1\n"); // 如果队列为空,打印-1
                }
            }
            else // 操作类型为3时,先交换w和v的含义,然后根据w决定是否翻转队列v,并将v的所有元素移动到队列u中
            {
                int v = w; // 注意这里先读入的是v的实际值,即原先的w
                scanf("%d", &w); // 再次读取w的真实值
                
                if (w == 1 && deq[v].size() >= 2) // 如果需要翻转且队列v至少有两个元素
                {
                    reverse(deq[v].begin(), deq[v].end()); // 翻转队列v
                }
                
                while (!deq[v].empty()) // 将队列v的所有元素移动到队列u
                {
                    deq[u].push_back(deq[v].front()); // 把队列v的队首元素添加到队列u的队尾
                    deq[v].pop_front(); // 移除队列v的队首元素
                }
            }
        }
    }
    return 0;
}

例二、 641. 设计循环双端队列 - 力扣(LeetCode)

设计实现双端队列。

实现 MyCircularDeque 类:

  • MyCircularDeque(int k) :构造函数,双端队列最大为 k 。
  • boolean insertFront():将一个元素添加到双端队列头部。 如果操作成功返回 true ,否则返回 false 。
  • boolean insertLast() :将一个元素添加到双端队列尾部。如果操作成功返回 true ,否则返回 false 。
  • boolean deleteFront() :从双端队列头部删除一个元素。 如果操作成功返回 true ,否则返回 false 。
  • boolean deleteLast() :从双端队列尾部删除一个元素。如果操作成功返回 true ,否则返回 false 。
  • int getFront() ):从双端队列头部获得一个元素。如果双端队列为空,返回 -1 。
  • int getRear() :获得双端队列的最后一个元素。 如果双端队列为空,返回 -1 。
  • boolean isEmpty() :若双端队列为空,则返回 true ,否则返回 false  。
  • boolean isFull() :若双端队列满了,则返回 true ,否则返回 false 。

示例 1:

输入
["MyCircularDeque", "insertLast", "insertLast", "insertFront", "insertFront", "getRear", "isFull", "deleteLast", "insertFront", "getFront"]
[[3], [1], [2], [3], [4], [], [], [], [4], []]
输出
[null, true, true, true, false, 2, true, true, true, 4]

解释
MyCircularDeque circularDeque = new MycircularDeque(3); // 设置容量大小为3
circularDeque.insertLast(1);			        // 返回 true
circularDeque.insertLast(2);			        // 返回 true
circularDeque.insertFront(3);			        // 返回 true
circularDeque.insertFront(4);			        // 已经满了,返回 false
circularDeque.getRear();  				// 返回 2
circularDeque.isFull();				        // 返回 true
circularDeque.deleteLast();			        // 返回 true
circularDeque.insertFront(4);			        // 返回 true
circularDeque.getFront();				// 返回 4

提示:

  • 1 <= k <= 1000
  • 0 <= value <= 1000
  • insertFrontinsertLastdeleteFrontdeleteLastgetFrontgetRearisEmptyisFull  调用次数不大于 2000 次
class MyCircularDeque {
private:
    vector<int> elements;
    int rear, front;
    int capacity;

public:
    MyCircularDeque(int k) {
        capacity = k + 1;
        rear = front = 0;
        elements = vector<int>(k + 1);
    }

    bool insertFront(int value) {
        if (isFull()) {
            return false;
        }
        front = (front - 1 + capacity) % capacity;
        elements[front] = value;
        return true;
    }

    bool insertLast(int value) {
        if (isFull()) {
            return false;
        }
        elements[rear] = value;
        rear = (rear + 1) % capacity;
        return true;
    }

    bool deleteFront() {
        if (isEmpty()) {
            return false;
        }
        front = (front + 1) % capacity;
        return true;
    }

    bool deleteLast() {
        if (isEmpty()) {
            return false;
        }
        rear = (rear - 1 + capacity) % capacity;
        return true;
    }

    int getFront() {
        if (isEmpty()) {
            return -1;
        }
        return elements[front];
    }

    int getRear() {
        if (isEmpty()) {
            return -1;
        }
        return elements[(rear - 1 + capacity) % capacity];
    }   

    bool isEmpty() {
        return rear == front;
    }

    bool isFull() {
        return (rear + 1) % capacity == front;
    }
};

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2314848.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【递归、搜索和回溯算法】专题二 :二叉树中的深搜

二叉树中的深搜 深度优先遍历&#xff08;DFS&#xff09;&#xff1a;一种沿着树或图的深度遍历节点的算法&#xff0c;尽可能深地搜索树或图的分支&#xff0c;如果一条路径上的所有结点都被遍历完毕&#xff0c;就会回溯到上一层&#xff0c;继续找一条路遍历。 在二叉树中…

【vue3学习笔记】(第150-151节)computed计算属性;watch监视ref定义的数据

尚硅谷Vue2.0Vue3.0全套教程丨vuejs从入门到精通 本篇内容对应课程第150-151节 课程 P150节 《computed计算属性》笔记 写一个简单的 姓、名输入框效果&#xff1a; 用vue2的形式定义一个计算属性 fullName&#xff1a; 测试页面展示无问题&#xff1a; 但是&#xff0c;在vue…

MySQL 8 设置允许远程连接(Windows环境)

&#x1f31f; MySQL 8 设置允许远程连接&#xff08;Windows环境&#xff09; 在开发和部署应用时&#xff0c;经常需要从远程主机连接到MySQL数据库。默认情况下&#xff0c;MySQL仅允许本地连接&#xff0c;因此需要进行一些配置才能允许远程访问。今天&#xff0c;我将详细…

我又又又又又又更新了~~纯手工编写C++画图,有注释~~~

再再再次感谢Ttcofee提的问题 本次更新内容&#xff1a; 鼠标图案&#xff08;切换&#xff09;&#xff0c;版本号获取&#xff0c;输入框复制剪切板 提前申明&#xff1a;如果运行不了&#xff0c;请到主页查看RedpandaDevc下载&#xff0c;若还是不行就卸了重装。 版本号&…

全面解析:将采购入库单数据集成到MySQL的技术实施

旺店通旗舰版-采购入库单集成到MySQL的技术案例分享 在数据驱动的业务环境中&#xff0c;如何高效、准确地实现系统间的数据对接是企业面临的重要挑战。本文将聚焦于一个具体的系统对接集成案例&#xff1a;将旺店通旗舰奇门平台上的采购入库单数据集成到MySQL数据库中&#x…

12. Pandas :使用pandas读Excel文件的常用方法

一 read_excel 函数 其他参数根据实际需要进行查找。 1.接受一个工作表 在 11 案例用到的 Excel 工作簿中&#xff0c;数据是从第一张工作表的 A1 单元格开始的。但在实际场景中&#xff0c; Excel 文件可能并没有这么规整。所以 panda 提供了一些参数来优化读取过程。 比如 s…

记录致远OA服务器硬盘升级过程

前言 日常使用中OA系统突然卡死&#xff0c;刷新访问进不去系统&#xff0c;ping服务器地址正常&#xff0c;立马登录服务器检查&#xff0c;一看磁盘爆了。 我大脑直接萎缩了&#xff0c;谁家OA系统配400G的空间啊&#xff0c;过我手的服务器没有50也是30台&#xff0c;还是…

Java网络多线程

网络相关概念: 关于访问: IP端口 因为一个主机上可能有多个服务, 一个服务监听一个端口,当你访问的时候主机通过端口号就能知道要和哪个端口发生通讯.因此一个主机上不能有两个及以上的服务监听同一个端口. 协议简单来说就是数据的组织形式 好像是两个人交流一样,要保证自己说…

VScode 运行LVGL

下载vscode解压 环境安装 安装mingw64&#xff0c;gcc 版本必须8.3以上 安装cmak 系统环境变量Path中添加&#xff08;以实际安装目录为准&#xff09; C:\Program Files\mingw64\bin C:\Program Files\CMake\bin 将GUI-Guider生成的代码目录拷贝一份放到vscode项目目录…

React Next项目中导入Echart世界航线图 并配置中文

公司业务要求做世界航线图&#xff0c;跑了三个ai未果&#xff0c;主要是引入world.json失败&#xff0c;echart包中并不携带该文件&#xff0c;源码的world.json文件页面404找不到。需要自己寻找。这是整个问题卡壳的关键点&#xff0c;特此贴出资源网址。 目录 一、安装 二…

QT与网页显示数据公式的方法

一.网页中显示数学公式通常有三种主要方法 1.图片方式 原理&#xff1a;将公式转换为图片&#xff08;如 PNG、SVG&#xff09;&#xff0c;通过 <img> 标签嵌入网页。 实现步骤&#xff1a; 使用工具&#xff08;如 LaTeX dvipng、在线生成工具&#xff09;将公式渲…

深入解析APP订阅页的运作机制(订阅页如何运作)

在当今数字经济的背景下&#xff0c;订阅模式已成为许多企业获取稳定收入的重要方式。无论是软件、视频流媒体还是电子商务&#xff0c;订阅服务都能为用户提供持续的价值体验。然而&#xff0c;如何有效地设计和运作一个订阅页&#xff0c;是决定用户是否愿意订阅的关键因素。…

Golang倒腾一款简配的具有请求排队功能的并发受限服务器

golang官方指南[1]给了一些代码片段&#xff0c;层层递进演示了信道的能力: 1>. 信号量2>. 限流能力 var sem make(chan int, MaxOutstanding) func Serve(queue chan *Request) {for req : range queue {req: reqsem <- 1 go func() { // 只会开启MaxOutstandin…

【运维】服务器系统从centos7重装为ubuntu22.04

目录 一、硬盘准备二、系统安装三、安装基本系统组件四、挂载机械硬盘五、问题解决 一、硬盘准备 【注意&#xff1a;这一步会把硬盘的数据清空&#xff0c;所以需要找一个空的U盘或者把U盘数据备份】 ubuntu22.04下载 需要先安装 bittorrent 下载完之后会打开一个网页 然后…

创新技术引领软件供应链安全,助力数字中国建设

编者按 随着数字化转型的加速&#xff0c;针对软件供应链的攻击事件呈快速增长态势&#xff0c;目前已成为网络空间安全的焦点。如何将安全嵌入到软件开发到运营的全流程&#xff0c;实现防护技术的自动化、一体化、智能化&#xff0c;成为技术领域追逐的热点。 悬镜安全作为…

【设计模式】建造者模式——工厂模式

三、建造者模式——工厂模式 3.1 工厂模式 创建一个类对象的传统方式是使用关键字new, 因为用new 创建的类对象是一个堆对象&#xff0c;可以实现多态。工厂模式通过把创建对象的代码包装起来&#xff0c;实现创建对象的代码与具体 的业务逻辑代码相隔离的目的(将对象的创建和…

蓝桥备赛(18)- 红黑树和 set 与 map(上)

对于二叉搜索树 &#xff0c; 平衡二叉树 &#xff0c; 以及红黑树 &#xff0c; 目前只需要了解背后的原理 &#xff0c; 不做代码实现的要求 &#xff0c; 重要的就是了解各种操作的时间复杂度即可 &#xff0c; 为set 与 map 做铺垫 一、二叉搜索树 1.1 基本概念 相较与于堆…

obeaver 连接oracle 库 模式乱码

下载orai18n-12.1.0.2.0.jar 库--添加文件--把提前下载好的jar 随便放在一个文件夹下--添加文件选中&#xff0c;然后点击找到类&#xff0c; 选择类&#xff0c;确定即可正常 下载地址&#xff1a;https://download.csdn.net/download/weixin_42845364/88368302

ChatGPT 使用教程:深度探索AI常用功能技巧

文章目录 前言一、ChatGPT介绍1.1 人工智能与自然语言处理的发展1.2 ChatGPT 的诞生与意义 二、ChatGPT 基础入门2.1 注册与登录2.2 对话界面介绍2.3 基本提问方式 三、常用功能详解3.1 文本生成3.2 问题回答3.3 语言翻译3.4 代码生成与调试 四、高级使用技巧4.1 指令优化4.2 多…

[HUBUCTF 2022 新生赛]messy_traffic

下载附件 看到文件类型直接用wireshark打开&#xff0c;对MySQL协议进行追踪流&#xff0c;并没有什么发现&#xff0c;后面对NO.437发现有用信息&#xff0c;http追踪流 发现**system(‘cat passwd.txt’);**这里是在打开查看passwd.txt&#xff0c;密码是"SignUpForHUBU…