链式双端队列Deque——数据结构系列教程(c++版)

梦想不会自己发光,真正闪耀的是那个为梦狂奔的你。献给知行的孩子们!(Eric.He著)


  链式双端队列 (Linked Double-Ended Queue),是一种基于链表实现、支持在队列两端(队首和队尾)都能进行插入和删除操作的数据结构。本教程基于链表实现的双端队列,从核心原理、代码结构到实战使用,全面讲解双端队列的原理与实现,功能包含队头入队、队尾入队、队头出队、队尾出队、获取队列头元素、获取队列尾元素等核心功能。

教程目录导航

一、核心原理

1.1 性质

双端队列(Double Ended Queue,简称 Deque,发音类似 “deck”)是一种线性数据结构,它突破了普通队列 “先进先出(FIFO)” 的限制

  1. 可以在队首(head)和队尾(tail)两个端点插入元素;
  2. 可以在队首(head)和队尾(tail)两个端点删除元素;
  3. 保留了队列的 “线性” 特性(元素按顺序排列),但操作更灵活。

你可以把双端队列想象成一个 “双向开口的管道”:既可以从左边(队首)塞 / 取东西,也可以从右边(队尾)塞 / 取东西。

1.2 核心操作

操作名称 功能描述 时间复杂度
队头入队 将指定元素添加到队头,队列满时抛出异常或返回失败标识 O(1)
队尾入队 将指定元素添加到队尾,队列满时抛出异常或返回失败标识 O(1)
队头出队 从队头移除并返回元素,队列为空时抛出异常或返回失败标识 O(1)
队尾出队 从队尾移除并返回元素,队列为空时抛出异常或返回失败标识 O(1)
判空 判断队列是否为空,返回布尔值(true = 空,false = 非空) O(1)
判满 判断固定容量队列是否已满,返回布尔值(true = 满,false = 未满)(链式队列无需此操作) O(1)
获取队头元素 查看队头元素的值,不执行移除操作,队列为空时抛出异常或返回无效值 O(1)
获取队尾元素 查看队尾元素的值,不执行移除操作,队列为空时抛出异常或返回无效值 O(1)
获取队列大小 查看队列元素数量,队列为空时返回0 O(1)

1.3 存储结构

双端队列一般采用链式存储形式:

每一个节点有三个域,即data数据域、prev前一个节点地址、next下一个节点地址。

二、代码结构解析

2.1 整体架构

提供的代码是模板化的双端队列实现(支持任意类型),采用C++结构体封装,分为头文件(声明)和源文件(实现)两部分,核心结构如下:

模块 作用 关键结构/函数
节点结构体 存储数据、前一个节点地址、下一个节点地址 DequeNode<T>(模板泛型)
双端队列结构体 封装所有操作 LinkedDeque<T>(模板类)

2.2 关键结构体详解

(1)节点结构体(QueueNode)

template <typename T>
struct DequeNode
{
    T data;             //队列节点数据
    DequeNode* prev;    //前一个队列节点指针
    DequeNode* next;    //下一个队列节点指针
};

(1)队列结构体(LinkedQueue)

template <typename T>
struct LinkedDeque{
    //------------------------------------------------------------------------------------------------------
    //                                          私有成员                                                ||
    // 注:私有成员只能在该结构体内部访问调用,外部通过该结构体定义(实例化)的变量(对象)不能访问调用)   ||
    //------------------------------------------------------------------------------------------------------
    private:

        //---------------声明私有成员变量---------------

        DequeNode<T> *head = nullptr;   // 队列头指针
        DequeNode<T> *tail = nullptr;   // 队列尾指针
        int qty = 0;                    // 用于跟踪队列中的元素数量

        //---------------声明私有成员函数---------------



    //------------------------------------------------------------------------------------------------------
    //                                          公共成员                                                ||
    // 注:公共成员能在该结构体内部访问调用,外部通过该结构体定义(实例化)的变量(对象)也能访问调用)     ||
    //------------------------------------------------------------------------------------------------------
    public:

        //---------------声明公共成员函数---------------

