【数据结构入门精讲 | 第六篇】队列知识点及考研408、企业面试练习

本文深入讲解了队列的基础概念、数组和循环数组、链表三种实现方式,并提供了丰富的编程和面试练习,包括判断题、选择题、函数题,涉及双端队列、列车调度、银行排队等问题,帮助读者巩固队列知识并提升解决实际问题的能力。
摘要由CSDN通过智能技术生成

在上一篇文章中我们介绍了栈的知识点及相关练习,在这一篇文章中我们将介绍队列的知识点并通过练习来巩固。

在这里插入图片描述

队列

队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(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代替一般循环队列中的frontrear指针来表示队列的范围,那么这样的循环队列可以容纳的元素个数最多为:(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; 

注意:PushInject应该在正常执行完操作后返回true,或者在出现非正常情况时返回false。当FrontRear相等时队列为空,PopEject必须返回由裁判程序定义的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);
      
  • 25
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

秋说

感谢打赏,祝你平安喜乐。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值