在上一篇文章中我们介绍了栈的知识点及相关练习,在这一篇文章中我们将介绍队列的知识点并通过练习来巩固。
目录
队列
队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。队列中没有元素时,称为空队列。
队列的数据元素又称为队列元素。在队列中插入一个队列元素称为入队,从队列中删除一个队列元素称为出队。因为队列只允许在一端插入,在另一端删除,所以只有最早进入队列的元素才能最先从队列中删除,故队列又称为先进先出(FIFO—first in first out)线性表。
队列的数组实现
基础概念
队列是一种先进先出(FIFO)的数据结构,它可以用数组来实现。在队列的数组实现中,使用一个固定大小的数组作为存储容器,在队列的操作过程中,通过维护队头和队尾的指针来实现元素的入队和出队。
伪代码
QueueArrayInit(queueSize)
queue = new Array[queueSize] // 创建一个大小为queueSize的数组
front = -1 // 队列前端指针初始化为-1
rear = -1 // 队列后端指针初始化为-1
return queue
QueueArrayIsEmpty(queue)
if front = -1 and rear = -1 then
return true // 如果前端指针和后端指针都为-1,则队列为空
else
return false // 否则队列不为空
QueueArrayIsFull(queue, queueSize)
if rear = queueSize-1 then
return true // 如果后端指针指向了最后一个元素,则队列已满
else
return false // 否则队列未满
QueueArrayEnqueue(queue, queueSize, item)
if QueueArrayIsFull(queue, queueSize) then
return "Queue is full" // 如果队列已满,则无法入队
else if QueueArrayIsEmpty(queue) then
front = 0 // 如果队列为空,则将前端指针移到第一个位置
end if
rear = rear + 1 // 后端指针后移一位
queue[rear] = item // 将元素存入队列中
QueueArrayDequeue(queue)
if QueueArrayIsEmpty(queue) then
return "Queue is empty" // 如果队列为空,则无法出队
else if front = rear then
front = -1 // 如果队列只有一个元素,则将前端指针和后端指针重置为-1
rear = -1
else
front = front + 1 // 前端指针后移一位
end if
return queue[front] // 返回出队的元素
队列的循环数组实现
基础概念
队列的循环数组实现是对队列的数组实现的一种改进。通过使用循环数组,可以充分利用数组空间,避免浪费。当队尾指针到达数组末尾时,如果队头指针不在数组开头,可以将队尾指针循环回到数组开头,实现循环利用。
伪代码
QueueCircularInit(queueSize)
queue = new Array[queueSize] // 创建一个大小为queueSize的数组
front = -1 // 初始化前端指针为-1
rear = -1 // 初始化后端指针为-1
QueueCircularIsEmpty()
if front = -1 and rear = -1 then
return true // 如果前端指针和后端指针都为-1,则队列为空
else
return false // 否则队列不为空
QueueCircularIsFull()
if (rear + 1) % queueSize = front then
return true // 如果后端指针加1后取模等于前端指针,则队列已满
else
return false // 否则队列未满
QueueCircularEnqueue(item)
if QueueCircularIsFull() then
return "Queue is full" // 如果队列已满,则无法入队
else if QueueCircularIsEmpty() then
front = 0 // 如果队列为空,则将前端指针设置为0
end if
rear = (rear + 1) % queueSize // 后端指针后移一位,并取模queueSize
queue[rear] = item // 将元素放入队列的后端位置
QueueCircularDequeue()
if QueueCircularIsEmpty() then
return "Queue is empty" // 如果队列为空,则无法出队
else if front = rear then
front = -1 // 如果队列中只有一个元素,则出队后将前端指针重置为-1
rear = -1 // 同时将后端指针也重置为-1
else
front = (front + 1) % queueSize // 前端指针后移一位,并取模queueSize
end if
item = queue[front] // 获取出队的元素
return item
队列的链表实现
基础概念
队列的链表实现使用链表作为存储结构,在队列的操作过程中,通过维护队头和队尾节点的指针来实现元素的入队和出队。
伪代码
以下是队列的链表实现的伪代码:
QueueLinkedListInit()
head = null // 将队列的头指针指向空,表示队列为空
tail = null // 将队列的尾指针指向空,表示队列为空
QueueLinkedListIsEmpty()
return head == null // 如果头指针为null,则队列为空,返回true;否则返回false
QueueLinkedListEnqueue(item)
newNode = new Node(item) // 创建一个新的结点,存储待入队的元素
if QueueLinkedListIsEmpty() then
head = newNode // 如果队列为空,则将头指针和尾指针都指向新结点
tail = newNode
else
tail.next = newNode // 否则,将新结点添加到尾结点的后面,并将尾指针指向新结点
tail = newNode
QueueLinkedListDequeue()
if QueueLinkedListIsEmpty() then
return "Queue is empty" // 如果队列为空,则无法出队
else
item = head.item // 获取头结点的元素
head = head.next // 将头指针指向下一个结点
if head == null then
tail = null // 如果队列中只有一个元素,则出队后将尾指针也置为null
end if
return item
接下来让我们进行队列的相关练习
判断题
循环数组实现队列时,当front和rear满足什么关系时队列为空?front==rear
满足什么关系时队列为满?(rear+1)%m=front
1.所谓“循环队列”是指用单向循环链表或者循环数组表示的队列。(错)
解析:循环队列指的是用数组表示的队列,利用求余数运算使得头尾相接。循环队列本身是一种顺序存储结构,而循环链表是一种链式存储结构。
2.队列适合解决处理顺序与输入顺序相反的问题。(错)
解析:栈是后进先出的,可以很方便地处理与输入顺序相反的情况。
3.不论是入队列操作还是入栈操作,在顺序存储结构上都需要考虑"溢出"情况。 (对)
解析:对于队列,当队列已满时,再进行入队操作就会发生溢出,称为队列满的情况。对于栈,当栈已满时,再进行入栈操作就会发生溢出,称为栈满的情况。
4.循环队列也存在空间溢出的问题。(对)
5.所谓“循环队列”是指用单向循环链表或者循环数组表示的队列。(错)
解析:循环队列是数组实现的。
选择题
1.已知初始为空的队列 Q 的一端仅能进行入队操作,另外一端既能进行入队操作又能进行出队操作。若 Q 的入队序列是 1、2、3、4、5,则不能得到的出队序列是:
A.4、1、3、2、5
B.4、2、1、3、5
C.5、4、3、1、2
D.5、3、1、2、4
选A,解析:对于A,并不能找到一个数字,使之左边是递减序列、右边是递增序列。
对于B,1、3、5右边入,2、4左边入,从左边开始出队操作。
对于C,1、2右边入,3、4、5左边入,从左边开始出队操作。
对于D,1、2、4右边入,3、5左边入,从右边开始出队操作。
2.在一个不带头结点的非空链式队列中,假设f和r分别为队头和队尾指针,则插入s所指的结点运算是( )。
A.s->next=s; r=s;
B.s->next=f; f=s;
C.r->next=s; r=s;
D.f->next=s; f=s;
选C,解析:链式队列在队尾进行插入。
3.循环队列的引入,目的是为了克服( 假溢出问题 )。
解析:假溢出问题是指当队列头部指针追上队列尾部指针时,即使队列中仍有空闲位置,队列也会被错误地判断为满,导致无法继续入队操作。通过循环队列的设计,可以有效地避免这种情况,使得队列能够循环利用数组空间,提高了队列的存储效率。
4.如果循环队列用大小为m
的数组表示,且用队头指针front
和队列元素个数size
代替一般循环队列中的front
和rear
指针来表示队列的范围,那么这样的循环队列可以容纳的元素个数最多为:(m)
解析:在这种实现方式下,队列满的条件是(front + size) % m == front
,即队列中的元素个数等于数组的大小。
5.若已知一队列用单向链表表示,该单向链表的当前状态(含3个对象)是:1->2->3
,其中x->y
表示x
的下一节点是y
。此时,如果将对象4
入队,然后队列头的对象出队,则单向链表的状态是:
A.
1->2->3
B.
2->3->4
C.
4->1->2
D.
答案不唯一
选B,解析:4入队之后3指向4,同时1出队。
6.线性表、堆栈、队列的主要区别是什么?
A.堆栈和队列都不是线性结构,而线性表是
B.线性表和队列都可以用循环链表实现,但堆栈不能
C.堆栈和队列都是插入、删除受到约束的线性表,因为一个新元素的插入只能在特定的地方。
D.线性表用指针,堆栈和队列用数组
选C,解析:对于A,栈堆、队列、线性表都是线性结构。对于B,三者都能用循环链表实现。C对。对于D,三者都能用链表实现。
7.最不适合用作链队的链表是()。
A.只带队尾指针的循环单链表
B.只带队头指针的非循环双链表
C.只带队尾指针的循环双链表
D.只带队头指针的循环双链表
选B,解析:B项查找队尾时间复杂度最长为O(n),其他都只需要O(1)
8.循环顺序队列中是否可以插入下一个元素()。
A.与曾经进行过多少次插入操作有关
B.只与队尾指针的值有关,与队头指针的值无关
C.只与数组大小有关,与队首指针和队尾指针的值无关
D.与队头指针和队尾指针的值有关
选D,解析:是否可以插入下一个元素需要判断是否front==(rear+1)%n
9.循环队列存储在数组A[0,m]中,则入队时的部分操作为__C__。
A.rear=rear+1
B.rear=(rear+1)mod(m-1)
C.rear=(rear+1)mod(m+1)
D.rear=(rear+1)mod m
10.写出下列程序段的输出结果(队列中的元素类型QElem Type为char)。
void main(){
Queue Q;Init Queue(Q);
Char x=’e’;y=’c’;
EnQueue(Q,’h’);EnQueue(Q,’r’);EnQueue(Q,y);
DeQueue(Q,x);EnQueue(Q,x);
DeQueue(Q,x);EnQueue(Q,’a’);
While(!QueueEmpty(Q))
{DeQueue(Q,y);
Printf(y);
};
Printf(x);
}
首先,初始化一个空队列Q。
将字符'h'、'r'和变量y的值('c')依次入队。
执行DeQueue(Q, x),将队头元素赋值给变量x,即将'h'出队。
执行EnQueue(Q, x),将变量x的值('h')入队。
再次执行DeQueue(Q, x),将队头元素赋值给变量x,即将'r'出队。
执行EnQueue(Q, 'a'),将字符'a'入队。
进入循环,依次执行DeQueue(Q, y)并输出y的值。
最后,输出变量x的值。
所以是char
设指针变量front表示链式队列的队头指针,指针变量rear表示链式队列的队尾指针,指针变量s指向将要入队列的结点X,则入队列的操作序列为( B )。
A. front->next=s;front=s; B. s->next=rear;rear=s;
C. rear->next=s;rear=s; D. s->next=front;front=s;
进行图的广度优先搜索时,需要用到下列哪种数据结构(队列)。
函数题
R6-1 双端队列
双端队列(deque,即double-ended queue的缩写)是一种具有队列和栈性质的数据结构,即可以(也只能)在线性表的两端进行插入和删除。若以顺序存储方式实现双端队列,请编写例程实现下列操作:
Push(X,D)
:将元素X
插入到双端队列D
的头;Pop(D)
:删除双端队列D
的头元素,并返回;Inject(X,D)
:将元素X
插入到双端队列D
的尾部;Eject(D)
:删除双端队列D
的尾部元素,并返回。
函数接口定义:
bool Push( ElementType X, Deque D );
ElementType Pop( Deque D );
bool Inject( ElementType X, Deque D );
ElementType Eject( Deque D );
其中Deque
结构定义如下:
typedef int Position;
typedef struct QNode *PtrToQNode;
struct QNode {
ElementType *Data; /* 存储元素的数组 */
Position Front, Rear; /* 队列的头、尾指针 */
int MaxSize; /* 队列最大容量 */
};
typedef PtrToQNode Deque;
注意:Push
和Inject
应该在正常执行完操作后返回true,或者在出现非正常情况时返回false。当Front
和Rear
相等时队列为空,Pop
和Eject
必须返回由裁判程序定义的ERROR
。
裁判测试程序样例:
#include <stdio.h>
#include <stdlib.h>
#define ERROR -1
typedef int ElementType;
typedef enum { push, pop, inject, eject, end } Operation;
typedef enum { false, true } bool;
typedef int Position;
typedef struct QNode *PtrToQNode;
struct QNode {
ElementType *Data; /* 存储元素的数组 */
Position Front, Rear; /* 队列的头、尾指针 */
int MaxSize; /* 队列最大容量 */
};
typedef PtrToQNode Deque;
Deque CreateDeque( int MaxSize )
{ /* 注意:为区分空队列和满队列,需要多开辟一个空间 */
Deque D = (Deque)malloc(sizeof(struct QNode));
MaxSize++;
D->Data = (ElementType *)malloc(MaxSize * sizeof(ElementType));
D->Front = D->Rear = 0;
D->MaxSize = MaxSize;
return D;
}
bool Push( ElementType X, Deque D );
ElementType Pop( Deque D );
bool Inject( ElementType X, Deque D );
ElementType Eject( Deque D );
Operation GetOp(); /* 裁判实现,细节不表 */
void PrintDeque( Deque D ); /* 裁判实现,细节不表 */
int main()
{
ElementType X;
Deque D;
int N, done = 0;
scanf("%d", &N);
D = CreateDeque(N);
while (!done) {
switch(GetOp()) {
case push:
scanf("%d", &X);
if (!Push(X, D)) printf("Deque is Full!\n");
break;
case pop:
X = Pop(D);
if ( X==ERROR ) printf("Deque is Empty!\n");
else printf("%d is out\n", X);
break;
case inject:
scanf("%d", &X);
if (!Inject(X, D)) printf("Deque is Full!\n");
break;
case eject:
X = Eject(D);