        // 构造函数:外部通过该结构体定义(实例化)变量(对象)时,自动执行该函数(主要用于初始化成员变量的值)
        LinkedDeque() {
            qty = 0;
        }

        int isEmpty();                      // 判断队列是否为空
        int isFull();                       // 判断队列是否已满
        T getHead();                        // 获取队列头元素(不弹出)
        T getTail();                        // 获取队列尾元素(不弹出)
        void pushHead(const T& value);   // 队头入队(左入)
        void pushTail(const T& value);    // 队尾入队(右入)
        T popHead();                      // 队头出队(左出))
        T popTail();                       // 队尾出队(右出))
        int getSize();                  // 获取队列大小
        void print();                     // 打印队列数据
        void destroyDeque();              // 手动销毁整个队列(防止内存泄漏)

        // 析构函数:自动销毁栈,避免内存泄漏
        ~LinkedDeque() {
            destroyDeque();
        }
};

三、核心操作实现详解

3.1 获取元素

(1)获取队头元素(getHead)

template <typename T>
T LinkedDeque<T>::getHead()
{
    if (isEmpty()) {
        return T();
    }
    return head->data;
}

(2)获取队列尾元素(getTail)

template <typename T>
T LinkedDeque<T>::getTail()
{
    if (isEmpty()) {
        return T();;
    }
    return tail->data;
}

3.2 队头入队(pushHead)

template <typename T>
void LinkedDeque<T>::pushHead(const T& value)
{
    DequeNode<T> *p = new DequeNode<T>;
    p->data = value;
    p->prev = nullptr;
    if(isEmpty())
    {
        p->next = nullptr;
        head = p;
        tail = p;
    }
    else
    {
        p->next = head;
        head->prev = p;
        head = p;
    }

    qty++;
}

3.3 队尾入队(pushTail)

template <typename T>
void LinkedDeque<T>::pushTail(const T& value)
{
    DequeNode<T> *p = new DequeNode<T>;
    p->data = value;
    p->next = nullptr;
    if(isEmpty())
    {
        p->prev = nullptr;
        head = p;
        tail = p;
    }
    else
    {
        p->prev = tail;
        tail->next = p;
        tail = p;
    }

    qty++;
}

3.4 队头出队(popHead)

template <typename T>
T LinkedDeque<T>::popHead()
{
    T value;
    if(isEmpty())
    {
        printf("队列为空,无法出队。\n");
        return T();
    }

    DequeNode<T> *p = head;
    if(p->next==nullptr)
    {
        head = nullptr;
    }
    else
    {
        head = p->next;
        head->prev = nullptr;
    }

    value = p->data;
    delete(p);
    qty--;

    return value;
}

3.5 队尾出队(popTail)

template <typename T>
T LinkedDeque<T>::popTail()
{
    T value;
    if(isEmpty())
    {
        printf("队列为空,无法出队。\n");
        return T();
    }

    DequeNode<T> *p = tail;
    if(p->prev==nullptr)
    {
        tail = nullptr;
    }
    else
    {
        tail = p->prev;
        tail->next = nullptr;
    }

    value = p->data;
    delete(p);
    qty--;

    return value;
}

四、实战使用示例

4.1 基础类型(int)使用示例

#include "LinkedDeque.h"
#include <iostream>

int main() {
    LinkedDeque<int> deques;

    // 1. 尾入队测试
    printf("\n=== 尾入队测试 ===\n");
    deques.pushTail(1);
    deques.pushTail(2);
    deques.pushTail(3);
    deques.pushTail(4);
    deques.pushTail(5);
    printf("尾入队1、2、3、4、5后:\n");
    deques.print();

    // 1. 头入队测试
    printf("\n=== 头入队测试 ===\n");
    deques.pushHead(6);
    deques.pushHead(7);
    deques.pushHead(8);
    deques.pushHead(9);
    deques.pushHead(10);
    printf("头入队6、7、8、9、10后:\n");
    deques.print();

    // 2. 头出队测试
    printf("\n=== 头出队测试 ===\n");
    printf("出队3次:");
    printf("%d ",deques.popHead());
    printf("%d ",deques.popHead());
    printf("%d \n",deques.popHead());
    printf("出队后");
    deques.print();

    // 2. 尾出队测试
    printf("\n=== 尾出队测试 ===\n");
    printf("出队3次:");
    printf("%d ",deques.popTail());
    printf("%d ",deques.popTail());
    printf("%d \n",deques.popTail());
    printf("出队后");
    deques.print();

    return 0;
}

