队列的一些概念梳理及题目

目录

一、队列基本概念

二、顺序队列

三、循环队列

四、链队列

五、双端队列

六、题



一、队列基本概念

像栈一样,队列(queue)也是一种线性表,它的特性是先进先出,插入在一端,删除在另一端。就像排队一样,刚来的人入队(push)要排在队尾(rear),每次出队(pop)的都是队首(front)的人。

  1. 队头:允许删除的一端
  2. 队尾:允许插入的一端

 

二、顺序队列

1.队列的顺序存储类型:

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

 初始状态(队空条件):

Q.front == Q.rear == 0;

2.front 和 rear分别指示队头和队尾元素的位置:

front指向队头,rear指向队尾的后一位置(一般默认情况),或者front指向队头的前一位置,rear指向队尾。

  • 进队操作:队不满时,先送值到队尾元素,再将队尾指针加一
  • 出队操作:队不空时,先取队头元素值,再将队头指针加一

注意:Q.rear == MaxSize不能作为队列满的条件,因为有可能存在假溢出(上溢出):

比如说5个元素入队,出来4个元素,这时队列里面仅剩下一个元素,但是由于整体上元素向前移动了4个格,最后一个元素就顶在了最上面一个格,队列其实好几个格都是空的,但是另一个层面来讲却已经满了,所以称作这种情况为假溢出。

为了避免这种假溢出,我们下面来看循环队列↓↓↓

 

三、循环队列

将顺序队列臆造为一个环装的空间,即把存储队列元素的表从逻辑上视为一个环。当队首指针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 - Q.front + MaxSize)% MaxSize

出队入队时,指针都按照顺时针方向进一。

队空和队满都会Q.front ==Q.rear,为了区分队空和队满,有以下三种方式:

1.牺牲一个单元来区分队空和队满:队头指针在队尾指针的下一位置作为队满的标志

  • 队满条件:(Q.rear + 1)% MaxSize == Q.front
  • 队空条件仍:Q.front == Q.rear
  • 队列中元素的个数:(Q.rear - Q.front + MaxSize)% MaxSize

2.类型中添加:

  • 元素个数:比如说结构体中添加size:size == 8个元素,size == 0空。
  • 是否满了:比如说结构体中添加flag:flag == 0空,flag == 1满。

循环队列的操作

(1)循环队列初始化

//循环队列初始化
void InitQueue(SqQueue &Q){
    Q.rear = Q.front = 0;
}

(2)循环队列判队空

//循环队列判队空
bool isEmpty(SqQueue 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;   //队列包含front结点和rear结点
}LinkQueue;
  1. 当Q.rear == NULL且Q.rear == NULL时,链式队列为空。
  2. 不带头结点的链式队列在操作上往往比较麻烦,因此通常将链式队列设计成带(头结点的单链表),这样插入和删除操作就统一了。
  3. 用单链表表示的链式队列特别适合于数据元素变动比较大的情形,而且不存在队列满而且溢出的问题。假如程序中需要多个队列,与多个栈的情况一样,最好使用链式队列,这样就不会出现存储分配不合理和“溢出”的问题

链式队列的基本操作

(1)链式队列初始化:新建一个头结点,两指针均指向此结点,结点后指空。

//链式队列初始化
void InitQueue(LinkQueue &Q){
    Q.front = Q.rear = (LinkNode*)malloc(sizeof(LinkNode));
    Q.front->next = NULL;//只有一个头结点
}

(2)链式队列判空:Q.rear == Q.front就是空

//循环队列判队空
bool isEmpty(SqQueue Q){
    if(Q.rear == Q.front) return true;
    else return false;
}

(3)链式队列入队:

  • 创建新结点,将结点进行赋值:值为x,指针指空
  • 将新结点链接到链表尾,将表尾后移
//链式队列入队
void EnQueue(LinkQueue &Q, ElemType x){
    LinkNode *s = (ListNode *)malloc(sizeof(LinkNode));//创建新结点
    s->data = x; s->next = NULL;
    Q.rear->next = s;
    Q.rear = s;
}

(4)链式队列出队

  • 判空,待删除结点p在头结点后,将p值赋给x(如果链表里只有一个结点,即p是尾结点,那么将尾结点指向头结点,否则尾结点不用动),然后删除掉p就完成啦!
//链式队列出队
bool DeQueue(LinkQueue &Q, ElemType &x){
    if(Q.rear == Q.front) return false;
    LinkNode *p = Q.front->next;
    x = p->data;
    Q.front->next = p->next;
    if(Q.rear == p) Q.rear = Q.front;
    free(p);
    return true;
}

 

五、双端队列

双端队列是指允许两端都可以入队和出队操作的队列。其元素的逻辑结构还是线性结构,将队列的两端分别称为前端和后端,两端都可以入队和出队。

  • 输出受限的双端对列:允许在一端进行插入和删除,另一端只允许插入
  • 输入受限的双端队列:允许在一端进行插入和删除,另一端只允许删除

 这里需要做习题哦~明天继续更新习题 [狗头]

 

 

六、题目

(1)循环队列存储在数组A[0...n]中,入队时的操作是

rear = (rear + 1) mod (n + 1);

队满条件是:

Q.front == (Q.rear + 1)% maxSize;

(2)已知循环队列的存储空间为数组A[21],front指向队头元素的前一位置,rear指向队尾元素,假设当前front和rear的值分别为8和3,则该队列的长度为( )

队列的长度为(rear - front + maxSize)% maxSize = (3 - 8 + 21)% maxSize = 16;

所以答案 16

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

循环队列中,每删除一个元素,队首指针front = (front + 1) % 6,每插入一个元素,队尾指针rear = (rear + 1)% 6

上述操作后,front = 0, rear = 3。所以答案 3 和 0

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

注意:一定要注意front和rear指向哪里!到底是队头,队尾?还是队头减一,队尾?还是队头,队尾加一?

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

(5)最适合用做链队的链表是(),最不适合用做链队的链表是( )

答案:带队首指针和队尾指针的非循环链表、只带队首指针的非循环双链表

原因:

  • 如果用循环链表,因为不涉及删除最后一个结点,所以不需要循环,是多余的!而在队尾添加元素时还要修改成循环的,就更多余了!
  • 而下一个问题不适合做链队的链表当然是这个没有尾结点的啦,因为每次删除尾巴头指针都要走好远~

下面这种链表最适合做链队啦嘻嘻↓↓↓

做这种题建议自己模拟一下~

 (6)用链式存储方式的队列进行删除操作时需要( )

答案:头尾指针可能都要修改

哇!当队列用链表形式存储时,删除元素时从队头删除,一般情况下仅需要修改头指针,但若此时队列中仅有一个元素,队尾指针也要被修改,因为仅有一个元素时,删除后队列为空,需修改尾指针为rear = front;

 (7)在一个链队列中,假设队头指针为 front,队尾指针为rear,x所指向的元素需要入队,则需要执行的操作为( )

答案:rear->next = x; x->next = null; rear = x;

唉当时错选了rear->next = x; rear = x;属实不小心,一定要小心!记住!!

 明日继续更~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章