队列相关习题及详解(选择题和综合题) ——数据结构

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_22073849/article/details/78362502

队列的基本概念

队列的定义

队列(Queue):队列简称队,也是一种操作受限的线性表,只允许在表的一端进行插入,而在表的另一端进行删除。向队列中插入元素称为入队或进队;删除元素称为出队或离队。这和我们日常生活中的排队是一致的,最早排队的也是最早离队的。其操作的特性是先进先出(First In First Out, FIFO),故又称为先进先出的线性表。

队头(Front):允许删除的一端,又称为首队
队尾(Rear):允许插入的一端
空队列:不含任何元素的空表

队列常见的基本操作

InitQueue(&Q):初始化队列,构造一个空队列
QueueEmpty(Q):判断队列空,若队列Q为空返回true,否则返回false
EnQueue(&Q,x):入队,若队列Q未满,将x加入,使之成为新的队尾
DeQueue(&Q,&x):出队,若队列非空,删除队头元素,并用x返回
GetHead(Q,&x):读队头元素,若队列Q非空,则将队头元素赋值给x

需要注意的是,队列是操作受限的线性表,所以,不是任何对线性表的操作都可以作为队列的操作。比如,不可以随便读取队列中间的某个数据。

队列的顺序存储结构

队列的顺序存储

队列的顺序实现是指分配一块连续的存储单元存放队列中的元素,并附设两个指针front rear分别指示队头元素和队尾元素的位置。设队头指针指向队头元素,队尾指针指向队尾元素的下一个位置

队列的顺序存储类型可描述为:

#define MaxSize 50
typedef struct{
    ElemType data[MaxSize];
    int front,rear;
} SqQueue;

在队列的初始状态时,有Q.front==Q.rear==0成立,该条件可以作为判断空的条件。但能否用Q.rear==MaxSize作为队列满的条件呢?显然不能,因为有些情况队列中只有一个元素,但仍满足该条件。这时入队出现“上溢出”,但这种溢出并不是真正的溢出,在data数组中依然存在可以存放元素的空位置,所以是一种“假溢出”。

循环队列

前面已经指出了顺序队列的缺点,这里我们引出循环队列的概念。将顺序队列臆造为一个环状的空间,即把存储队列元素的表从逻辑上看成一个环,称为循环队列当队首指针Q.front=MaxSize-1后,再前进一个位置就可以自动到0,这可以利用除法取余运算(%)来实现

初始时:Q.front=Q.rear=0;
队首指针进1:Q.front=(Q.front+1)%MaxSize
队尾指针进1:Q.rear=(Q.rear+1)%MaxSize
队列长度:(Q.rear+MaxSize-Q.front)%MaxSize

为了区分队空还是队满的情况,有三种处理方式

1)牺牲一个单元来区分队空和堆满,入队时少用一个队列单元,这是一种较为普遍的做法,约定以“队头指针在队尾指针的下一个位置作为队满的标识”
队满条件为:(Q.rear+1)%MaxSize==Q.front
队空条件为:Q.front==Q.rear
队列中元素的个数:(Q.rear=Q.front+MaxSize)%MaxSize

2)类型中增设表示元素个数的数据成员。这样,则队空的条件为Q.size==0;队满的条件为Q.size=MaxSize.这两种情况都有Q.front==Q.rear

3)类型中增设tag数据成员,以区分堆满还是队空。tag等于0的情况下,若因删除导致Q.front=Q.rear则队为空;tag等于1的情况下,若因插入导致Q.front=Q.rear则为队满。

循环队列的操作

1)初始化

void InitQueue(&Q){
    Q.rear=Q.front=0;           //初始化队首、队尾指针
}

2)判队空



bool isEmpyt(Q){
    if(Q.rear==Q.front) return true;    //队空条件
    else return false;
}

3)入队


bool EnQueue(SqQueue &Q,ElemType x){
    if((Q.rear+1)%MaxSize==Q.front) return false;   //队满
    Q.data[Q.rear]=x;
    Q.rear=(Q.rear+1)%MaxSize;                  //队尾指针加1取模
    return true;
}

