C++容器之双端队列(std::deque)

1 概述

  deque(通常发音像“deck”)是双端队列的一个不规则缩写。双端队列是具有动态大小的序列容器,可以在两端(前端或后端)进行扩展或收缩。
  特定的库可以以不同的方式实现deques,通常作为某种形式的动态数组。但在任何情况下,它们都允许通过随机访问迭代器直接访问单个元素,并根据需要通过扩展和收缩容器来自动处理存储。
  因此,它们提供了类似于矢量的功能,但也在序列的开始处,而不仅仅是在序列的结束处,有效地插入和删除元素。但是,与向量不同的是,deque不能保证将其所有元素存储在连续的存储位置:通过将指针偏移到另一个元素来访问deque中的元素会导致未定义的行为。
  vectors和deque都提供了非常相似的接口,可以用于类似的目的,但在内部都以完全不同的方式工作:虽然vectors使用一个偶尔需要重新分配才能增长的数组,但deque的元素可以分散在不同的存储块中,容器在内部保留必要的信息,以便在恒定时间内直接访问其任何元素,并具有统一的顺序接口(通过迭代器)。因此,deques的内部比向量复杂一点,但这使它们在某些情况下能够更有效地生长,尤其是在非常长的序列中,重新定位变得更加昂贵。
  对于涉及在除开头或结尾以外的位置频繁插入或删除元素的操作,与列表和前向列表相比,deques的性能更差,迭代器和引用的一致性也更低。

其类图如下:
类图

2 使用实例

void DequeSuite::push_back()
{
    int array[] = { 1, 2, 3, 4, 5 };
    std::deque<int> a;

    for(size_t i = 0; i < ARRAY_SIZE(array); i++)
        a.push_back(array[i]);

    for(size_t i = 0; i < a.size(); i++)
        TEST_ASSERT_EQUALS(array[i],  a[i])
}

void DequeSuite::push_front()
{
    int array[] = { 1, 2, 3, 4, 5 };
    std::deque<int> a;
    std::deque<int> b;

    for(size_t i = 0; i < ARRAY_SIZE(array); i++)
        a.push_front(array[i]);

    int index = 0;
    for(auto it = a.crbegin(); it != a.crend(); ++it)
        TEST_ASSERT_EQUALS(array[index++],  *it)
}

3 接口使用

3.1 construct

std::deque<int> getDeque(int size, int value)
{
    return std::deque<int>(size, value);
}
void DequeSuite::construct()
{
    std::deque<int> a;
    std::deque<int> b(4); // 0 0 0 0
    std::deque<int> c(4, 5);// 5 5 5 5
    std::deque<int> d({1, 2, 3, 4, 5});
    std::deque<int> e(d.begin(), d.end());
    std::deque<int> f(d);
    std::deque<int> g(getDeque(6, 8));

    TEST_ASSERT_EQUALS(0, a.size())
    TEST_ASSERT_EQUALS(4, b.size())
    TEST_ASSERT_EQUALS(4, c.size())
    TEST_ASSERT_EQUALS(5, d.size())
    TEST_ASSERT_EQUALS(5, e.size())
    TEST_ASSERT_EQUALS(5, f.size())
    TEST_ASSERT_EQUALS(6, g.size())
}

3.2 assigns

void DequeSuite::assigns()
{
    std::deque<int> a;
    std::deque<int> b;
    std::deque<int> c;
    a = { 1, 2, 3, 4, 5 };
    b = a;
    c = getDeque(5, 10);

    TEST_ASSERT_EQUALS(5, a.size())
    TEST_ASSERT_EQUALS(5, b.size())
    TEST_ASSERT_EQUALS(5, c.size())
}

3.3 iterators

