数据结构(11)队列之循环队列

前言

昨天说到在顺序队列中,如果按照“入队则尾指针加一,出队则头指针加一”的方式实现入队出队操作,就可能出现尾指针已越界,无法插入元素;队列实际上却仍有空间的“假溢出”现象。如图

img_1

为了充分利用空间,我们可以再让尾指针循环回来,寻找插入的位置。这样,队列的这种头尾相接的顺序存储结构就被称为循环队列。

为了方便理解,书上常常将循环队列画成一个环状的结构,但是实际上它仍然是线性的。

img_2

img_3

这样,在循环队列中,判断队列已满的条件就从“查看rear值是否大于等于队列最大容量”,变成了“查看rear值是否与fount值相等”。

img_4

循环队列的实现

按照现在的设计,我们进行插入操作(新元素入队)时,需要比较rear值与fount值是否相等,如果相等说明队列已满,无法插入。但回想我们在初始化队列时,把fount值和rear值都初始化为0了。现在rear值与fount值相等就有两种情况:第一是队列是空的,可以插入元素;第二是队列已满,无法插入元素。如何区分这两种状态呢?主要有以下几种方法:

  • 设置一个flag变量标志队列的状态,当flag为0时,说明队列未满;当flag为1时,说明队列已满。
  • 与上一个类似,设置一个变量来记录队列内的元素个数,当元素个数小于队列最大容量时,说明队列为满;元素个数大于等于队列最大容量时,说明队列已满。(也可以不特别用变量来记录,而是用获取队列内元素个数的方法来获得当前的元素个数,主要看自己是怎么实现这个方法的。)
  • 一个比较特别的方法是:少用一个元素空间,当队列还有最后一个空间时,我们就认为它已经满了。什么意思呢?即设队列最大容量为m,当队列内有m-1个元素时,就认为它满了。这时候会有三种情形:

img_5
在这种前提下,当(rear == fount)时,说明队列是空的。那么判定队满的条件是什么呢?假如只考虑到 1 和 2 两种情况,很明显就是( (rear+1) == fount);但是还有 3 这种情况,这时候fount和rear相差了整整一圈,需要解决的问题是:怎样让rear循环回来?

我首先想到的办法是:当rear值等于数组最大下标时,再把它设为0就行了。当然因为涉及到与fount的比较,是设为0还是-1,自增后设置还是设置后自增等等问题还需要详细考虑,能不能行也还没有实际去做,这里就只记录下思路。

现在说下正常的方法:首先需要知道,当数A与某个数B求余时(A % B),得到的结果一定是在 0~(B-1) 这个范围内。利用求余运算的特性,我们不让rear单纯地自增,而是自增后与队列最大容量求余( (rear + 1) % MAXSIZE)。这样,当rear小于数组最大下标(MAXSIZE-1)时,rear仍是自增的;当rear值等于数组最大下标(MAXSIZE-1)时,rear就被设成了0; rear的值始终在 0~(MAXSIZE-1)范围内循环。那么,判断队满的条件就是:((rear+1)%MAXSIZE == fount)。

img_6

循环队列求当前元素个数

循环队列在求当前元素个数(即长度)时,同样有三种情形:

img_7

第一种是rear > fount,第二种是 rear < fount,第三种则是 rear == fount。

毫无疑问,如果是第一第三种情况时,只需要(rear - fount)就可以得到当前元素个数了。但是在第二种情况中,(rear - fount)得到的是负值,由于此时fount和rear相差了整整一圈,实际上应该是( (MAXSIZE+rear) - fount)。

现在使用if判断情况然后调用不同的算式固然能得到答案,但有没有能统一三种情况的方法呢?求余运算又登场了。假如我们都使用(MAXSIZE + rear - fount)这个算式,可以发现在第一第三种情况中,算式值只是多了一个MAXSIZE值而已,我们让它与MAXSIZE求余,所得的结果就是(rear - fount)。而在第二种情况中,由于算式值小于MAXSIZE,所得的结果仍是(MAXSIZE + rear - fount)。

这样,通用的计算公式就是:(rear - fount + MAXSIZE)%MAXSIZE

img_8

全部代码

SeqQueue.h

#ifndef SeqQueue_h
#define SeqQueue_h

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

#define ElemType int
#define MAXSIZE 8

typedef struct Queue{
    ElemType *base;
    int fount;
    int rear;
}Queue;

//初始化
void InitQueue(Queue *Q);

//入队
void EnQueue(Queue *Q,ElemType x);
//出队
void DeQueue(Queue *Q);
//展示
void ShowQueue(Queue *Q);

//获取队首元素
void GetHead(Queue *Q,ElemType *x);
//求长度
int GetLength(Queue *Q);
//清除
void CleatQueue(Queue *Q);
//摧毁
void DestoryQueue(Queue *Q);

#endif /* SeqQueue_h */

SeqQueue.c

#include "SeqQueue.h"

//初始化
void InitQueue(Queue *Q){
    //开辟空间
    Q->base = (ElemType *)malloc(sizeof(ElemType)*MAXSIZE);
    assert(Q->base != NULL);
    //队头指针和队尾指针指向0下标
    Q->fount = Q->rear = 0;
}

//入队
void EnQueue(Queue *Q,ElemType x){
    //判断队列是否满
    if ((Q->rear + 1)%MAXSIZE == Q->fount) {
        printf("队列已满\n");
        return;
    }
    Q->base[Q->rear] = x;
    Q->rear = (Q->rear+1)%MAXSIZE;
}
//出队
void DeQueue(Queue *Q){
    //判断队列是否空
    if (Q->fount == Q->rear) {
        printf("队列已空\n");
        return;
    }
    Q->fount = (Q->fount+1)%MAXSIZE;
}

//展示
void ShowQueue(Queue *Q){
    for (int i = Q->fount; i != Q->rear;) {
        printf("%4d",Q->base[i]);
        i = (i+1)%MAXSIZE;
    }
    printf("\n");
}

//获取队首元素
void GetHead(Queue *Q,ElemType *x){
    //判断队列是否空
    if (Q->fount == Q->rear) {
        printf("队列已空\n");
        return;
    }
    *x = Q->base[Q->fount];
}
//求长度
int GetLength(Queue *Q){
    return (MAXSIZE + Q->rear - Q->fount) % MAXSIZE;
}
//清除
void CleatQueue(Queue *Q){
    Q->fount = Q->rear = 0;
}
//摧毁
void DestoryQueue(Queue *Q){
    free(Q);
    Q->base = NULL;
}

Main.c

#include "SeqQueue.h"

int main(int argc, const char * argv[]) {
    Queue Q;
    InitQueue(&Q);
    
    //入队
    for (int i = 0; i < 7; i ++) {
        EnQueue(&Q, i);
    }
    
    ShowQueue(&Q);
    //出队
    DeQueue(&Q);
    
    EnQueue(&Q, 10);
    ShowQueue(&Q);
    
    DeQueue(&Q);
    EnQueue(&Q, 20);
    ShowQueue(&Q);
    
    int len = GetLength(&Q);
    printf("len = %d\n",len);
    
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章