说明:本笔记依照《王道论坛-数据结构》视频内容整理。
队列是只允许一端进行插入,在另一端删除的线性表。
一、操作
InitQueue(&Q):初始化队列。构造一个空队列 Q。
DestroyQueue(&Q):销毁队列。销毁并释放队列 Q 所占用的内存空间。
EnQueue(&Q,x):入队。若队列 Q 未满,将 x 加入,使之成为新的对尾。
DeQueue(&Q,&x):出队。若队列 Q 非空,删除对头元素,并用 x 返回。
GetHead(Q,&x):读队头元素。若队列 Q 非空,则将对头元素赋值给 x。
二、队列的顺序存储实现
1、队列
#define MAXSIZE 10 // 定义最大长度
typedef int ElemType; // 根据实际情况定义
typedef struct{
ElemType data[MAXSIZE]; // 用数组存放数据元素(ElemType - 实际数据类型)
int fornt,rear; // fornt - 对头指针,rear - 队尾指针
}QueueT; // Stack(列表)T(数据类型)Q(指针类型)
2、内存布局
3、空队列判断
空队列判断条件:rear == front
。
4、操作
1、InitQueue(&Q)
/****************************************
* 初始化队列
* q - 要操作的队列首地址
****************************************/
void initQueue(QueueT *q)
{
q->rear = q->front = 0;
}
2、DestroyQueue(&Q)
使用静态分配方式实现队列,定义变量时,自动分配空间,变量回收时,销毁空间。
3、EnQueue(&Q,x)
/****************************************
* 入队
* q - 要操作的队列首地址
* x - 入队元素
****************************************/
int enQueue(QueueT *q,ElemType x)
{
if(q->rear==MAXSIZE) return 1; // 队列满
q->data[q->rear] = x; // 将 x 插入队尾
q->rear = q->rear + 1; // 队尾指针后移
return 0;
}
4、DeQueue(&Q,&x)
/****************************************
* 出队
* q - 要操作的队列首地址
* x - 出队元素
****************************************/
int DeQueue(QueueT *q,ElemType *x)
{
if(q->rear == q->front) return 0; // 队列为空
*x = q->data[q->front]; // 提取对头元素
q->front = q->front+1; // 队头后移
// 移动数据
for(int i=0;i<q->rear-q->front;i++){
q->data[i] = q->data[q->front+i];
}
q->rear = q->rear-q->front;
q->front = 0;
// 以上两句不能颠倒
return 0;
}
5、GetHead(&Q,&x)
/****************************************
* 打印队列
* q - 要操作的队列首地址
****************************************/
int GetHead(QueueT *q,ElemType *x)
{
if(q->rear == q->front) return 0; // 队列为空
*x = q->data[q->front]; // 提取对头元素
return 0;
}
6、printfQueue(&Q)
/****************************************
* 打印队列
* q - 要操作的队列首地址
****************************************/
int printfQueue(QueueT *q)
{
int i = 0;
while(q->rear != q->front+i){
printf("q->data[q->fornt+i] = %d\n",q->data[q->front+i]);
i++;
}
return 0;
}
7、QueueEmpty(&Q)
/****************************************
* 判断队列是否为空
* q - 要操作的队列首地址
****************************************/
int queueEmpty(QueueT *q)
{
if(q->rear == q->front) return 0; // 队列为空
else return 1; // 队列不为空
}
5、总结
问题:以上队列在执行出队操作后需要移动数据。
三、循环队列
核心:通过取模运算完成循环。
用取模运算将线状存储空间在逻辑上编程“环状”。
队列空条件:q->rear == q->front
。
队列已满条件:队尾指针的再下一个位置时队头,即(q->rear+1)%MAXSIZE == q->front
。
队列元素个数计算:(rear+MAXSIZE-front)%MAXSIZE
。
四、队列的链式存储实现
1、队列
typedef int ElemType; // 根据实际情况定义
typedef struct lNode{ // 定义链式队列结点类型
ElemType data; // 每个结点存放一个数据元素
struct lNode *next; // 指针指向下一个元素
}lNode; // lNode 强调是一个结点
typedef struct{ // 链式队列
lNode *front, *rear; // 队列的队头和队尾
}LinkQueueT;
2、操作
1、InitQueue(&Q)
/****************************************
* 初始化队列
* q - 要操作的队列首地址
****************************************/
void initQueue(LinkQueueT *q)
{
q->front = q->rear = (lNode*)malloc(sizeof(lNode)); // 创建头结点,并初始化队头和队尾指针
q->front->next = NULL; // 初始化头结点中 next 指针
}
2、isEmpty(Q)
/****************************************
* 判断队列是否为空
* q - 要操作的队列
****************************************/
int isEmpty(LinkQueueT q)
{
if(q.front==q.rear) return 0; // 队列为空
else return 1; // 队列不为空
}
3、enQueue(&Q,x)
/****************************************
* 入队
* q - 要操作的队列首地址
* x - 要插入元素
****************************************/
void enQueue(LinkQueueT *q,ElemType x)
{
lNode *s = (lNode*)malloc(sizeof(lNode));
s->data = x;
s->next = NULL;
q->rear->next = s;
q->rear = s;
}
4、deQueue(&Q,&x)
/****************************************
* 出队
* q - 要操作的队列首地址
* x - 要插入元素
****************************************/
int deQueue(LinkQueueT *q,ElemType *x)
{
if(q->front==q->rear) return 1; // 空队列
lNode *p = q->front->next;
*x = p->data; // 用变量 x 返回头元素
q->front->next = p->next; // 修改头结点 next 指针
if(q->rear==p) q->rear=q->front; // 对最后一个结点进行处理,修改 rear 指针
free(p); // 释放结点
return 0;
}
五、总结
顺序存储:预分配的空间耗尽时队满。
链式存储:一般不会队满,除非内存不足。