#define ARRAY_SIZE(array) sizeof(array) / sizeof(array[0])
void DequeSuite::iterators()
{
    int  array[] = { 1, 2, 3, 4, 5 };
    int  rarray[] = { 5, 4, 3, 2, 1 };
    std::deque<int> a(array, array + ARRAY_SIZE(array));
    int index = 0;
    for(auto it = a.begin(); it != a.end(); ++it)
    {
        TEST_ASSERT_EQUALS(array[index++], *it)
    }
    
    index = 0;
    for(auto it = a.cbegin(); it != a.cend(); ++it)
    {
        TEST_ASSERT_EQUALS(array[index++], *it)
    }

    index = 0;
    for(auto it = a.rbegin(); it != a.rend(); ++it)
    {
        TEST_ASSERT_EQUALS(rarray[index], *it)
        rarray[index] = rarray[index] * 2;
        *it = rarray[index++];
    }
    
    index = 0;
    for(auto it = a.crbegin(); it != a.crend(); ++it)
    {
        TEST_ASSERT_EQUALS(rarray[index++], *it)
    }
}

3.4 capacity

void DequeSuite::capacity()
{
    std::deque<int> a;
    std::deque<int> b({1, 2, 3, 4, 5});
    TEST_ASSERT_EQUALS(0, a.size());
    TEST_ASSERT_EQUALS(5, b.size());
    TEST_ASSERT_EQUALS(true, a.empty());
    TEST_ASSERT_EQUALS(false, b.empty());

    TEST_ASSERT_EQUALS(a.max_size(), b.max_size());
}

3.5 rezize

void DequeSuite::rezize()
{
    std::deque<int> a;
    a.resize(5, 10);  //10 10 10 10 10
    for(auto it = a.begin(); it != a.end(); ++it)
        TEST_ASSERT_EQUALS(10, *it);
    a.resize(8);      //10 10 10 10 10 0 0 0
    for(auto it = a.begin(); it != a.begin() + 5; ++it)
        TEST_ASSERT_EQUALS(10, *it);
    for(auto it = a.begin() + 5; it != a.end(); ++it)
        TEST_ASSERT_EQUALS(0, *it);
    a.resize(10, 20); //10 10 10 10 10 0 0 0 20 20
    for(auto it = a.begin() + 10; it != a.end(); ++it)
        TEST_ASSERT_EQUALS(20, *it);
}

3.6 shrink_to_fit

void DequeSuite::shrink_to_fit()
{
    std::deque<int> a (100);
    TEST_ASSERT_EQUALS(100, a.size())
    a.resize(10);
    TEST_ASSERT_EQUALS(10, a.size())
    a.shrink_to_fit();
}

说明:

  • 该函数不修改size大小

3.7 access

void DequeSuite::access()
{
    int  array[] = { 1, 2, 3, 4, 5 };
    std::deque<int> a(array, array + ARRAY_SIZE(array));
    

    TEST_ASSERT_EQUALS(1, a.front())//size of a must more than 0
    TEST_ASSERT_EQUALS(5, a.back()) //size of a must more than 0

    for(size_t i = 0; i < a.size(); i++)
    {
        TEST_ASSERT_EQUALS(array[i], a[i])
         array[i] = a[i] * 2;
        a[i] = array[i];
    }
    TEST_ASSERT_EQUALS(2, a.front())
    TEST_ASSERT_EQUALS(10, a.back())

    for(size_t i = 0; i < a.size(); i++)
    {
        TEST_ASSERT_EQUALS(array[i], a.at(i))
        array[i] = a.at(i) / 2;
        a.at(i) = array[i];
    }
    TEST_ASSERT_EQUALS(1, a.front())
    TEST_ASSERT_EQUALS(5, a.back())

    bool hasExcpetion = false;
    try {
        int v = a.at(6);
    }
    catch(...)
    {
        hasExcpetion = true;
    }
    TEST_ASSERT_EQUALS(true, hasExcpetion)
}

3.8 assign