4.2 输出结果


=== 尾入队测试 ===
尾入队1、2、3、4、5后:
队列内容:1 2 3 4 5

=== 头入队测试 ===
头入队6、7、8、9、10后:
队列内容:10 9 8 7 6 1 2 3 4 5

=== 头出队测试 ===
出队3次:10 9 8
出队后队列内容:7 6 1 2 3 4 5

=== 尾出队测试 ===
出队3次:5 4 3
出队后队列内容:7 6 1 2

===== 队列内存已全部释放 =====
            

五、完整可运行代码

5.1 Entitys.h 头文件


#ifndef ENTITYS_H_INCLUDED
#define ENTITYS_H_INCLUDED

//************************************************************************************************************************************************************************
//                                                                      自定义类型
//************************************************************************************************************************************************************************

//========================================================================================================================================================================
//                                                                    学生结构体(Student)
//========================================================================================================================================================================
struct Student {
    int id;// 学号
    std::string name;// 姓名
    std::string dob;// 出生日期
    std::string sex;// 性别
    std::string gender;// 民族
    std::string address;// 地址
    float score;// 入学总分
    std::string school;// 学校
    std::string team;// 班级
    std::string status;// 状态

    bool operator<(const Student& other) const { return id < other.id; }
    bool operator>(const Student& other) const { return id > other.id; }
    bool operator==(const Student& other) const { return id == other.id; }
    bool operator!=(const Student& other) const { return id != other.id; }

    friend std::ostream& operator<<(std::ostream& os, const Student& s) {
        os << "[" << s.id<< ", " << s.name << ", " << s.dob << ", " << s.sex << ", " << s.gender  << ", " << s.address << ", " << s.score<< ", " << s.school<< ", " << s.team<< ", " << s.status << "]";
        return os;
    }
};

//========================================================================================================================================================================
//
//                                                                    学生索引结构体(Student)
//
//========================================================================================================================================================================
struct StudentIndex {
    int id;// 学号
    int row;// 行号

    bool operator<(const StudentIndex& other) const { return id < other.id; }
    bool operator>(const StudentIndex& other) const { return id > other.id; }
    bool operator==(const StudentIndex& other) const { return id == other.id; }
    bool operator!=(const StudentIndex& other) const { return id != other.id; }

    friend std::ostream& operator<<(std::ostream& os, const StudentIndex& i) {
        os << "[" << i.id << ", " << i.row<< "]";
        return os;
    }
};

//========================================================================================================================================================================
//                                                                    迷宫坐标结构体(Pos)
//========================================================================================================================================================================
struct Pos{
    int x;     //x坐标
    int y;    //y坐标
    int step; //步数
};

//========================================================================================================================================================================
//                                                                    打印任务结构体(PrintTask)
//========================================================================================================================================================================
struct PrintTask{
    int taskId;     // 任务ID
    char content[50]; // 打印内容
};

#endif // ENTITYS_H_INCLUDED
        

LinkedDeque.h 头文件


#ifndef LINKEDDEQUE_H_INCLUDED
#define LINKEDDEQUE_H_INCLUDED

#include "Entitys.h"

//************************************************************************************************************************************************************************
//
//      类名:链表双端队列(LinkedDeque)
//
//      概述:一个可复用的链表双端队列,存储结构采用链表实现,支持任意类型数据(模板泛型)。
//
//      说明:因为采用链表存储结构,队列的容量没限制,入队出队时间复杂度为O(1)
//
//************************************************************************************************************************************************************************

//========================================================================================================================================================================
//
//                                                                 队列节点结构体(QueueNode)
//
//========================================================================================================================================================================
template <typename T>
struct DequeNode
{
    T data;             //队列节点数据
    DequeNode* prev;    //前一个队列节点指针
    DequeNode* next;    //下一个队列节点指针
};