4)出队




bool DeQueue(SqQueue &Q,ElemType &x){
    if(Q.rear==Q.front) return false;           //队空,报错
    x=Q.data[Q.front];
    Q.front=(Q.front+1)%MaxSize;                //队头指针加1取模
    return true;
}

队列的链式存储结构

队列的链式存储

队列的链式表示称为链队列,它实际上是一个同事带有队头指针和队尾指针的单链表。头指针向队头结点,尾指针指向尾结点,即单链表的最后一个结点(注意与顺序存储的不同)。队列的链式存储如图所示

这里写图片描述

栈的链式存储类型可以描述为

typedef struct          //链式队列结点
{
    ElemType data;
    struct LinkNode *next;
}LinkNode;

typedef struct          //链式队列
{
    LinkNode *front,*rear;      //队列的队头和队尾指针

}LinkQueue;

当Q.front==NULL且Q.rear=NULL时,链式队列为空

出队时,首先判断队是否为空,若不空,则去除队头元素,将其从链表中摘除,并让Q.front指向下一个结点(若该结点为最后一个结点,则置Q.front和Q.rear都为NULL)入队时,建一个新结点,将新结点插入到链表的尾部,并改让Q.rear指向这个心插入的结点(若原队列为空队,则令Q.front也指向该结点)

不难看出,不设头结点的链式队列在操作上往往比较麻烦,因此,通常将链式队列设计成一个带头结点的单链表,这样插入和删除操作就统一了。

用单链表表示的链式队列特别适合于数据元素变动比较大的情形,而且不存在队列满且产生溢出的问题。另外加入程序中要使用多个队列,与多个栈的情形一样,最好使用链式队列,这样就不会出现存储分配不合理和“溢出”的问题。

链式队列的基本操作

1)初始化

void InitQueue(LinkQueue &Q){
    Q.front=Q.rear=(LinkNode*)malloc(sizeof(LinkNode));//建立头结点
    Q.front->next=NULL;             //初始为空
}

2)判队空

bool IsEmpty(LinkQueue Q){
    if(Q.front==Q.rear) return true;
    else return false;
}

3)入队


void EnQueue(LinkQueue &Q,ElemType x){
    s=(LinkNode *)malloc(sizeof(LinkNode));
    s->data=x;s->next=NULL; //创建新结点,插入到链尾
    Q.rear->next=s;
    Q.rear=s;
}

4)出队



boole DeQueue(LinkQueue &Q,ElemType &x){
    if(Q.front==Q.rear)     return false;//空队
    p=Q.front->next;
    x=p->data;
    Q.front->next=p->next;
    if(Q.rear==p)
        Q.rear=Q.front;     //若原队列中只有一个结点
    free(p);
}

习题部分

选择题

第一题

栈和队列的主要区别在于()
A. 它们的逻辑结构不一样 B. 它们的存储结构不一样 C. 所包含的元素不一样 D. 插入、删除操作的限定不一样

第二题

循环队列存储在数组A[0…n],则入队时的操作为()
A. rear=rear+1 B. rear=(rear+1)mod(n-1) C. rear=(rear+1)modn D. rear=(rear+1)mod(n+1)

第三题

若用数组A[0..5]来实现循环队列,且当前rear和front的值分别为1和5,当从队列中删除一个元素,再加入两个元素后,rear和front的值分别为()
A. 3和4 B. 3和0 C. 5和0 D. 5和1

第四题

已知循环队列存储在一维数组A[0…n-1],且队列非空时,front和rear分别指向队头元素和队尾元素。若初始时队列为空,且要求第一个进入队列的元素存储在A[0]处,则初始时front和rear的值分别是()

A. 0,0 B. 0,n-1 C. n-1,0 D. n-1,n-1

第五题

循环队列放在以为数组A[0…M-1]中,end1指向队头元素,end2指向队尾元素的后一个位置。假设队列两段均可进行入队和出队操作,队列中最多能容纳M-1个元素。初始时为空。下列判断队空和队满的条件中,正确的是()

