书继上回:这节课我们来谈谈另一个典型代表:队列。
栈的插入和删除操作只能在线性表的一端进行,并且元素遵循“后进先出”的原则
而队列的插入和删除操作分布在两端,插入的一端是队尾,删除的一端是队首,并且元素遵循“先进先出”的原则。
与一般线性表相比,栈和队列的插入和删除操作受到了更多的约束和限制,故称作限定性线性表结构。
(前言提示,本节与上一节内容结构及其相似,代码实现部分不作过多文字阐述)
3.4队列的基本概念
队列(Queue)也是一种线性结构,是一种限定只能在表的一端进行插入,另一端进行删除的线性表。在队列中,插入元素的一端称为“队尾”(Rear),删除元素的一端称为“队首”(Front)。
先进队列的元素先出队列,因此队列又称FIFO(First In First Out)表。
我们可以将其类比为“需要排队的表”
定义:
ADT Queue{
数据对象:
数据关系:
基本操作:
InitQueue(&Q)
操作结果:创建一个空的队列Q。
DestroyQueue(&Q)
操作结果:销毁队列Q。
参数说明:队列Q已存在。
ClearQueue(&Q)
操作结果:将队列Q清空。
参数说明:队列Q已存在。
QueueEmpty(Q)
操作结果:若队列Q为空则返回TRUE;否则返回FALSE。
参数说明:队列Q已存在。
QueueLength(Q)
操作结果:返回队列Q中的元素数,亦即队列的长度。
参数说明:队列Q已存在。
GetHead(Q,&e)
操作结果:将e赋值为队列Q的队首元素。
参数说明:队列Q已存在且非空。
EnQueue(&Q,e)
操作结果:将e插入队列Q成为新的队尾元素。
参数说明:队列Q已存在。
DeQueue(&Q,&e)
操作结果:若队列非空,删除队列Q的队首元素,并将其值赋给e。
参数说明:队列Q已存在。
QueueTraverse(Q)
操作结果:从队首到队尾依次输出Q中各个数据元素。
参数说明:队列Q已存在。
}end ADT Queue
3.5队列的表示与实现
队列的实现有顺序和链式两种。
顺序队列
顺序队列一般设置两个静态指针 front(头指针)和rear(尾指针)来分别表示队首元素和队尾元素在队列中的位置。
注意这个“指针”也不是内存地址,而是一个 int 类型的值,它表示的是数组存储单元的下标。一般约定,初始空队列时front = rear =0;每当在队尾插入一个元素时 rear 加 1 ,每当在队首删除一个元素时 front 加1。这样,front 始终指向队列中的队首元素,而 rear 指针则指向队尾元素的“下一个”位置。
如果数组容量为,我们向队首插入了超过n个数(当然也会从队尾删除一些元素使实际存储数的量小于n),
的指向超出了数组的边界,而实际上这时队列空间并没有装满。为了解决这种情况,我们把顺序队列想象成一个首尾相接的循环空间,认为逻辑上
的下一个位置又从数组空间的0下标开始,可以通过取模
来实现。因此习惯上把这样的顺序队列称为循环队列。
由于插满的满状态(有n个数)以及空队列的front和rear值都相等,无法区别。
因此我们少用一个队列空间,认为存有n-1个数,队列就满了,此时队列满的标志为:
而队列空的标志为:
循环队列实现如下:
#define QUEUE_INIT_SIZE 100
typedef int Elemtype;
typedef struct {
Elemtype* elem;
int queuesize;
int front;
int rear;
}SqQueue;
1.循环队列的初始化
void InitQueue(SqQueue& Q, int msize = QUEUE_INIT_SIZE) {
Q.elem = new Elemtype[msize];
Q.queuesize = msize;
Q.front = Q.rear = 0;
}
2.循环队列的销毁操作
void DestroyQueue(SqQueue& Q) {
delete[] Q.elem;
Q.front = Q.rear = 0;
Q.queuesize = 0;
}
3.获取循环队列的长度
int Queuelength(SqQueue Q) {
return (Q.rear + Q.queuesize - Q.front) % Q.queuesize;
}
4.入队列操作
void Enqueue(SqQueue& Q, Elemtype e) {
if ((Q.rear + 1) % Q.queuesize == Q.front)Increment(Q);
//如果队列满则增加空间
Q.elem[Q.rear] = e;
Q.rear = (Q.rear + 1) % Q.queuesize;
}
Increment函数已经是我们的老朋友了,大家可以翻看前面的笔记来思考它的实现
参考代码:(之前的顺序栈内容,可以把SqStack改成SqQueue)
void ErrorMsg(const char* a) {
printf(a); //错误处理函数,我们老熟人了,再打上去一次
}
void Increment(SqStack& S) {
ElemType* a;
a = new ElemType[S.stacksize + 1];//重新创建一个数组
if (!a) { ErrorMsg("内存创建失败"); return; }
for (int i = 0; i < S.stacksize; i++) {
a[i] = S.elem[i];
}
delete[] S.elem;
S.elem = a;
S.stacksize++;//容量加一
}
5.出队列操作
bool DeQueue(SqQueue& Q, Elemtype& e) {
if (Q.front == Q.rear) return false;
e = Q.elem[Q.front];
Q.front = (Q.front + 1) % Q.queuesize;
return true;
}
其实与栈部分相同,链队列其实比顺序队列更佳
链队列
链式存储方式实现的队列称为链队列。
链队列的实现为:
LinkList是链表,其实现请查看我之前的文字:附链接:【数据结构】五分钟自测主干知识(三)
http://t.csdnimg.cn/OsA27http://t.csdnimg.cn/OsA27在这里我以后就直接用了,不作过多阐述
typedef LinkList Queueptr;
typedef struct {
Queueptr front;
Queueptr rear;
}LinkQueue;
1.链队列的初始化
void InitQueue(LinkQueue& Q) {
Q.front = Q.rear = new LNode;
Q.front->next = NULL;
}
2.链队列的销毁
void DestroyQueue(LinkQueue& Q) {
while (Q.front) {
Q.rear = Q.front->next;
delete Q.front;
Q.front = Q.rear;
}
}
3.链队列获取队首元素
bool GetHead(LinkQueue Q, ElemType& e) {
if (Q.front == Q.rear) return false;
e = Q.front->next->data;
return true;
}
4.入队列操作
void Enqueue(LinkQueue& Q, ElemType e) {
LNode*p = new LNode;
p->data = e;
p->next = NULL;
Q.rear->next = p;
Q.rear = p;
}
5.出队列操作
bool DeQueue(LinkQueue& Q, ElemType& e) {
if (Q.front == Q.rear)return false;
LNode*p = Q.front->next;
Q.front->next = p->next;
e = p->data;
if (Q.rear == p) Q.rear = Q.front;
delete p;
return true;
}
出队列操作时,如果删除的结点是队列中唯一的元素结点,那么在删除该结点之后还需要修改rear指针。可以看出,和顺序存储相比,链式存储是实现队列更好的选择!
下一讲,我们来探讨一个新的概念“串”
我做好了会把链接直接放在这里,供需要自测的读者方便自取~
【数据结构】五分钟自测主干知识(六)