运算受限的线性表---队列
1.队列的概念
和栈一样,队列(Queue)也是一种运算受限的线性表。它只允许在表的一端进行插入,而在另一端进行删除。允许删除的一端称为队头(front),允许插入的一端称为队尾(rear)。
队列的数据元素又称为队列元素。在队列中插入一个队列元素称为入队,从队列中删除一个队列元素称为出队。因为队列只允许在一端插入,在另一端删除,所以只有最早进入队列的元素才能最先从队列中删除,故队列又称为先进先出(FIFO—first in first out)线性表。
当队列中没有元素时称为空队列。在空队列中依次加入元素a1,a2,…an之后,a1是队头元素,an是队尾元素。显然退出队列的次序也只能是a1,a2,…an,也就是说队列的修改是依先进先出的原则进行的。
2.顺序队列
2.1顺序队列的概念
顺序队列实际上是运算受限的顺序表,和顺序表一样,顺序队列也是必须用一个向量空间来存放当前队列中的元素。
队空:q.front = q.rear
队满:q.rear - q.front = maxsize(队长)
入队:新元素按 rear 指示位置加入,再将队尾指针加一 ,即 q.rear = q.rear + 1
出队:将 q.front 指示的元素取出,再将队头指针加一,即 q.front = q.front + 1。
2.2非循环队列
建立顺序队列结构必须为其静态分配或动态申请一片连续的存储空间,每次在队尾插入一个元素是,q.rear增1;每次在队头删除一个元素时,q.front增1。随着插入和删除操作的进行,队列元素的个数不断变化,队列所占的存储空间也在为队列结构所分配的连续空间中移动。
当 q.front=q.rear时,队列中没有任何元素,称为空队列。当rear增加到指向分配的连续空间之外时,队列无法再插入新元素,但这时往往还有大量可用空间未被占用,这些空间是已经出队的队列元素曾经占用过得存储单元(例如 d 图中的 0 1 2 位置)。
非循环队列中的溢出现象
- "下溢"现象:当队列为空时,做出队运算产生的溢出现象。“下溢”是正常现象,常用作程序控制转移的条件。
- "真上溢"现象:当队列满时,做进栈运算产生空间溢出的现象。“真上溢”是一种出错状态,应设法避免。
- "假上溢"现象:由于入队和出队操作中,头尾指针只增加不减小,致使被删元素的空间永远无法重新利用。当队列中实际的元素个数远远小于向量空间的规模时,也可能由于尾指针已超越向量空间的上界而不能做入队操作。该现象称为"假上溢"现象。
2.3循环队列
为了充分利用向量空间,克服上述假上溢现象,可以将向量空间想象为一个首尾相接的圆环,并称这种向量为循环向量,存储在其中的队列称为循环队列(Circular Queue)。在循环队列中进行出队、入队操作时,头尾指针仍要加1,朝前移动。只不过当头尾指针指向向量上界(QueueSize-1)时,其加 1 操作的结果是指向向量的下界 0 。
显然,因为循环队列元素的空间可以被利用,除非向量空间真的被队列元素全部占用,否则不会上溢。因此,除一些简单的应用外,真正实用的顺序队列是循环队列。
循环队列的空满判别
如图,由于入队时尾指针向前追赶头指针,出队时头指针向前追赶尾指针,故队空和队满时头尾指针均相等。因此,我们无法通过 front=rear 来判断队列“空”还是“满”。
解决此问题的方法至少有两种:
-
1.另设一个布尔变量以区别队列的空和满;
-
2.少用一个元素的空间,约定入队前,测试尾指针在循环意义下加1后是否等于头指针,若相等则认为队满(注意:rear所指的单元始终为空)。
队空:q.front=q.rear; 队满:(q.rear+1)%M=q.front 。 -
队空:q.front = q.rear
队满: q.front =(q.rear + 1) % maxsize
入队: q.rear = (q.rear + 1) % maxsize
出队: q.front = (q.front + 1) % maxsize
求队长:(q.rear - q.front+maxsize)%maxsize
循环队列的基本运算
// 队列的顺序存储结构(循环队列)
#define MAX_QSIZE 5 // 最大队列长度+1
typedef struct {
int *base; // 初始化的动态分配存储空间
int front; // 头指针,若队列不空,指向队列头元素
int rear; // 尾指针,若队列不空,指向队列尾元素的下一个位置
} SqQueue;
// 构造一个空队列Q
SqQueue* Q_Init() {
SqQueue *Q = (SqQueue*)malloc(sizeof(SqQueue));
// 存储分配失败
if (!Q){
exit(OVERFLOW);
}
Q->base = (int *)malloc(MAX_QSIZE * sizeof(int));
// 存储分配失败
if (!Q->base){
exit(OVERFLOW);
}
Q->front = Q->rear = 0;
return Q;
}
// 销毁队列Q,Q不再存在
void Q_Destroy(SqQueue *Q) {
if (Q->base)
free(Q->base);
Q->base = NULL;
Q->front = Q->rear = 0;
free(Q);
}
// 将Q清为空队列
void Q_Clear(SqQueue *Q) {
Q->front = Q->rear = 0;
}
// 若队列Q为空队列,则返回1;否则返回-1
int Q_Empty(SqQueue Q) {
if (Q.front == Q.rear) // 队列空的标志
return 1;
else
return -1;
}
// 返回Q的元素个数,即队列的长度
int Q_Length(SqQueue Q) {
return (Q.rear - Q.front + MAX_QSIZE) % MAX_QSIZE;
}
// 若队列不空,则用e返回Q的队头元素,并返回OK;否则返回ERROR
int Q_GetHead(SqQueue Q, int &e) {
if (Q.front == Q.rear) // 队列空
return -1;
e = Q.base[Q.front];
return 1;
}
// 插入元素e为Q的新的队尾元素
int Q_Put(SqQueue *Q, int e) {
if ((Q->rear + 1) % MAX_QSIZE == Q->front) // 队列满
return -1;
Q->base[Q->rear] = e;
Q->rear = (Q->rear + 1) % MAX_QSIZE;
return 1;
}
// 若队列不空,则删除Q的队头元素,用e返回其值,并返回1;否则返回-1
int Q_Poll(SqQueue *Q, int &e) {
if (Q->front == Q->rear) // 队列空
return -1;
e = Q->base[Q->front];
Q->front = (Q->front + 1) % MAX_QSIZE;
return 1;
}
/*!!!!!!via 百度百科!!!!!!!*/
3.链队列
3.1 链队列的概念
队列的链式存储结构简称为链队列,它是限制仅在表头删除和表尾插入的单链表。显然仅有单链表的头指针不便于在表尾做插入操作,为此再增加一个尾指针,指向链表的最后一个结点。于是,一个链队列由头指针和尾指针唯一确定。
3.2链队列的基本运算
将链队列的类型LinkQueue定义为一个结构类型
//将链队列的类型LinkQueue定义为一个结构类型:
typedef struct queuenode
{
datatype data;
struct queuenode *next;
}queuenode;
typedef struct
{
queuenode *front;
queuenode *rear;
}linkqueue;
构造一个空队列
void initqueue(linkqueue q)
{
q.front->next=q.rear->next=NULL;
}
判断是否为空
int queueempty(linkqueue q)
{
return (q.front->next==NULL &&q.rear->next==NULL);
}
入队操作
void enqueue(linkqueue q,datatype x)
{
queuenode *p;
p=(queuenode * )malloc(sizeof(queuenode));
p->data=x;
p->next=NULL;
if(queueempty(q))
q.front->next=q.rear=p;
else{
q.rear->next=p;
q.rear=p;
}
}
出队操作
void dequeue(linkqueue q)
{
datatype x;
queuenode *p;
if(queueempty(q))printf("queue underflow");
p=q.front->next;
x=p->data;
q.front->next=p->next;
if(q.rear ==p)q.rear=q.front;
free(p);
return x;
}
文章内容来源于博主老师传授、自身理解以及网络收集