void DequeSuite::assign()
{
    std::deque<int> a;
    std::deque<int> b;
    std::deque<int> c;
    a.assign({ 1, 2, 3, 4, 5 });
    b.assign(a.begin(), a.end());
    c.assign(4, 5);

    TEST_ASSERT_EQUALS(5, a.size())
    TEST_ASSERT_EQUALS(5, b.size())
    TEST_ASSERT_EQUALS(4, c.size())
}

3.9 push_back

void DequeSuite::push_back()
{
    int array[] = { 1, 2, 3, 4, 5 };
    std::deque<int> a;

    for(size_t i = 0; i < ARRAY_SIZE(array); i++)
        a.push_back(array[i]);

    for(size_t i = 0; i < a.size(); i++)
        TEST_ASSERT_EQUALS(array[i],  a[i])
}

3.10 push_front

void DequeSuite::push_front()
{
    int array[] = { 1, 2, 3, 4, 5 };
    std::deque<int> a;
    std::deque<int> b;

    for(size_t i = 0; i < ARRAY_SIZE(array); i++)
        a.push_front(array[i]);

    int index = 0;
    for(auto it = a.crbegin(); it != a.crend(); ++it)
        TEST_ASSERT_EQUALS(array[index++],  *it)
}

说明:

  • 相对vector,deque可以从front插入元素

3.11 pop_back

void DequeSuite::pop_back()
{
    std::deque<int> a({1, 2, 3, 4, 5});

    TEST_ASSERT_EQUALS(5, a.back())

    a.pop_back();
    TEST_ASSERT_EQUALS(4, a.back())
    
    a.pop_back();
    TEST_ASSERT_EQUALS(3, a.back())

    a.pop_back();
    TEST_ASSERT_EQUALS(2, a.back())
    
    a.pop_back();
    TEST_ASSERT_EQUALS(1, a.back())

    a.pop_back();
    TEST_ASSERT_EQUALS(true, a.empty())

    if(!a.empty())
        a.pop_back();//??
    TEST_ASSERT_EQUALS(true, a.empty())
}

3.12 pop_front

void DequeSuite::pop_front()
{
    std::deque<int> a({1, 2, 3, 4, 5});

    TEST_ASSERT_EQUALS(1, a.front())

    a.pop_front();
    TEST_ASSERT_EQUALS(2, a.front())
    
    a.pop_front();
    TEST_ASSERT_EQUALS(3, a.front())

    a.pop_front();
    TEST_ASSERT_EQUALS(4, a.front())
    
    a.pop_front();
    TEST_ASSERT_EQUALS(5, a.front())

    a.pop_front();
    TEST_ASSERT_EQUALS(true, a.empty())

    if(!a.empty())
        a.pop_front();//??
    TEST_ASSERT_EQUALS(true, a.empty())
}

说明:

  • 相对vector,deque可以从front弹出元素

3.13 insert

void DequeSuite::insert()
{
    std::deque<std::string> names;
    std::string name("James");

    auto it = names.insert(names.begin(), name);//James
    TEST_ASSERT_EQUALS(name, *it)

    it = names.insert(names.end(), 2, "Tom"); //James Tom Tom 
    TEST_ASSERT_EQUALS("Tom", *it)

    it = names.insert(names.end(), "Peter"); //James Tom Tom Peter
    TEST_ASSERT_EQUALS("Peter", *it)

    it = names.insert(names.end(), {"Jim", "Rose", }); //James Tom Tom Peter Jim Rose
    TEST_ASSERT_EQUALS("Jim", *it)
}

说明:

  • 返回iterator指向被删除元素的下一位置

3.14 erase

void DequeSuite::erase()
{
    std::deque<int> a({1, 2, 3, 4, 5});
    TEST_ASSERT_EQUALS(5, a.back())
    
    auto it = a.erase(a.begin() + 4);//1, 2, 3, 4

    //a.erase(a.begin() + 5); double free or corruption (out)

    TEST_ASSERT_EQUALS(true, it == a.end())
    TEST_ASSERT_EQUALS(4, a.back())

    it = a.erase(a.begin() + 1, a.begin() + 3);//1, 4
    TEST_ASSERT_EQUALS(4, *it)
    TEST_ASSERT_EQUALS(4, a.back())
}

