一、队列的基本概念
队列(Queue)是一种遵循"先进先出"(FIFO, First In First Out)原则的线性数据结构。它在计算机科学领域有着广泛应用,例如:
- 操作系统中的进程调度与任务排队
- 网络通信中的数据包缓冲处理
- 广度优先搜索(BFS)算法的实现
- 打印机等外设的任务处理队列
队列的核心操作包括:
- 入队(Enqueue):在队列尾部插入元素
- 出队(Dequeue):从队列头部移除元素
- 判空(IsEmpty):判断队列是否为空
- 获取队头(GetFront):查看队列头部元素
二、基于计数器的循环队列实现
本次实现的循环队列通过引入计数器(size)来解决传统循环队列的空间浪费问题,下面我们详细解析代码实现:
#include <iostream>
#include <stdio.h>
using namespace std;
#define maxsize 10
typedef int elemtype;
typedef struct queue {
elemtype data[maxsize];
int front ,rear;
int size;
}sq_queue;
1. 数据结构定义
maxsize:定义队列的最大容量为10elemtype:元素类型别名,此处为int类型sq_queue:队列结构体,包含三个成员:data[maxsize]:存储队列元素的数组front:队头指针,指向队头元素rear:队尾指针,指向队尾元素的下一个位置size:计数器,记录当前队列中的元素个数
关键改进:相比传统循环队列,本实现增加了size变量,用于直接记录队列中元素的数量
2. 队列初始化
void init_queue(sq_queue &q) {
q.rear=q.front=0;
q.size=0;
}
初始化操作做了三件事:
- 将队头指针front置为0
- 将队尾指针rear置为0
- 将计数器size置为0,表示初始状态下队列为空
3. 队列判空操作
bool queueempty(sq_queue &q) {
if (q.size==0) {
cout<<"队列为空"<<endl;
return true;
}
else
cout<<"队列不空"<<endl;
return false;
}
判空逻辑非常直观:
- 当size为0时,队列为空,返回true
- 否则队列非空,返回false
- 同时输出相应的状态提示信息
4. 入队操作
void enqueue(sq_queue &q,elemtype e) {
if (q.size==maxsize) {
cout<<"队列已满,无法插入"<<endl;
return;
}
q.data[q.rear]=e;
q.rear=(q.rear+1)%maxsize;
q.size++;
cout<<"插入元素"<<e<<endl;
}
入队操作步骤:
- 检查队列是否已满:通过判断size是否等于maxsize
- 若队列未满,将元素e存入rear指向的位置
- 队尾指针rear循环后移(通过模运算实现循环特性)
- 计数器size加1,更新元素数量
- 输出插入成功的提示信息
5. 出队操作
void dequeue(sq_queue &q) {
if (queueempty(q)) {
return;
}
cout<<"删除元素为"<<q.front<<endl; // 注意:此处应为q.data[q.front]
q.front=(q.front+1)%maxsize;
q.size--;
}
出队操作步骤:
- 先调用queueempty函数检查队列是否为空,若为空则直接返回
- 输出要删除的队头元素(注:原代码此处有笔误,应输出q.data[q.front])
- 队头指针front循环后移
- 计数器size减1,更新元素数量
6. 获取队头元素
elemtype gethead(sq_queue &q) {
if (queueempty(q)) {
return q.front; // 队空时返回值不合理,建议返回特殊标记
}
else {
return q.data[q.front];
}
}
获取队头元素操作:
- 检查队列是否为空
- 若队列非空,返回front指针指向的元素
- 若队列为空,返回front值(此处设计不合理,建议返回-1等特殊标记)
7. 主函数测试
int main() {
sq_queue q;
// 测试初始化队列
cout << "=== 初始化队列 ===" << endl;
init_queue(q);
// 测试队列是否为空
cout << "\n=== 测试队列是否为空 ===" << endl;
queueempty(q);
// 测试入队操作
cout << "\n=== 测试入队操作 ===" << endl;
enqueue(q, 10);
enqueue(q, 20);
enqueue(q, 30);
enqueue(q, 40);
// 再次检查队列是否为空
cout << "\n=== 再次检查队列是否为空 ===" << endl;
queueempty(q);
// 测试获取队头元素
cout << "\n=== 测试获取队头元素 ===" << endl;
cout << "当前队头元素为: " << gethead(q) << endl;
// 测试出队操作
cout << "\n=== 测试出队操作 ===" << endl;
dequeue(q);
dequeue(q);
// 再次获取队头元素
cout << "\n=== 再次获取队头元素 ===" << endl;
cout << "当前队头元素为: " << gethead(q) << endl;
// 测试队列满的情况
cout << "\n=== 测试队列满的情况 ===" << endl;
enqueue(q, 50);
enqueue(q, 60);
enqueue(q, 70);
enqueue(q, 80);
enqueue(q, 90);
enqueue(q, 100);
enqueue(q, 110);
enqueue(q, 120); // 尝试插入第10个元素(队列最大容量为10)
// 测试连续出队直到队空
cout << "\n=== 测试连续出队直到队空 ===" << endl;
dequeue(q);
dequeue(q);
dequeue(q);
dequeue(q);
dequeue(q);
dequeue(q);
dequeue(q);
dequeue(q);
// 测试队空时出队
cout << "\n=== 测试队空时出队 ===" << endl;
dequeue(q);
return 0;
}
主函数设计了完整的测试流程,涵盖了队列的各种基本操作和边界情况,包括:
- 初始化队列
- 队列判空
- 入队操作
- 获取队头元素
- 出队操作
- 队列满状态测试
- 连续出队至空队列
- 队空时的出队操作
三、基于计数器的循环队列的优缺点
优点:
- 空间利用率100%:相比传统循环队列,不需要牺牲一个存储空间来区分空满状态
- 判断逻辑简单:通过size直接判断队列空满,无需复杂的模运算判断
- 操作高效:所有操作(入队、出队、判空、获取队头)的时间复杂度均为O(1)
- 实现直观:通过计数器直观反映队列中元素数量,便于理解和维护
- 循环特性:保持了循环队列的优势,避免了普通顺序队列的"假溢出"问题
缺点:
- 需要额外存储空间:增加了一个int类型的size变量(在maxsize较大时影响可忽略)
- 容量固定:队列大小在初始化时确定,无法动态扩展
- 元素类型受限:需要预先定义元素类型,通用性不如模板实现
- 计数器维护成本:入队和出队时都需要手动更新size,增加了出错可能性
四、空间浪费问题的优化分析
传统循环队列通过(rear+1)%maxsize == front判断队列满,这种方式必须牺牲一个存储空间,导致实际可用容量为maxsize-1,存在1个单位的空间浪费。
本实现通过引入size计数器,从根本上解决了这个问题:
- 队空条件:
size == 0 - 队满条件:
size == maxsize
这种设计使得队列的实际可用容量达到maxsize,实现了100%的空间利用率。当maxsize较大时,这种优化能显著提升空间效率。
以代码中的maxsize=10为例:
- 传统实现最多存储9个元素
- 本实现可存储10个元素,充分利用了所有空间
空间效率的提升对于资源受限的环境(如嵌入式系统)尤为重要,即使在普通应用中,这种直观的设计也能减少理解和维护成本。
五、总结
基于计数器的循环队列是对传统循环队列的有效改进,它通过增加一个简单的计数器变量,在几乎不增加额外开销的情况下,解决了空间浪费问题,同时简化了队列空满状态的判断逻辑。
这种实现特别适合于:
- 已知最大容量的场景
- 对空间利用率有要求的场景
- 需要直观了解队列元素数量的场景
当然,在实际开发中,我们还可以进一步改进,例如:
- 使用模板实现通用类型队列
- 增加动态扩容功能
- 实现线程安全版本
通过本文的解析,希望读者能理解循环队列的两种实现方式的异同,掌握计数器方式的优势,并能根据实际需求选择合适的队列实现方案。数据结构的选择和实现细节的优化,往往能在实际应用中带来显著的性能提升。
417

被折叠的 条评论
为什么被折叠?



