数据结构系列篇章-可以参照如下顺序阅读
一、队列概述
1.1 队列定义
队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。队列中没有元素时,称为空队列。
1.2 队列的特性
队列的数据元素又称为队列元素。在队列中插入一个队列元素称为入队,从队列中删除一个队列元素称为出队。因为队列只允许在一端插入,在另一端删除,所以只有最早进入队列的元素才能最先从队列中删除,故队列又称为先进先出(FIFO—first in first out)线性表。
二、顺序队列
2.1 顺序队列的结构
建立顺序队列结构必须为其静态分配或动态申请一片连续的存储空间,并设置两个指针进行管理。一个是队头指针front,它指向队头元素;另一个是队尾指针rear,它指向下一个入队元素的存储位置,如图所示
2.2 顺序队列的入队和出队
每次在队尾插入一个元素是,rear增1;每次在队头删除一个元素时,front增1。随着插入和删除操作的进行,队列元素的个数不断变化,队列所占的存储空间也在为队列结构所分配的连续空间中移动。当front=rear时,队列中没有任何元素,称为空队列。当rear增加到指向分配的连续空间之外时,队列无法再插入新元素,但这时往往还有大量可用空间未被占用,这些空间是已经出队的队列元素曾经占用过得存储单元。
2.3 顺序队列中的溢出现象
2.3.1 “下溢”现象
- 当队列为空时,做出队运算产生的溢出现象。
- “下溢”是正常现象,常用作程序控制转移的条件。
2.3.2 “真上溢”现象
- 当队列满时,做进栈运算产生空间溢出的现象。
- “真上溢”是一种出错状态,应设法避免。
2.3.3 "假上溢"现象
- 由于入队和出队操作中,头尾指针只增加不减小,致使被删元素的空间永远无法重新利用。
- 当队列中实际的元素个数远远小于向量空间的规模时,也可能由于尾指针已超越向量空间的上界而不能做入队操作。
- 该现象称为"假上溢"现象。
三、循环队列
3.1 解决顺序队列的溢出问题
在实际使用队列时,为了使队列空间能重复使用,往往对队列的使用方法稍加改进:
- 无论插入或删除,一旦
rear指针增1或front指针增1时超出了所分配的队列空间,就让它指向这片连续空间的起始位置。 - 如果位置从
MaxSize-1增1变到0,可用取余运算rear%MaxSize和front%MaxSize来实现。 - 这实际上是把队列空间想象成一个环形空间,环形空间中的存储单元循环使用,用这种方法管理的队列也就称为循环队列。
3.2 循环队列的状态
在循环队列中,当队列为空时,有front=rear,而当所有队列空间全占满时,也有front=rear。为了区别这两种情况,规定循环队列最多只能有MaxSize-1个队列元素,当循环队列中只剩下一个空存储单元时,队列就已经满了。因此,队列判空的条件时front=rear,而队列判满的条件时front=(rear+1)%MaxSize。
四、循环队列数据结构
4.1 循环队列的结构设计
#define ERROR 0
#define OK 1
#define MAXSIZE 20
typedef int Status;/* Status是函数的类型,其值是函数结果状态代码,如OK等 */
typedef int QElemType;/* ElemType类型根据实际情况而定,这里假设为int */
typedef struct {
QElemType data[MAXSIZE]; // 数据域
int front; // 对头位置
int rear; // 队尾位置
}CircleQueue;
4.2 循环队列的初始化
Status InitCircleQueue(CircleQueue *Q){
// 由于循环队列的数据是数组,由系统开辟了空间,所以不需要自行再分配内存空间
Q->front = Q->rear = 0;
return OK;
}
4.3 空队列判定
// 2.空队列判定
int IsEmptyCircleQueue(CircleQueue Q){
if (Q.front == Q.rear) { // 只有当对头和队尾指向同一个位置的时候表示队列为空
return TRUE;
}
return FALSE;
}
4.4 获取循环队列的大小
// 3.获取循环队列的大小
Status GetSizeCircleQueue(CircleQueue Q, int *size){
//因为是循环队列 有可能队尾的位置比对头的位置小 所以这里需要对两者的结果进行处理
*size = (Q.rear - Q.front + MAXSIZE) % MAXSIZE;
return OK;
}
4.5 获取对头元素
// 4.获取对头元素 因为设计是队尾永远指向一个空数据位置,所以不处理获取队尾情况
Status GetFrontCircleQueue(CircleQueue Q, QElemType *e){
if (Q.front == Q.rear) { //空队列判断处理
return ERROR;
}
*e = Q.data[Q.front];
return OK;
}
4.6 清空队列
// 5.清空队列
Status ClearCircleQueue(CircleQueue *Q){
if (Q->front == Q->rear) {
return ERROR;
}
Q->front = Q->rear = 0;
return OK;
}
4.7 入队
// 6.入队
Status PushCircleQueue(CircleQueue *Q, QElemType e){
// 对满判定
if(Q->front == (Q->rear + 1) % MAXSIZE)return ERROR;
int p = Q->rear;
// 入队
Q->data[Q->rear] = e;
Q->rear = (p + 1) % MAXSIZE; // 防止超界处理
return OK;
}
4.8 出队
// 7.出队
Status PopCircleQueue(CircleQueue *Q, QElemType *e){
// 空队列判定
if (Q->front == Q->rear) return ERROR;
// 出队
int p = Q->front;
Q->front = (p + 1) % MAXSIZE;
// 获取出队元素
*e = Q->data[Q->front];
return OK;
}
4.9 遍历队列
// 8.遍历队列
Status TravelCircleQueue(CircleQueue Q){
if (Q.front == Q.rear) return ERROR;
int i,j;
i = Q.front;
j = Q.rear;
printf("当前队列信息:\n");
while (i != j){
printf("%d ",Q.data[i]);
i = (i+1)%MAXSIZE;
}
printf("\n");
return OK;
}
4.10 结果示例
这里为了方便演示,我把MAXSIZE的值改为了5,如下结果
五、链式队列
5.1 链式队列的结构
#define FALSE 0
#define TRUE 1
#define OK 1
#define ERROR 0
#define OK 1
typedef int Status;/* Status是函数的类型,其值是函数结果状态代码,如OK等 */
typedef int QElemType;/* ElemType类型根据实际情况而定,这里假设为int */
// 队列节点
typedef struct QueueNode{
QElemType data;
struct QueueNode *next;
}QueueNode,*QNode;
// 链式队列
typedef struct {
QNode front; // 对头节点
QNode rear; // 队尾节点
}QueueLink;
5.2 链式队列的初始化
// 链式队列的初始化
Status InitQueueLink(QueueLink *Q){
// 分配空间
Q->front = Q->rear = (QNode)malloc(sizeof(QueueNode));
if (Q->front == NULL) {
return ERROR;
}
Q->front->next = NULL;
return OK;
}
5.3 链式队列的非空判定
// 链式队列的非空判定
int IsEmptyQueueLink(QueueLink Q){
if (Q.front == Q.rear) {
return TRUE;
}
return FALSE;
}
5.4 链式队列的对头获取
// 链式队列的对头获取
Status GetFrontQueueLink(QueueLink Q, QElemType *e){
// 空队列判定
if (Q.front == Q.rear) {
return ERROR;
}
*e = Q.front->next->data;
return OK;
}
5.5 获取链式队列的大小
// 获取链式队列的大小
Status GetSizeQueueLink(QueueLink Q, int *size){
// 空队列判定
if (Q.front == Q.rear) {
return ERROR;
}
int i = 0;
QNode p = Q.front->next;
while (p) { // 如果两个指向同一个元素
p = p->next;
i++;
}
*size = i;
return OK;
}
5.6 销毁链式队列
// 销毁链式队列
Status DestroyQueueLink(QueueLink *Q){
QNode p;
p = Q->front;
while (p) { // 遍历到队尾元素了
Q->rear = p->next;
free(p);
p = Q->rear;
}
return OK;
}
5.7 置空链式队列
// 置空链式队列
Status ClearQueueLink(QueueLink *Q){
QNode p,temp;
p = Q->front->next;
Q->rear = Q->front;// 将头尾指向头一个点
Q->front->next = NULL;
while(p){
temp = p->next;
free(p);
p = temp;
}
return OK;
}
5.8 入队
// 入队
Status PushQueueLink(QueueLink *Q, QElemType e){
// 创建节点
QNode p = (QNode)malloc(sizeof(QueueNode));
if (p == NULL) return ERROR;
p->data = e;
p->next = NULL;
// 入队 队尾节点指向该节点
QNode temp = Q->rear; //原队尾节点
temp->next = p;
Q->rear = p;
return OK;
}
5.9 出队
// 出队
Status PopQueueLink(QueueLink *Q, QElemType *e){
// 非空判定
if (Q->rear == Q->front) return ERROR;
// 找到对头节点
QNode temp = Q->front->next;
// 去除对头节点的值
*e = temp->data;
// 对头节点指向下一个节点,并且释放原对头节点
Q->front->next = temp->next;
free(temp);
//若队头就是队尾,则删除后将rear指向头结点.
if(Q->rear == temp) Q->rear = Q->front;
return OK;
}
5.10 遍历
// 遍历
Status TravelQueueLink(QueueLink Q){
// 非空判定
if (Q.rear == Q.front) return ERROR;
QNode temp = Q.front->next;
printf("队列元素:\n");
while (temp) { // 遍历结束
printf("%d ",temp->data);
temp = temp->next;
}
printf("\n");
return OK;
}