队列
队列,可以说是日常生活中最常见的一种现象,队列与平时排队有着相似的特点。队列也是一种运算受限制的线性表,与栈不同的是,其是限制在两端操作的线性表
定义:
队列就跟日常排队有一样的特点:先进先出,只允许在队列的一端插入数据元素(入队),只允许在队列的另一端删除数据元素(出队),可删除的一端称为队头,可插入的一端称为队尾。
队列的修改总是按照先进先出的原则进行的,也就是说新的元素,只能添加在对尾,每次离开的元素只能从队头离开,就和排队打饭一样,不过这个队不允许中途离队,比如队列中加入元素a1,a2,a3……an,a1就是队头元素,an就是队尾元素,退出队列的顺序也只能是a1,a2……an.
队列的基本操作
- InitQueue(Q): 构造一个空队列
- EmptyQueue(Q): 判断一个队列是否为空
- LengthQueue(Q): 求队列的长度
- FrontQueue(Q,e): 获取队头元素
- EnQueue(Q,e): 入队操作
- DeQueue(Q,e): 出队操作
和栈同理,队列也可以分为顺序队列和链队列。
顺序队
顺序存储结构的类型定义
#define MAXSIZE 20 //单纯的空间大小,并不能代表队列中的元素有多少
typedef int ElemType;
typedef struct
{
ElemType data[MAXSIZE];
int front,rear; //尾指针和头指针
}SeQueue;
具体操作
#define OK 1
#define ERROR 0
typedef int Status;
//将队列初始化置为空
Status InitQueue(SeQueue *q)
{
q->front = 0;
q->rear = 0;
return OK;
}
//判断队列是否为空
int EmptyQueue(SeQueue *q)
{
if(q->front == q->rear)
return 1;
else
return 0;
}
//获取队列长度
int LengthQueue(SeQueue q)
{
return q.rear-q.front; //队尾指针减去队头获得长度
}
//入队操作
Status EnQueue(SeQueue *q,ElemType e)
{
if(q->rear == MAXSIZE) //队满
{
printf("FULL\n");
return ERROR;
}
q->data[q->rear] = e; //入队操作
q->rear++; //队尾指针向后移
return OK;
}
//获取队头元素
Status FrontQueue(SeQueue *q,ElemType *e)
{
if(EmptyQueue(q)) //队空
{
printf("Empty\n");
return ERROR;
}
*e = q->data[q->front];
return OK;
}
//出队操作
Status DeQueue(SeQueue *q,ElemType *e)
{
if(EmptyQueue(q))
{
printf("Empty\n");
return ERROR;
}
*e = q->data[q->front]; //出队操作
q->front++;
return OK;
}
缺点:
由于出队操作和入队操作中,头尾指针只会增加,所以会导致被删除元素无法被重新利用,所以有一种情况,当队列中元素个数少于最大空间时,但也会因为尾指针超过空间上限而不能做入队操作。所以这种队列还是很弱的,但是如果表示成循环队列就能克服这种情况了
循环队列
(脑补长成一条的队列被拗成环的样子)当队尾指针移动到空间的上限位置时,因为这是一个环,所以队尾指针还可以继续移动下去进入到数组最小下标的位置(用取模来实现),这样就可以最大限度的利用存储空间了。
还有一个要解决的问题就是如何判断队满,有两种解决方案:
- 因为是环所以当头尾指针相等就是队满的情况了,但是一开始的
时候如果头尾指针相等就是队空了,所以可以多开设一个一个变量
来记录队空还是队满 - 还有一个比较简便的方法就是空出一格来,这样队满和队空就可
以被区分了(下面有这种来处理队满情况)
循环队列的具体操作:
循环队列也是用的顺序存储结构,所以类型定义和顺序队是基本一样的,而在部分运算操作上就有些不同了。
//队列长度
int LengthQueue(SeQueue q)
{
int len = (q.rear-q.front+MAXSIZE)%MAXSIZE;
return len;
}
//入队操作
Status EnQueue(SeQueue *q,ElemType e)
{
if((q->rear+1)%MAXSIZE == q->front)//牺牲多一个空间来判断队满的情况
return ERROR;
q->data[rear] = e; //入队
q->rear = (q->rear+1)%MAXSIZE; //尾指针加一并保证循环状态
return OK;
}
//出队操作
Status DeQueue(SeQueue *q,ElemType *e)
{
if((q->rear+1)%MAXSIZE == q->front)
return ERROR;
*e = q->data[q->front]; //出队
q->front = (q->front+1)%MAXSIZE;//头指针加一并保证循环意义
}
链队
顾名思义,用链表来表示的队列被称为链队。链队的结点结构和单链表是一样的,只不过队列的插入和删除操作是分别在队尾和队头进行的。因此除了设置头指针外还要设置尾指针在指向队尾,为了方便,这里采用有头结点的单链表来表示队列
队列的链式存储结构类型定义
typedef int ElemType; //数据类型
typedef struct node
{
ElemType data; //数据域
struct node *next; //指针域
}QueueNode;
typedef struct
{
QueueNode *front; //头指针
QueueNode *rear; //尾指针
}LinkQueue;
链队运算的具体操作
//初始化队列置为空
Status InitQueue(LinkQueue *q)
{
QueueNode *head;
head = (QueueNode*)malloc(sizeof(QueueNode));//为头结点开辟存储空间
if(!head) //空间申请失败
return ERROR;
head->next = NULL; //头结点指针域置为空
q->front = head; //头指针指向头结点
q->rear = head; //尾指针指向头结点
return OK
}
//判断队列是否为空
Status EmptyQueue(LinkQueue *q)
{
return q->rear == q->front;
}
//获取队头元素
Status FrontQueue(LinkQueue *q.ElemType *e)
{
if(EmptyQueue(q)) //队列为空
return ERROR;
*e = q->front->data;
return OK;
}
//元素入队
Status EnQueue(LinkQueue *q,ElemType e)
{
QueueNode *s;
s = (QueueNode*)malloc(sizeof(QueueNode)); //为新结点申请空间
if(!s) //申请空间失败
return ERROR;
s->data = e; //新结点数据域赋值
s->next = NULL; //新结点指针域置为空
q->next->next = s; //新结点插入到队尾
q->rear = s; //更新尾结点
return OK;
}
//元素出队
Status DeQueue(LinkQueue *q,ElemType *e)
{
if(EmptyQueue(q)) //队空
return ERROR;
QueueNode *s;
s = q->front->next; //s指向队头元素
q->front->next = s->next; //将s指向的元素移出队列
if(q->rear = s) //如果移出的元素是最后一个
q->rear = q->front; //队尾指针指向头结点
*e = s->data; //用e返回移出的元素值
free(s);
return OK;
}