C++ STL 之双向队列 deque 详解

Part.I Attention

在这里插入图片描述

  • deque 是 double-ended queue 的缩写,意即双端队列,详细信息参见官网
  • deque<T>容器适配器是
  • 必须要包含头文件#include <deque>
  • deque相较于vector:①它不擅长在中间添加删除元素;②它擅长在队列头部和尾部添加删除元素,时间复杂度为O(1),而vector只有在尾部添加删除元素的时间复杂度才为O(1)
  • deque相较于queue:queue只擅长在头部删除元素,在尾部添加元素;而deque在头部和尾部都擅长添加或删除元素。
  • C++ 双向队列用的并不是很多,一般在刷题的时候常用vector。但是它也是STL中的一种比较典型的数据结构,所以在实际生产中也可以考虑使用它。

Part.II Funciton

deque 的函数相较于queue 就多很多了,和Vector相当,如下图:

在这里插入图片描述

下面是对常用函数的说明:

函数 解释
begin() 开始的迭代器
end() 结束的迭代器
rbegin() 反向开始的迭代器
rend() 反向结束的迭代器
size() 所含元素个数
max_size 最大能容纳的元素个数
resize() 重新调整其大小
empty() 判断其是否是空的
front() 最前面的元素
back() 最后面的元素
assign() 可用它通过数组来对其进行赋值
push_back() 在双端队列后面添加元素
push_front() 在双端队列前面添加元素
pop_back() 弹出双端队列最后一个元素
pop_front() 弹出双端队列最前面一个元素
insert() 插入元素,有好几种用法
erase(d.begin(),d.begin()+3) 删除前面3个元素
a.swap(b) 交换ab
clear() 清空所有元素
emplace(itr,a) 在迭代器itr位置前插入元素a,返回a的迭代器
emplace_front() 在前面插入元素
emplace_back() 在后面插入元素

Part.III Code

下面根据一道 LeetCode 上面的编程题 剑指 Offer II 026. 重排链表 来介绍双端队列的使用。

Chap.I 题目描述与分析

题目描述

给定一个单链表 L 的头节点 head ,单链表 L 表示为:
L0 → L1 → … → Ln-1 → Ln

请将其重新排列后变为:
L0 → Ln → L1 → Ln-1 → L2 → Ln-2 → …

不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。


分析

根据题意,需要将原链表按照前后交错的形式重新排列;笔者立刻就想到了双端队列,思路如下:

  • 首先遍历一边原链表,将所有的结点都存到双端队列中
  • 然后顺次从头部取一个,从尾部取一个
  • 直到队列中不存在节点为止。
  • 时间复杂度O(n),空间复杂度O(n)

Chap.II 代码实现

双端队列C++代码。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
    
    
public:
    void reorderList(ListNode* head) {
    
    
        deque<ListNode*> dqu;
        while(head) {
    
    
            dqu.emplace_back(head);
            head=head->next;
        }
        ListNode* pre=nullptr;
        while(dqu.size()>=2) {
    
    
            if(!pre) {
    
    
                pre=dqu.front();
            }
            else {
    
    
                pre->next=dqu.front();
                pre=pre->next;
            }
            pre->next=dqu.back();
            pre=pre->next;
            dqu.pop_front();dqu.pop_back();
        }
        if(!dqu.empty()) {
    
    
            if(!pre) pre=dqu.front();
            else {
    
    
                pre->next=dqu.front();
                pre=pre->next;
            }
        }
        pre->next=nullptr;
    }
};

可以作为一个使用双端队列的示例,但是对于此题,使用双端队列并不一定是最优解。

Chap.III 两一种解题思路

下面的一种思路效率就比较高:

  • 利用两个指针,一个是快指针(一次走两个节点),另一个是慢指针(一次走一个节点);慢指针边走边翻转链表,用一个pre做中间变量。
  • 当快指针都到链表尾部时,快指针恰好指向链表的中间
  • 此时快指针指向pre,慢指针继续向前走,边走边交叉构建结果即可。

