队列(Queue) 简称队,也是一种操作受限的线性表,只允许在表的一端进行插入,而在表的另一端进行删除。向队列中插入元素称为入队或进队。删除元素称为出队或离队。这和我们日常生活中的排队是一致的,最早排队的也是最早离队的,其操
作的特性是先进先出(First In First Out, FIFO),如:
队头(Front): 允许删除的一-端,又称队首。
队尾(Rear)。: 允许插入的一-端。
空队列: 不含任何元素的空表。
InitQueue(&Q): 初始化队列,构造-一个空队列 Q.
QueueEmpty(Q): 判队列空,若队列Q为空返回true,否则返回false.
EnQueue(&Q,x): 入队,若队列Q未满,将x加入,使之成为新的队尾。
DeQueue (&Q, &x): 出队,若队列e非空,删除队头元素,并用x返回。
GetHead(Q,&x): 读队头元素,若队列Q非空,则将队头元素赋值给x。
注意: 栈和队列是操作受限的线性表,因此不是任何对线性表的操作都可以作为栈 和队列的操作。比如,不可以随便读取栈或队列中间的某个数据。
队列的顺序实现是指分配一块连续的存储单元存放队列中的元素,并附设两个指针:队头指针front 指向队头元素,队尾指针rear 指向队尾元素的下一个位置 (也可以让rear指向队尾元素、front 指向队头元素) 。
顺序结构可描述为:
typedef struct SqQueue
{
ElemType data[MaxSize]; //存放队列元素
int front; //队头指针
int rear; //队尾指针
}SqQueue;
初始状态(队空条件):Q.front==Q.rear==0
进队操作:队不满时,先送值到队尾元素,再将队尾指针加1。
出队操作:队不空时,先取队头元素值,再将队头指针加1。
出队a1、a2,则front指针指向下标为2的位置,rear 不变,如左图所示,再入队a5,此时front 指针不变,rear 指针移动到数组之外。嗯?数组之外,那么就会产生数组越界的错误,可实际上,我们的队列在下标为0和1的地方还是空闲的。我们把这种现象叫做 “假溢出”。如右图所示。
为了解决假溢出的问题,引入了循环队列,使其头尾相连。我们把队列的这种头尾相接的顺序存储结构称为循环队列。
当队首指针Q. front=MaxSize-1后,.再前进一个位置就自动到0,这可以利用 除法取余运算(%) 来实现。.
初始时: Q.front=Q.rear=0
。
队首指针进 1: Q.front= (Q.front+1) % MaxSize
。
队尾指针进 1: Q.rear= (Q.rear+1) % MaxSize
。
队列长度: (Q. rear +MaxSize-Q. front) % MaxSize
。
出队入队时:指针都按顺时针方向进1 (如图所示)
循环队列队空和队满的判断条件是什么呢?显然,队空的条件是Q.front==Q.rear
若入队元素的速度快于出队元素的速度,则队尾指针很快就会赶上队首指针,如图(d1)所示,此时可以看出队满时也有Q.front==Q.rear
。
为了区分队空还是队满的情况,有三种处理方式:
(Q.rear+1) % MaxSize==Q.front
Q.size==0
;队满的条件为#include
#include
#define MaxSize 20
typedef int ElemType;
typedef struct SqQueue
{
ElemType *data; //存放队列元素
int front; //队头指针
int rear; //队尾指针
}SqQueue;
void InitQueue(SqQueue *Q); //初始化队列
bool isEmpty(SqQueue Q); //判断队列是否为空
bool isFull(SqQueue Q); //判断队列是否已满
bool EnQueue(SqQueue *Q,ElemType e); //入队
bool DeQueue(SqQueue *Q,ElemType *e); //出队
void PrintQueue(SqQueue pQ);
int main()
{
SqQueue Q;
ElemType e;
InitQueue(&Q);
EnQueue(&Q,1);
EnQueue(&Q,2);
EnQueue(&Q,3);
EnQueue(&Q,4);
EnQueue(&Q,5);
EnQueue(&Q,6);
EnQueue(&Q,7);
PrintQueue(Q);
if(DeQueue(&Q,&e))
printf("出队成功,出队元素为:%d\n",e);
else
printf("出队失败\n");
PrintQueue(Q);
return 0;
}
void InitQueue(SqQueue *Q)
{
Q->data = (ElemType *)malloc(sizeof(ElemType)* MaxSize);
Q->front = Q->rear = 0;
}
bool isEmpty(SqQueue Q)
{
if(Q.rear == Q.front)
return true;
else
return false;
}
bool isFull(SqQueue Q)
{
if((Q.rear + 1) % MaxSize == Q.front)
return true;
else
return false;
}
bool EnQueue(SqQueue *Q,ElemType e)
{
if((Q->rear + 1) % MaxSize == Q->front)
return false; //队满报错
Q->data[Q->rear] = e;
Q->rear = (Q->rear +1) % MaxSize; //队尾指针加1取模
return true;
}
bool DeQueue(SqQueue *Q,ElemType *e)
{
if(Q->rear == Q->front)
return false; //队空报错
*e = Q->data[Q->front];
Q->front = (Q->front +1) % MaxSize; //队头指针加1取模
return true;
}
void PrintQueue(SqQueue pQ)
{
int i = pQ.front;
while(i != pQ.rear)
{
printf("%d ",pQ.data[i]);
i = (i+1) % MaxSize;
}
printf("\n");
}
队列的链式表示称为链队列,它实际上是一个同时带有队头指针和队尾指针的单链表。头指
针指向队头结点,尾指针指向队尾结点,即单链表的最后一个结点
队列的链式存储如图:
可描述为:
typedef struct QNode /* 结点结构 */
{
ElemType data;
struct QNode *next;
}QNode,*QueuePtr;
typedef struct /* 队列的链表结构 */
{
QueuePtr front,rear; /* 队头、队尾指针 */
}LinkQueue;
当Q. front==NULL
且Q.rear==NULL
时,链式队列为空。
出队时,首先判断队是否为空,若不空,则取出队头元素,将其从链表中摘除,并让Q. front
指向下一个结点(若该结点为最后一个结点,则置Q. front和Q. rear都为NULL)。入队时,
建立一个新结点,将新结点插入到链表的尾部,并改让Q. rear指向这个新插入的结点(若原队列为空队,则令Q. front也指向该结点)。
不带头结点的链式队列在操作上往往比较麻烦,因此通常将链式队列设计成一个带头结点的单链表,这样插入和删除操作就统一了。
使用场景: 用单链表表示的链式队列特别适合于数据元素变动比较大的情形,而且不存在队列满且产生溢出的问题。另外,假如程序中要使用多个队列,与多个栈的情形一样,最好使用链式
队列,这样就不会出现存储分配不合理和“溢出”的问题。
void InitQueue(LinkQueue *Q)
{
Q->front=Q->rear=(QueuePtr)malloc(sizeof(QNode));
Q->front->next=NULL;
}
bool IsEmpty(LinkQueue Q)
{
if(Q.front==Q.rear)
return true;
else
return false;
}
入队操作时,其实就是在链表尾部插入结点,如图所示:
其代码如下:
bool EnQueue(LinkQueue *Q,ElemType e)
{
QueuePtr s=(QueuePtr)malloc(sizeof(QNode));
s->data=e;
s->next=NULL;
Q->rear->next=s; /* 把拥有元素e的新结点s赋值给原队尾结点的后继,见图中① */
Q->rear=s; /* 把当前的s设置为队尾结点,rear指向s,见图中② */
return true;
}
出队操作时,就是头结点的后继结点出队,将头结点的后继改为它后面的结点,若链表除头结点外只剩一个元素时,则需将rear 指向头结点,如图所示:
其代码如下:
bool DeQueue(LinkQueue *Q,ElemType *e)
{
QueuePtr p;
if(Q->front==Q->rear)
return false;
p=Q->front->next; /* 将欲删除的队头结点暂存给p,见图中① */
*e=p->data; /* 将欲删除的队头结点的值赋值给e */
Q->front->next=p->next;/* 将原队头结点的后继p->next赋值给头结点后继,见图中② */
if(Q->rear==p) /* 若队头就是队尾,则删除后将rear指向头结点,见图中③ */
Q->rear=Q->front;
free(p);
return true;
}
#include
#include
#define MaxSize 10;
typedef int ElemType;
typedef struct QNode /* 结点结构 */
{
ElemType data;
struct QNode *next;
}QNode,*QueuePtr;
typedef struct /* 队列的链表结构 */
{
QueuePtr front,rear; /* 队头、队尾指针 */
}LinkQueue;
/* 构造一个空队列Q */
void InitQueue(LinkQueue *Q)
{
Q->front=Q->rear=(QueuePtr)malloc(sizeof(QNode));
Q->front->next=NULL;
}
bool IsEmpty(LinkQueue Q)
{
if(Q.front==Q.rear)
return true;
else
return false;
}
/* 插入元素e为Q的新的队尾元素 */
bool EnQueue(LinkQueue *Q,ElemType e)
{
QueuePtr s=(QueuePtr)malloc(sizeof(QNode));
s->data=e;
s->next=NULL;
Q->rear->next=s; /* 把拥有元素e的新结点s赋值给原队尾结点的后继,见图中① */
Q->rear=s; /* 把当前的s设置为队尾结点,rear指向s,见图中② */
return true;
}
/* 若队列不空,删除Q的队头元素,用e返回其值,并返回OK,否则返回ERROR */
bool DeQueue(LinkQueue *Q,ElemType *e)
{
QueuePtr p;
if(Q->front==Q->rear)
return false;
p=Q->front->next; /* 将欲删除的队头结点暂存给p,见图中① */
*e=p->data; /* 将欲删除的队头结点的值赋值给e */
Q->front->next=p->next;/* 将原队头结点的后继p->next赋值给头结点后继,见图中② */
if(Q->rear==p) /* 若队头就是队尾,则删除后将rear指向头结点,见图中③ */
Q->rear=Q->front;
free(p);
return true;
}
void PrintQueue(LinkQueue Q)
{
QueuePtr p;
p=Q.front->next;
while(p)
{
printf("%d ",p->data);
p=p->next;
}
printf("\n");
}
int main()
{
LinkQueue Q;
ElemType e;
InitQueue(&Q);
EnQueue(&Q,1);
EnQueue(&Q,2);
EnQueue(&Q,3);
EnQueue(&Q,4);
EnQueue(&Q,5);
EnQueue(&Q,6);
EnQueue(&Q,7);
PrintQueue(Q);
if(DeQueue(&Q,&e))
printf("出队成功,出队元素为:%d\n",e);
else
printf("出队失败\n");
PrintQueue(Q);
return 0;
}