A. 6 B. 4 C. 3 D. 2

第六题

最适合用作链队的链表是()
A. 队空:end1==en2; 队满:end1==(edn2+1)mod M
B. 队空:end1==en2; 队满:end1==(edn1+1)mod (M-1)
C. 队空:end2==(edn1+1)mod M 队满:end1==(edn2+1)mod M
D. 队空:end1==(edn2+1)mod M 队满:end1==(edn1+1)mod (M-1)

第七题

最不适合用作链式队列的链表是()
A. 只带队首指针的非循环双链表 B. 只带队首指针的循环双链表
C. 只带队尾指针的循环双链表 D. 只带队尾指针的循环单链表


解答部分

第一题

栈和队列的逻辑结构都是线性表,它们的存储结构可能是顺序的也可能是链式的,但不能说是它们的主要区别,C的道理也是一样,只有D才是栈和队列的本质区别。不管是顺序存储还是链式存储,栈和队列都只嗯呢该顺序存取,而向量数组是直接(随机)存取

第二题

这道题需要注意的是,由于数组的下标范围是0-n,所以数组的容量为n+1
循环队列中新元素入队的操作是rear=(rear+1)MOD maxsize,本题中maxsize=n+1。因此入队操作应为rear=(rear+1)MOD(n+1)

第三题

循环队列中,每删除一个元素,队首指针:front=(front+1)%6,每插入一个元素,队尾指针:rear=(rear+1)%6。上述操作后,front=0,rear=3

第四题

根据题意,第一个元素进入队列后存储在A[0]处,此时front和rear值都为0。入队时由于要执行(rear+1)%n ,所以如果入队后指针都指向0,则rear初值为n-1,而由于第一个元素在A[0]中,插入操作只改变rear指针,所以front为0不变。

第五题

end1指向队头元素,可知出队的操作是先从A[edn1]读数,然后end1再+1。end2指向对尾元素的后一个位置,可知入队操作是先存数到A[end2],然后end2再加1。若把A[0]存储第一个元素,当队列初始时,入队操作是把数据放到A[0],然后end2自增,即可知end2初值为0;
而end1指向的是队头元素,队头元素的在数组A中的下标为0,所以得知end1初值也为0,可知队空条件为end1==end2;

然后考虑队列满时,因为队列最多能容纳M-1个元素,假设队列存储在下标为0到下标为M-2的M-1个区域,队头为A[0],队尾为A[M-2],此时队列满,考虑在这种情况下end1和end2的状态,end1指向队头元素,可知end1=0,end2指向对尾元素的后一个位置,可知end2=M-2+1=M-1,所以可知队满的条件为end1==(end2+1)mod M, 选A

最好还是画图看着题

第六题

由于对了需在双端进行操作,选项C和D的链表显然不太适合链队。选项A的链表在完成进队和出队后还要修改为循环的,对于队列来讲是多余的。对于选项B,由于有首指针,适合删除首结点;由于有尾指针,适合在其后面插入结点,故选B

第七题

由于非循环双链表只带队首指针,可在执行入队操作时需要修改队尾结点的指针域,而查找队尾结点需要O(n)的时间。BCD均可在O(1)的时间内找到队首和队尾。

综合题

习题部分

第一题

如果希望循环队列中的元素都能得到利用,则需设置一个标志域tag,并以tag的值为0或1来区分头指针front和队尾指针rear相同时的队列状态是“空”还是“满”,试编写与此结构相应的入队和出队算法

第二题

Q是一个队列,S是一个空栈,实现将队列中的元素逆置的算法

第三题

利用两个栈S1/ S2模拟一个队列,已知栈的4个运算定义如下:

Push(S,x);          //元素x入栈S
Pop(S,x);           //S出栈并将出栈的值赋给x
StackEmpty(S);      //判断栈是否为空
StackOverflow(S);   //判断栈是否满

那么如何利用栈的运算来实现该队列的3个运算(形参由做题这根据要求自己设计)

Enqueue;            //将元素x入队
Dequeue;            //出队,并将出队元素存储在x中
QueueEmpty;         //判断队列是否为空