下面是代码实现(写的有点乱)

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
    
    
public:
    void reorderList(ListNode* head) {
    
    
        if(!head||!head->next) return;
        int tag=0;      // 针对节点个数奇偶涉及的标识 奇1偶0
        ListNode* slow=head, *fast=head, *pre=nullptr;
        while(fast) {
    
    
            if(fast->next) {
    
    
                fast=fast->next;
                ListNode* tmp=slow->next;
                slow->next=pre; pre=slow;
                slow=tmp;
            }
            else {
    
     tag=1; break; }
            if(fast->next) {
    
    
                fast=fast->next;
            }
            else break;
        }
        fast=nullptr;
        head=slow; // slow 就是中点
        slow=slow->next;
        head->next=fast;
        if(tag) {
    
     fast=slow; slow=slow->next; fast->next=head; head=fast; }
        while(slow) {
    
    
            fast=pre; pre=pre->next; fast->next=head; head=fast;
            fast=slow; slow=slow->next; fast->next=head; head=fast;
        }
        if(pre) pre->next=head; head=pre;
    }
};

Part.IV 双端队列的实现

下面一部分是双端队列的底层实现思想,以 LeetCode 641. 设计循环双端队列 题目为例。

Chap.I 题目描述

在不用deque类的前提下,设计实现双端队列。

实现 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 。

Chap.II 代码实现

这道题和 662. 设计循环队列 很相似。可以看 题解

#include <iostream>
#include <vector>

using namespace std;

class MyCircularDeque {
    
    

private:
    vector<int> arr;
    int front;
    int rear;
    int capacity;

public:
    /** Initialize your data structure here. Set the size of the deque to be k. */
    MyCircularDeque(int k) {
    
    
        capacity = k + 1;
        arr.assign(capacity, 0);

        front = 0;
        rear = 0;
    }

    /** Adds an item at the front of Deque. Return true if the operation is successful. */
    bool insertFront(int value) {
    
    
        if (isFull()) {
    
    
            return false;
        }
        front = (front - 1 + capacity) % capacity;
        arr[front] = value;
        return true;
    }

    /** Adds an item at the rear of Deque. Return true if the operation is successful. */
    bool insertLast(int value) {
    
    
        if (isFull()) {
    
    
            return false;
        }
        arr[rear] = value;
        rear = (rear + 1) % capacity;
        return true;
    }

    /** Deletes an item from the front of Deque. Return true if the operation is successful. */
    bool deleteFront() {
    
    
        if (isEmpty()) {
    
    
            return false;
        }
        // front 被设计在数组的开头,所以是 +1
        front = (front + 1) % capacity;
        return true;
    }

    /** Deletes an item from the rear of Deque. Return true if the operation is successful. */
    bool deleteLast() {
    
    
        if (isEmpty()) {
    
    
            return false;
        }
        // rear 被设计在数组的末尾,所以是 -1
        rear = (rear - 1 + capacity) % capacity;
        return true;
    }

    /** Get the front item from the deque. */
    int getFront() {
    
    
        if (isEmpty()) {
    
    
            return -1;
        }
        return arr[front];
    }

    /** Get the last item from the deque. */
    int getRear() {
    
    
        if (isEmpty()) {
    
    
            return -1;
        }
        // 当 rear 为 0 时防止数组越界
        return arr[(rear - 1 + capacity) % capacity];
    }

    /** Checks whether the circular deque is empty or not. */
    bool isEmpty() {
    
    
        return front == rear;
    }

    /** Checks whether the circular deque is full or not. */
    bool isFull() {
    
    
        // 注意:这个设计是非常经典的做法
        return (rear + 1) % capacity == front;
    }
};

/**
 * Your MyCircularDeque object will be instantiated and called as such:
 * MyCircularDeque* obj = new MyCircularDeque(k);
 * bool param_1 = obj->insertFront(value);
 * bool param_2 = obj->insertLast(value);
 * bool param_3 = obj->deleteFront();
 * bool param_4 = obj->deleteLast();
 * int param_5 = obj->getFront();
 * int param_6 = obj->getRear();
 * bool param_7 = obj->isEmpty();
 * bool param_8 = obj->isFull();
 */

猜你喜欢

转载自blog.csdn.net/Gou_Hailong/article/details/128381718