队列
1.队列的定义
队列也是一种特殊的线性表,限定插入操作在线性表的一端进行,删除操作则在线性表的另一端,它具有先进先出的特点。
- 队头(front):允许删除的一端。
- 队尾(rear):允许插入的一端。
队列在实际应用中非常广泛,例如:
(1)解决由多用户(多终端)引起的资源竞争问题。在分时操作系统中,多个用户程序排成队列,分时地循环使用CPU和主机。当队头的用户给定时间片内未完成工作时,它就要放弃使用CPU,从队列中撤出,重新拍到队尾,等待下一轮的分配。
(2)解决主机与外部设备之间的速度不匹配问题。当计算机对外设进行输出时,会遇到高速主机和低速外设的矛盾。解决的方法是在内存中开辟一个缓冲区,主机每处理完一个数据,就送到缓冲区,而不需要等待外设。送到缓冲区的数据按时间顺序形成循环队列,打印机只需从缓冲区中依次取出数据打印即可。
队列的抽象数据类型描述如下。
ADT Queue
{
数据对象:D={ai|ai∈ElemSet,i=1,2,…,n, n>=0}
数据关系:R={<ai-1,ai>|ai-1,ai∈D ,i=2,…,n}
约定其中ai端为队头,an端为队尾
基础操作:
(1)初始化操作:InitQueue(&Q);
(2)销毁队列:DestroyQueue(&Q);
(3)判断队列是否为空:QueueEmpty(Q);
(4)求队列长度:QueueLength(Q);
(5)获取队头元素:GetHead(Q,&e);
(6)清空队列:ClearQueue(&Q);
(7)进队列:EnQueue(&Q,&e);
(8)出队列:DeQueue(&Q,&e);
(9)遍历队列:QueueTravers(Q);
}
2.队列的顺序存储结构
队列的顺序结构是指用一组连续的存储空间存储队列中的数据元素,这种队列称为顺序队列。
由于队列的插入操作限制在队尾,删除操作限制在队头,因此对队列的操作必须已知队头和队尾的位置。如果约定队头指向队头,队尾指针指向队尾,则会引起进队、出队以及队空操作的二义性。当队尾指针指向数组的最后一个元素时,空间“溢出”,不能再进队列。但是此前已有若干元素出队列,一部分空间是“闲置”的,称这种现象为“假溢出”。
解决“假溢出”的常用方法有两种。
(1)用一个变量记录当前队列的长度或用一个标记量区分队空和队满。
(2)通常约定队头指针指向当前的队头,队尾指针指向当前队尾的下一个元素(或队头指针指向当前队头的前一个元素,队尾指针指向当前队尾)。通过对队头和队尾指针的运算,使得存储空间能够循环使用。我们称这种顺序队列为循环队列。
假设队头指针为front,队尾指针为rear,queueSize=8,
- 队头front和队尾rear的初始值都为0。
- 空队列:front==rear为真。
- 进队列:rear=(rear+1)%queueSize。
- 出队列:front=(front+1)%queueSize。
- 队满:(rear+1)%queueSize==front为真
- 队列长度:(rear-front+queueSize)%queueSize,队列长度比循环队列的容量少1.
循环队列的数据类型定义如下:
typedef struct SqQueue
{
int front;//指向队头位置
int rear;//指向队尾的下一个元素的位置
int queueSize;//队列的容量
QElemType data[MAXLEN];//存放队列数据元素的数组
}SqQueue;
3.循环队列的基本操作实现
3.1.循环队列的初始化操作
int initSqQueue(SqQueue* LQ)
{
LQ->front = LQ->rear = 0;
LQ->queueSize = MAXLEN;
return 1;
}
3.2.循环队列的判断队空操作
if(Q.front==Q.rear)printf("NULL");
3.3.循环队列的长度
int LengthSqQueue(SqQueue Q)
{
return (Q.rear - Q.front + Q.queueSize) % Q.queueSize;
}
3.4.循环队列的获取队头元素操作
int GetHeadSqQueue(SqQueue Q, char* e)
{
if (Q.rear == Q.front)return 0;
*e = Q.data[Q.front];
return 1;
}
3.5.循环队列的进队操作
int EnSqQueue(SqQueue* LQ, char e)
{
if ((LQ->rear + 1) % LQ->queueSize == LQ->front)return 0;//判断队满
LQ->data[LQ->rear] = e;
LQ->rear = (LQ->rear + 1) % LQ->queueSize;
return 1;
}
3.6.循环队列的出栈操作
int DeSqQueue(SqQueue* LQ, char* e)
{
if (LQ->front == LQ->rear)return 0;//NULL
*e = LQ->data[LQ->front];
LQ->front = (LQ->front + 1) % LQ->queueSize;
return 1;
}
3.7.循环队列的遍历操作
void TraversSqQueue(SqQueue Q)
{
int p = Q.front;
while (p != Q.rear)
{
printf("%c ", Q.data[p]);
p = (p + 1) % Q.queueSize;
}
printf("\n");
}
4.队列的链式存结构
队列的链式存储结构是值用带头结点的单向链表存放队列中的数据元素,这种队列称为链队列。
为了方便队列的进队和出队操作,用一个头指针记录链表的头结点,头结点的直接后继结点就是队头,并用一个尾指针记录链表的尾结点(队尾)。
链队列Q中包含两个指向结点的指针变量,是一个结构体类型。链队列的数据类型定义分两步完成。
(1)链队列的结点和指向结点的指针数据类型定义
typedef struct qnode
{
QElemType data;
struct qnode* next;
}Qnode ,*QueueLink;
(2)链队列的数据类型定义
typedef struct QLink
{
QueueLink front;//指向头结点
QueueLink rear;//指向队尾
}QLink;
typedef char QElemType[20];
5.链队列的基本操作实现
5.1. 链队列的初始化操作
int InitLinkQueue(QLink* LQ)
{
LQ->front = LQ->rear = (QueueLink)malloc(sizeof(Qnode));
if (LQ->front == NULL)
{
perror("InitLinkQueue::");
return 0;
}
LQ->front->next = NULL;
return 1;
}
5.2. 链队列的判断队空操作
Q.front==Q.rear是否为真
5.3. 求链队列的长度操作
int LengthQueue(QLink Q)
{
QueueLink p;
int n = 0;
p = Q.front->next;
while (p)
{
n++;
p = p->next;
}
return 0;
}
5.4. 链队列的获取队头元素操作
int GetHeadLinkQueue(QLink Q, QElemType e)
{
if (Q.front == Q.rear)return 0;
strcpy(e, Q.front->next->data);
return 1;
}
5.5. 链队列的进队操作
就是链表的尾插
int EnLinkQueue(QLink* LQ, QElemType e)
{
QueueLink p = (QueueLink)malloc(sizeof(Qnode));
if (p == NULL)
{
perror("EnLinkQueue::");
return 0;
}
strcpy(p->data, e);
p->next = NULL;
LQ->rear->next = p;
LQ->rear = p;
return 1;
}
5.6. 链队列的出队操作
就是头删除
int DeLinkQueue(QLink* LQ, QElemType e)
{
QueueLink p;
if (LQ->front == LQ->rear)return 0;
strcpy(e, LQ->front->next->data);
p = LQ->front->next;
LQ->front->next = p->next;
if (LQ->rear == p)//原队列只有一个元素
{
LQ->rear = LQ->front;//保证空队列时的头、尾指针均指向头结点
}
free(p);
return 1;
}
5.7. 链队列的遍历操作
void TraversLinkQueue(QLink Q)
{
QueueLink p = Q.front->next;
while (p)
{
printf("%s->\n", p->data);
p = p->next;
}
printf("NULL\n");
}