解答部分

第一题

在循环队列的类型结构中,增设一个tag的整形变量,进队时置tag为1,出对时置tag为0(因为只有入队操作可能导致队满,也只有出对操作可能导致队空)。队列Q初始时,tag=0、front=rear=0。这样的队列4要素如下:

队空条件:Q.front=Q.rear 且Q.tag==0
队满条件:Q.front==Q.rear且Q.tag==1
进队操作:Q.data[Q.rear]=x;Q.rear=(Q.rear+1)/MaxSize;Q.tag=1
出队操作:x.Q.data[Q.front];Q.front=(Q.front+1)/MaxSize;Q.tag=0

1)设“tag”法的循环队列入队算法:

int EnQueue1(SqQueue &Q ,ElemType x){
    if(Q.front==Q.rear&&Q.tag==1)
        return 0;               //连个条件都满足时则队满
    Q.data[Q.rear]=x;
    Q.rear=(Q.rear+1)%MaxSize;
    Q.tag=1;                    //可能队满
    return 1;
}

2)设“tag”法的循环队列入队算法:



int DeQueue1(SqQueue &Q,ElemType &x){
    if(Q.front==Q.rear&&Q.tag==0)
        return 0;               //两个条件都满足时则队空
    x=Q.data[Q.front];
    Q.front=(Q.front+1)%MaxSize;
    Q.tag=0;                    //可能队空
    return 1;
}

第二题

主要考察队队列和栈的特性和操作的理解。只是对队列的一系列操作是不可能将其中的元素逆置的,而栈可以将入栈的元素逆序提取出来。所以,我们可以将队列中的元素逐个地出队列,入栈;全部入栈后再逐个出栈,如队列。

void Inverser(Stack S,Queue Q){
    //本算法实现将队列中的元素逆置
    while(!=QueueEmpty(Q)){
        x=DeQueue(Q);           //队列中全部元素一次出队
        Push(S,x);              //元素一次入栈
    }
    while(!StackEmpty(S)){
        Pop(S,x);               //栈中全部元素一次出栈
        EnQueue(Q,x);           //再入队
    }
}

第三题

利用两个栈S1和S2来模拟一个队列,当需要向队列中插入一个元素时,用S1来存放已输入的元素,即S1执行入栈操作。当需要出队时,则队S2执行出栈操作。由于从栈中去除元素的顺序是原顺序的逆序,所以,必须先将S1中的所有元素全部出栈并入栈到S2中,再在S2中执行出栈操作,即可实现出队操作,而在执行此操作前必须判断S2是否为空,否则会导致顺序混乱。当栈S1和S2都为空时,队列为空。

总结如下:
1)对S2的出栈操作用作出队,若S2为空,则先将S1中的鄋元素送入S2
2)对S1的入栈操作用作入队,若S1满,必须先保证S2为空,才能将S1中的元素全部插入S2中。

入队算法


int EnQueue(Stack S1,Stack S2,ElemType e){
    if(!StackOverflow(S1)){
        Push(S1,x);
        return 1;
    }
    if(StackOverflow(S1)&&!StackEmpty(S2)){
        printf("队列满\n");
        return 0;
    }
    if(StackOverflow(S1)&&StackEmpty(S2)){
        while(!=StackEmpty(S1)){
            Pop(S1,x);
            Push(S2,x);
        }
    }
    Push(S1,x);
    return 1
}

出队算法



void DeQueue(Stack S1,Stack S2,ElemType &x){
    if(!StackEmpty(S2)){
        Pop(S2,x);
    }
    else if (StackEmpty(S1)){
        printf("队列为空\n");
    }
    else{
        while(!=StackEmpty(S1)){
            Pop(S1,x);
            Push(S2,x);
        }
        Pop(S2,x);
    }
}

判断队列为空的算法:

int QueueEmpty(Stack S1,Stack S2){
    if(StackEmpty(S1)&&StackEmpty(S2))
        return 1;
    else
        return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_22073849/article/details/78362502
今日推荐