//========================================================================================================================================================================
//
//                                                               链表双端队列结构体(LinkedDeque)
//
//========================================================================================================================================================================
template <typename T>
struct LinkedDeque{
    //------------------------------------------------------------------------------------------------------
    //                                          私有成员                                                ||
    // 注:私有成员只能在该结构体内部访问调用,外部通过该结构体定义(实例化)的变量(对象)不能访问调用)   ||
    //------------------------------------------------------------------------------------------------------
    private:

        //---------------声明私有成员变量---------------

        DequeNode<T> *head = nullptr;   // 队列头指针
        DequeNode<T> *tail = nullptr;   // 队列尾指针
        int qty = 0;                    // 用于跟踪队列中的元素数量

        //---------------声明私有成员函数---------------



    //------------------------------------------------------------------------------------------------------
    //                                          公共成员                                                ||
    // 注:公共成员能在该结构体内部访问调用,外部通过该结构体定义(实例化)的变量(对象)也能访问调用)     ||
    //------------------------------------------------------------------------------------------------------
    public:

        //---------------声明公共成员函数---------------

        // 构造函数:外部通过该结构体定义(实例化)变量(对象)时,自动执行该函数(主要用于初始化成员变量的值)
        LinkedDeque() {
            qty = 0;
        }

        int isEmpty();                      // 判断队列是否为空
        int isFull();                       // 判断队列是否已满
        T getHead();                        // 获取队列头元素(不弹出)
        T getTail();                        // 获取队列尾元素(不弹出)
        void pushHead(const T& value);   // 队头入队(左入)
        void pushTail(const T& value);    // 队尾入队(右入)
        T popHead();                      // 队头出队(左出)
        T popTail();                       // 队尾出队(右出)
        int getSize();                  // 获取队列大小
        void print();                     // 打印队列数据
        void destroyDeque();              // 手动销毁整个队列(防止内存泄漏)

        // 析构函数:自动销毁栈,避免内存泄漏
        ~LinkedDeque() {
            destroyDeque();
        }
};

#endif // LINKEDDEQUE_H_INCLUDED
                

LinkedDeque.cpp 程序代码


#include <iostream>
#include"LinkedDeque.h"

//---------------实现公共成员函数---------------

// 判断队列是否为空
template <typename T>
int LinkedDeque<T>::isEmpty()
{
    return qty == 0;
}

// 判断队列是否已满
template <typename T>
int LinkedDeque<T>::isFull()
{
    return -1;
}

// 获取队列头元素(不弹出)
template <typename T>
T LinkedDeque<T>::getHead()
{
    if (isEmpty()) {
        return T();
    }
    return head->data;
}

// 获取队列尾元素(不弹出)
template <typename T>
T LinkedDeque<T>::getTail()
{
    if (isEmpty()) {
        return T();;
    }
    return tail->data;
}

// 队头入队(左入)
template <typename T>
void LinkedDeque<T>::pushHead(const T& value)
{
    DequeNode<T> *p = new DequeNode<T>;
    p->data = value;
    p->prev = nullptr;
    if(isEmpty())
    {
        p->next = nullptr;
        head = p;
        tail = p;
    }
    else
    {
        p->next = head;
        head->prev = p;
        head = p;
    }

    qty++;
}

// 队尾入队(右入)
template <typename T>
void LinkedDeque<T>::pushTail(const T& value)
{
    DequeNode<T> *p = new DequeNode<T>;
    p->data = value;
    p->next = nullptr;
    if(isEmpty())
    {
        p->prev = nullptr;
        head = p;
        tail = p;
    }
    else
    {
        p->prev = tail;
        tail->next = p;
        tail = p;
    }

    qty++;
}

// 队头出队(左出)
template <typename T>
T LinkedDeque<T>::popHead()
{
    T value;
    if(isEmpty())
    {
        printf("队列为空,无法出队。\n");
        return T();
    }

    DequeNode<T> *p = head;
    head = p->next;
    head->prev = nullptr;
    value = p->data;
    delete(p);
    qty--;

    return value;
}