说明:

  • 传入iterator必须是有效的
  • 返回iterator指向被删除元素的下一位置

3.15 swap

void DequeSuite::swap()
{
    std::deque<int> a({1, 2, 3, 4, 5});
    std::deque<int> b;

    TEST_ASSERT_EQUALS(5, a.size())
    TEST_ASSERT_EQUALS(0, b.size())

    a.swap(b);
    TEST_ASSERT_EQUALS(0, a.size())
    TEST_ASSERT_EQUALS(5, b.size())
}

3.16 clear

void DequeSuite::clear()
{
    std::deque<int> a({1, 2, 3, 4, 5});

    TEST_ASSERT_EQUALS(5, a.size())
    a.clear();

    TEST_ASSERT_EQUALS(0, a.size())
}

3.17 emplace

void DequeSuite::emplace()
{
    std::deque<std::string> names;
    auto it = names.emplace(names.begin(), "james"); //james
    TEST_ASSERT_EQUALS("james", *it)
    it = names.emplace(names.end(), "jim"); //james jim
    TEST_ASSERT_EQUALS("jim", *it)
}

说明:

  • 返回iterator指向被删除元素的下一位置

3.18 emplace_front

void DequeSuite::emplace_front()
{
    std::deque<std::string> names;
    names.emplace_front("james"); //james
    TEST_ASSERT_EQUALS("james", names.front())
    names.emplace_front("jim"); //james jim
    TEST_ASSERT_EQUALS("jim", names.front())
}

说明:

  • 返回iterator指向被删除元素的下一位置

3.19 emplace_back

void DequeSuite::emplace_back()
{
    std::deque<std::string> names;
    names.emplace_back("james"); //james
    TEST_ASSERT_EQUALS("james", names.back())
    names.emplace_back("jim"); //james jim
    TEST_ASSERT_EQUALS("jim", names.back())
}

3.20 get_allocator

