这里只讨论队列的链式实现,即链式队列(Linked Queue)
基本概念
链式队列是一种基于链表实现的队列数据结构,它使用链表节点来存储数据元素,并通过指针连接这些节点来形成队列结构。与顺序队列(数组实现)相比,链式队列的主要特点是不需要预先分配固定大小的存储空间,可以动态地增长和缩减。
结构组成
链式队列通常由以下两个部分组成:
队首指针(front):指向队列的第一个元素(即将被移除的元素)
队尾指针(rear):指向队列的最后一个元素(最新添加的元素)
每个节点包含:
数据域:存储实际的数据
指针域:指向下一个节点的指针
优缺点分析
优点:
动态大小:不需要预先指定队列大小,可以动态增长
无空间浪费:不会出现顺序队列中的"假溢出"问题
内存利用率高:只在使用时分配内存
缺点:
每个节点需要额外的指针空间
操作稍慢:需要动态内存分配和释放
内存不连续:可能导致缓存不友好
应用场景
链式队列适合以下情况:
无法预估队列最大长度的场景
内存碎片化严重的环境
需要频繁插入删除且队列大小变化大的场合
例如:
操作系统中的进程调度队列
网络数据包缓冲队列
打印机任务队列
时间复杂度分析
操作 时间复杂度
入队 O(1)
出队 O(1)
检查空 O(1)
变体与扩展
双向链式队列:可以在两端进行插入和删除操作
优先队列:结合优先级的链式队列实现
循环链式队列:最后一个节点指向第一个节点,形成循环
实现注意事项
内存管理:确保正确释放出队节点的内存
边界条件:特别注意空队列和只有一个元素的情况
线程安全:在多线程环境中使用时需要添加同步机制
LinkedQueue.cpp实现
#include <iostream>
#include <stdexcept> // For std::underflow_error
template <typename T>
class Node {
public:
T value;
Node* next;
Node(T val) : value(val), next(nullptr) {}
};
template <typename T>
class Queue {
private:
Node<T>* front; // 指向队列首部的指针
Node<T>* rear; // 指向队列尾部的指针
int count; // 队列中的元素数量
public:
Queue() : front(nullptr), rear(nullptr), count(0) {}
~Queue() { clear(); } // 析构函数,释放所有节点内存
void push(const T& value) { // 在队尾添加元素
Node<T>* newNode = new Node<T>(value);
if (rear == nullptr) { // 如果队列为空,则新节点既是头部也是尾部
front = rear = newNode;
} else { // 如果队列不为空,将新节点添加到尾部,并更新尾部指针
rear->next = newNode;
rear = newNode;
}
count++; // 增加计数器
}
void pop() { // 从队首移除元素,如果队列为空则抛出异常
if (empty()) throw std::underflow_error("Queue is empty"); // 检查队列是否为空并抛出异常(可选)
Node<T>* temp = front; // 保存当前队首节点的指针以便释放内存
front = front->next; // 更新队首指针到下一个节点
if (front == nullptr) rear = nullptr; // 如果队列变为空,更新尾部指针为nullptr
delete temp; // 释放原队首节点的内存
count--; // 减少计数器
}
T& front() { // 获取队首元素但不移除(引用返回)
if (empty()) throw std::underflow_error("Queue is empty"); // 检查队列是否为空并抛出异常(可选)
return front->value; // 返回队首节点的值引用(注意:不检查是否为nullptr,因为已经在pop中做了检查)
}
bool empty() const { return count == 0; } // 检查队列是否为空
int size() const { return count; } // 获取队列的大小(元素数量)
void clear() { // 清空队列,释放所有节点内存(可选)
while (!empty()) { pop(); } // 清空队列直到为空,释放所有节点内存(可选)
}
};
main.cpp测试