// 队尾出队(右出)
template <typename T>
T LinkedDeque<T>::popTail()
{
    T value;
    if(isEmpty())
    {
        printf("队列为空,无法出队。\n");
        return T();
    }

    DequeNode<T> *p = tail;
    tail = p->prev;
    tail->next = nullptr;
    value = p->data;
    delete(p);
    qty--;

    return value;
}

// 获取队列大小
template template <typename T>
int LinkedDeque<T>::getSize()
{
    return qty;
}

// 打印队列数据
template <typename T>
void LinkedDeque<T>::print()
{
    printf("队列内容:");

    DequeNode<T> *p = head;
    if(p == nullptr)
    {
        printf("队列为空!无法遍历。\n");
        return;
    }
    while(p != nullptr)
    {
        printf("%d ",p->data);
        p = p->next;
    }
    printf("\n");
}

// 销毁整个栈(防止内存泄漏)
template <typename T>
void LinkedDeque<T>::destroyDeque()
{
    while(head != nullptr)
    {
        DequeNode<T> *p = head;
        head = p->next;
        delete(p);
        qty--;
    }
    printf("\n===== 队列内存已全部释放 =====\n");
}

// 显式实例化常用类型(避免链接错误,可选)
template class LinkedDeque<int>;
template class LinkedDeque<char>;
template class LinkedDeque<float>;
template class LinkedDeque<std::string>;
template class LinkedDeque<Student>; // 新增:显式实例化Student类型
template class LinkedDeque<Pos>; // 新增:显式实例化Pos类型
template class LinkedDeque<PrintTask>; // 新增:显式实例化PrintTask类型
        

main.cpp 测试代码

#include "LinkedDeque.h"
#include <iostream>

int main() {
    LinkedDeque<int> deques;

    // 1. 尾入队测试
    printf("\n=== 尾入队测试 ===\n");
    deques.pushTail(1);
    deques.pushTail(2);
    deques.pushTail(3);
    deques.pushTail(4);
    deques.pushTail(5);
    printf("尾入队1、2、3、4、5后:\n");
    deques.print();

    // 1. 头入队测试
    printf("\n=== 头入队测试 ===\n");
    deques.pushHead(6);
    deques.pushHead(7);
    deques.pushHead(8);
    deques.pushHead(9);
    deques.pushHead(10);
    printf("头入队6、7、8、9、10后:\n");
    deques.print();

    // 2. 头出队测试
    printf("\n=== 头出队测试 ===\n");
    printf("出队3次:");
    printf("%d ",deques.popHead());
    printf("%d ",deques.popHead());
    printf("%d \n",deques.popHead());
    printf("出队后");
    deques.print();

    // 2. 尾出队测试
    printf("\n=== 尾出队测试 ===\n");
    printf("出队3次:");
    printf("%d ",deques.popTail());
    printf("%d ",deques.popTail());
    printf("%d \n",deques.popTail());
    printf("出队后");
    deques.print();
    
    return 0;
}

输出结果


=== 尾入队测试 ===
尾入队1、2、3、4、5后:
队列内容:1 2 3 4 5

=== 头入队测试 ===
头入队6、7、8、9、10后:
队列内容:10 9 8 7 6 1 2 3 4 5

=== 头出队测试 ===
出队3次:10 9 8
出队后队列内容:7 6 1 2 3 4 5

=== 尾出队测试 ===
出队3次:5 4 3
出队后队列内容:7 6 1 2

===== 队列内存已全部释放 =====
    

六、注意事项

模板类型约束

  • 模板类型需支持</>/==运算符(内置类型如int/string默认支持,自定义类型需重载);
  • 自定义类型需重载<<运算符(打印输出时使用)。

显式实例化:代码末尾的template class LinkedQueue<int>;等显式实例化,避免链接错误,新增类型需补充显式实例化。

七、应用场景

八、总结

掌握这些原理后,你不仅能手写双端队列,还能根据实际场景选择最优的实现方式,也能理解算法题中双端队列的应用逻辑。


返回顶部