void DequeSuite::get_allocator()
{
    std::deque<int> a;
    auto allocator = a.get_allocator();
    int* p = allocator.allocate(5);

    allocator.deallocate(p, 5);
    try
    {
        p = allocator.allocate(allocator.max_size() + 1);
    }
    catch(...)
    {
        p = nullptr;
    }
    TEST_ASSERT_EQUALS(true, p == nullptr)
}
<think>好的,我现在需要详细回答用户关于栈和队列的问题。首先,用户提到必须记忆的内容,包括栈的LIFO特性,std::stack的常用方法,队列的FIFO特性,std::queue的常用方法,循环队列的空满判断条件。然后是需要熟练掌握的部分,如用栈实现表达式求值和括号匹配,用队列实现BFS,以及双端队列std::deque的底层结构。 首先,我得先回顾栈的基本概念。栈是后进先出的数据结构,主要的操作是push、pop和top。在C++中,std::stack通常基于deque或list实现。用户可能需要知道这些方法的用法和注意事项,比如pop不返回元素,而top返回顶部元素但不移除。 然后是队列,FIFO结构,主要操作是push、pop、front。同样,std::queue的底层容器可以是deque或list。需要注意front和back的区别,以及pop操作同样不返回元素。 循环队列的空满判断条件是一个容易出错的地方。通常,循环队列使用数组实现,头尾指针的移动需要模运算。判断空的条件是头尾指针相等,满的条件则是尾指针的下一个位置是头指针(可能需要考虑数组大小,比如使用一个空位来区分空和满的情况)。 接下来是栈的应用,比如表达式求值和括号匹配。表达式求值可能需要用到两个栈,一个操作数栈和一个运算符栈,处理优先级和括号。括号匹配则相对简单,遇到左括号入栈,右括号时检查栈顶是否匹配。 队列在BFS中的应用是关键,BFS通常用队列来管理待访问的节点,逐层扩展。需要确保正确地将相邻节点加入队列,并处理访问标记,防止重复访问。 关于双端队列deque,底层结构通常是分块的连续存储,允许在两端高效插入和删除。需要解释其动态扩展的机制,比如多个固定大小的块,通过中控器管理,这样在两端添加元素时不需要移动其他元素,而中间插入可能效率较低。 在组织回答时,要分点清晰,先介绍基本概念,再具体到C++的实现,然后应用场景,最后底层结构。代码示例需要简洁,比如栈和队列的基本操作,循环队列的实现,括号匹配的代码,BFS的伪代码,以及deque的使用示例。 同时,用户要求最后提出相关问题,我需要确保问题涵盖基本概念、应用、实现细节和常见问题,比如栈和队列的区别,循环队列的判断条件,括号匹配的实现,BFS为何用队列deque的优缺点等。 检查是否有遗漏,比如栈的top和队列的front容易混淆,需要强调。循环队列的实现细节是否准确,比如空和满的判断是否需要牺牲一个存储单元。在代码示例中,确保语法正确,比如使用std::stack<int>等。 最后,确保回答结构符合用户要求:先回答问题,给出解释,然后相关问题。使用用户指定的分隔符,避免使用markdown格式,保持自然的中文表达。</think><< **栈(Stack)** - **特性**:LIFO(后进先出) - **核心操作**: 1. `push()`:元素入栈 2. `pop()`:移除栈顶元素(不返回元素) 3. `top()`:获取栈顶元素(不移除) - **C++实现**:`std::stack<T, Container=deque<T>>`,默认基于双端队列实现。 **队列(Queue)** - **特性**:FIFO(先进先出) - **核心操作**: 1. `push()`:元素入队尾 2. `pop()`:移除队首元素(不返回元素) 3. `front()`:获取队首元素(不移除) - **C++实现**:`std::queue<T, Container=deque<T>>`,默认基于双端队列实现。 **循环队列空/满判断** - 空:`head == tail` - 满:`(tail + 1) % capacity == head`(需牺牲一个存储单元区分空和满) **栈的应用** 1. **表达式求值**: - 用两个栈(操作数栈、运算符栈),按优先级处理运算符。 ```cpp // 示例:计算后缀表达式 std::stack<int> st; for (char c : postfix) { if (isdigit(c)) st.push(c - '0'); else { int b = st.top(); st.pop(); int a = st.top(); st.pop(); if (c == '+') st.push(a + b); else if (c == '*') st.push(a * b); } } ``` 2. **括号匹配**: ```cpp bool isValid(std::string s) { std::stack<char> st; for (char c : s) { if (c == '(' || c == '[' || c == '{') st.push(c); else { if (st.empty()) return false; char top = st.top(); if ((c == ')' && top != '(') || (c == ']' && top != '[') || (c == '}' && top != '{')) return false; st.pop(); } } return st.empty(); } ``` **队列的应用** - **BFS(广度优先搜索)**: ```cpp void BFS(Node* root) { std::queue<Node*> q; q.push(root); while (!q.empty()) { Node* curr = q.front(); q.pop(); for (Node* neighbor : curr->neighbors) { if (!visited[neighbor]) { visited[neighbor] = true; q.push(neighbor); } } } } ``` **双端队列std::deque)** - **底层结构**:动态分块数组,由多个固定大小的连续存储块(如512字节)组成,通过中央控制器管理块指针。 - **特性**: 1. 支持`O(1)`时间复杂度的头尾插入/删除。 2. 中间插入/删除为`O(n)`。 3. 随机访问通过计算块偏移实现,性能略低于`vector`。 - **示例**: ```cpp std::deque<int> dq; dq.push_front(1); // 头部插入 dq.push_back(2); // 尾部插入 dq.pop_front(); // 移除头部 ``` >>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

flysnow010

你的鼓励就是我最大的创作动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值