分别基于顺序存储/链式存储设计一个队列(C语言)(数据结构学习6)

什么是队列

队列,和栈一样,也是一种对数据的"存"和"取"有严格要求的线性存储结构。

与栈结构不同的是,队列的两端都"开口",要求数据只能从一端进,从另一端出,如图所示:


队列.png

通常,称进数据的一端为 "队尾",出数据的一端为 "队头",数据元素进队列的过程称为 "入队",出队列的过程称为 "出队"。
不仅如此,队列中数据的进出要遵循 "先进先出" 的原则,即最先进队列的数据元素,同样要最先出队列。拿图 中的队列来说,从数据在队列中的存储状态可以分析出,元素 1 最先进队,其次是元素 2,最后是元素 3。此时如果将元素 3 出队,根据队列 "先进先出" 的特点,元素 1 要先出队列,元素 2 再出队列,最后才轮到元素 3 出队列。
栈和队列不要混淆,栈结构是一端封口,特点是"先进后出";而队列的两端全是开口,特点是"先进先出"。
因此,数据从表的一端进,从另一端出,且遵循 "先进先出" 原则的线性存储结构就是队列。

队列存储结构的实现有以下两种方式:

  • 顺序队列:在顺序表的基础上实现的队列结构;
  • 链队列:在链表的基础上实现的队列结构;

两者的区别仅是顺序表和链表的区别,即在实际的物理空间中,数据集中存储的队列是顺序队列,分散存储的队列是链队列。

实际生活中,队列的应用随处可见,比如排队买 XXX、医院的挂号系统等,采用的都是队列的结构。拿排队买票来说,所有的人排成一队,先到者排的就靠前,后到者只能从队尾排队等待,队中的每个人都必须等到自己前面的所有人全部买票成功并从队头出队后,才轮到自己买票。这就是典型的队列结构。

明白了什么是队列,接下来开始系统地学习顺序队列和链队列。

顺序存储(数组)实现队列

由于顺序队列的底层使用的是数组,因此需预先申请一块足够大的内存空间初始化顺序队列。除此之外,为了满足顺序队列中数据从队尾进,队头出且先进先出的要求,我们还需要定义两个指针(top 和 rear)分别用于指向顺序队列中的队头元素和队尾元素,如图 1 所示:

图1.png

由于顺序队列初始状态没有存储任何元素,因此 top 指针和 rear 指针重合,且由于顺序队列底层实现靠的是数组,因此 top 和 rear 实际上是两个变量,它的值分别是队头元素和队尾元素所在数组位置的下标。

在图 1 的基础上,当有数据元素进队列时,对应的实现操作是将其存储在指针 rear 指向的数组位置,然后 rear+1;当需要队头元素出队时,仅需做 top+1 操作。
例如,在图 1 基础上将 {1,2,3,4} 用顺序队列存储的实现操作如图 2 所示:


图2.png

在图 2 基础上,顺序队列中数据出队列的实现过程如图 3 所示:


图3.png

此方法存在的问题

先来分析以下图 2b) 和图 3b)。图 2b) 是所有数据进队成功的示意图,而图 3b) 是所有数据全部出队后的示意图。通过对比两张图,你会发现,指针 top 和 rear 重合位置指向了 a[4] 而不再是 a[0]。也就是说,整个顺序队列在数据不断地进队出队过程中,在顺序表中的位置不断后移。

顺序队列整体后移造成的影响是:

  • 顺序队列之前的数组存储空间将无法再被使用,造成了空间浪费;
  • 如果顺序表申请的空间不足够大,则直接造成程序中数组 a 溢出,产生溢出错误;

既然明白了上面这种方法的弊端,那么我们可以试着在它的基础上对其改良。

为了解决以上两个问题,可以使用巧妙的方法将顺序表打造成一个环状表,如图 4 所示:


图4.png

图 4 只是一个想象图,在真正的实现时,没必要真创建这样一种结构,只需要对其进行一点小小的处理,指针到了顺序表最后一位时加一时指向首位。

使用此方法需要注意的是,顺序队列在判断数组是否已满时,出现下面情况:

  • 当队列为空时,队列的头指针等于队列的尾指针;
  • 当数组满员时,队列的头指针等于队列的尾指针;

顺序队列的存储状态不同,但是判断条件相同。为了对其进行区分,最简单的解决办法是:牺牲掉数组中的一个存储空间,判断数组满员的条件是:尾指针的下一个位置和头指针相遇,就说明数组满了。

代码如下:

定义队列结构

#define MAX_SIZE 10
typedef int ElemType;
typedef struct{
    
    ElemType data[MAX_SIZE];//存储内容
    int front;//头指针
    int rear; //尾指针
    
}HjQueue;

1、初始化队列

void initQueue(HjQueue *queue){
    queue->front = 0;
    queue->rear = 0;
}

2、判断队列是否为空

Status isEmptyQueue(HjQueue queue){
    return queue.front == queue.rear;
}

3、判断队列是否已满

Status isFullQueue(HjQueue queue){
    return (queue.rear + 1) % MAX_SIZE == queue.front;
}

4、清空队列

void clearQueue(HjQueue *queue){
    queue->front = 0;
    queue->rear = 0;
}

5、返回队列的长度

int getQueueLength(HjQueue queue){
    return (queue.rear + MAX_SIZE - queue.front) % MAX_SIZE;
}

6、获取队列的头元素

Status getQueueHeadData(HjQueue queue, ElemType *data){
    if (queue.front == queue.rear) {
        return ERROR;
    }
    if (queue.front < MAX_SIZE) {
        *data = queue.data[queue.front];
        return OK;
    }
    return ERROR;
}

7、插入元素到队列中

Status insertDataIntoQueue(HjQueue *queue, ElemType data){
    //如果队列已满,则不能插入元素
    int newRear = (queue->rear + 1) % MAX_SIZE;
    if (newRear == queue->front) {
        return ERROR;
    }
    queue->data[queue->rear] = data;
    queue->rear = newRear;
    return OK;
}

8、从队列中删除元素

Status deleteDataFromQueue(HjQueue *queue, ElemType *data){
    //如果队列已空,则不能删除
    if (queue->front == queue->rear) {
        return ERROR;
    }
    //删除元素只需要头指针动就可以了
    *data = queue->data[queue->front];
    queue->front = (queue->front + 1) % MAX_SIZE;
    return OK;
}

9、遍历打印队列

void printfQueue(HjQueue queue){
    
    printf("遍历队列:");
    int i = queue.front;
    while (i != queue.rear) {
        printf("%d ",queue.data[I]);
        i = (i + 1) % MAX_SIZE;
    }
    printf("\n");
}

其它辅助代码

#include "stdlib.h"

#define OK    1
#define ERROR 0
typedef int Status;

int main(int argc, const char * argv[]) {
    printf("--顺序队列操作实现--\n");
    
    HjQueue queue;
    ElemType data;
    
    printf("初始化队列\n");
    initQueue(&queue);
    printf("队列是否为空?(1:空 0:否) %d\n",isEmptyQueue(queue));
    
    //入队
    printf("操作入队:\n");
    for (int i = 1; i <= 10; i++) {
        insertDataIntoQueue(&queue, i);
    }
    printfQueue(queue);
    printf("队列是否为空?(1:空 0:否) %d\n",isEmptyQueue(queue));
    printf("队列是否已满?(1:满 0:否) %d\n",isFullQueue(queue));
    printf("队列长度为: %d\n",getQueueLength(queue));
    
    //出队
    printf("操作出队:\n");
    deleteDataFromQueue(&queue,&data);
    printf("出队的元素: %d\n",data);
    printfQueue(queue);

    //获取队头
    Status status = getQueueHeadData(queue, &data);
    if (status) {
        printf("现在队头元素为: %d\n",data);
    }
    
    //清空队列
    printf("清空队列后");
    clearQueue(&queue);
    printf("队列是否为空?(1:空 0:否) %d\n",isEmptyQueue(queue));
    return 0;
}

输出结果

输出结果.png

链式存储(链表)实现队列

链式队列的实现思想同顺序队列类似,只需创建两个指针(命名为 top 和 rear)分别指向链表中队列的队头元素和队尾元素,如图 1 所示:


图1.png

图 1 所示为链式队列的初始状态,此时队列中没有存储任何数据元素,因此 top 和 rear 指针都同时指向头节点。我写的链式队列有头节点,因为这样更简单。

链式队列中,当有新的数据元素入队,只需进行以下 3 步操作:

  • 将该数据元素用节点包裹,例如新节点名称为 node;
  • 与 rear 指针指向的节点建立逻辑关系,即执行 rear->next = node;
  • 最后移动 rear 指针指向该新节点,即 rear = node;

由此,新节点就入队成功了。

例如,在图 1 的基础上,我们依次将 {1,2,3} 依次入队,各个数据元素入队的过程如图 2 所示:


图2.png

当链式队列中,有数据元素需要出队时,按照 "先进先出" 的原则,只需将存储该数据的节点以及它之前入队的元素节点按照原则依次出队即可。这里,我们先学习如何将队头元素出队。链式队列中队头元素出队,需要做以下 3 步操作:

  • 通过 top 指针直接找到队头节点,创建一个新指针 p 指向此即将出队的节点;
  • 将 p 节点(即要出队的队头节点)从链表中摘除;
  • 释放节点 p,回收其所占的内存空间;

例如,在图 2b) 的基础上,我们将元素 1 和 2 出队,则操作过程如图 3 所示:

图3.png

实现代码如下:

定义队列结构

typedef int ElemType;
typedef struct HjLinkNode{
    
    ElemType data;//存储内容
    struct HjLinkNode *next;
    
}LinkNode, *QueueNode;

typedef struct HjQueue{
    
    QueueNode front;//队头指针
    QueueNode rear;//队尾指针
}HjQueue;

1、初始化队列

Status initQueue(HjQueue *queue){
    
    queue->front = malloc(sizeof(LinkNode));
    if (!queue->front) {
        return ERROR;
    }
    //数据初始化
    queue->front->next = NULL;
    queue->rear = queue->front;
    return ERROR;
}

2、判断队列是否为空

Status isEmptyQueue(HjQueue queue){
    return queue.front->next == NULL;
}

3、销毁队列

void destoryQueue(HjQueue *queue){
    QueueNode node = queue->front;
    QueueNode temp;
    while (node) {
        temp = node;
        node = node->next;
        free(temp);
    }
    queue->front = queue->rear = NULL;
}

4、清空队列

void clearQueue(HjQueue *queue){
    queue->rear = queue->front;
    QueueNode node = queue->front->next;
    QueueNode temp;
    while (node) {
        temp = node;
        node = node->next;
        free(temp);
    }
}

5、返回队列的长度

int getQueueLength(HjQueue queue){
    
    QueueNode node = queue.front->next;
    int count = 0;
    while (node) {
        count ++;
        node = node->next;
    }
    return count;
}

6、获取队列的头元素

Status getQueueHeadData(HjQueue queue, ElemType *data){
    //判断是否空队列
    QueueNode node = queue.front->next;
    if (!node) {
        return ERROR;
    }
    *data = node->data;
    return OK;
}

7、插入元素到队列中

Status insertDataIntoQueue(HjQueue *queue, ElemType data){
    
    if (!queue->rear) {
        return ERROR;
    }
    QueueNode node = malloc(sizeof(LinkNode));
    if (!node) {
        return ERROR;
    }
    node->data = data;
    node->next = NULL;
    queue->rear->next = node;
    queue->rear = node;
    return OK;
}

8、从队列中删除元素

Status deleteDataFromQueue(HjQueue *queue, ElemType *data){
    //如果队列已空,则不能删除
    QueueNode node = queue->front->next;
    if (!node) {
        return ERROR;
    }
    queue->front->next = node->next;
    if (queue->rear == node) {
        queue->rear = queue->front;
    }
    *data = node->data;
    free(node);
    return OK;
}

9、遍历打印队列

void printfQueue(HjQueue queue){

    printf("遍历队列:");
    QueueNode node = queue.front->next;
    while (node) {
        printf("%d ",node->data);
        node = node->next;
    }
    printf("\n");
}

其它辅助代码

#include "stdlib.h"

#define OK    1
#define ERROR 0
typedef int Status;

int main(int argc, const char * argv[]) {
    printf("--链式队列操作实现--\n");
    
    HjQueue queue;
    ElemType data;
    
    printf("初始化队列\n");
    initQueue(&queue);
    printf("队列是否为空?(1:空 0:否) %d\n",isEmptyQueue(queue));
    
    //入队
    printf("操作入队:\n");
    for (int i = 1; i <= 10; i++) {
        insertDataIntoQueue(&queue, i);
    }
    printfQueue(queue);
    printf("队列是否为空?(1:空 0:否) %d\n",isEmptyQueue(queue));
    printf("队列长度为: %d\n",getQueueLength(queue));
    
    //出队
    printf("操作出队:\n");
    deleteDataFromQueue(&queue,&data);
    printf("出队的元素: %d\n",data);
    printfQueue(queue);

    //获取队头
    Status status = getQueueHeadData(queue, &data);
    if (status) {
        printf("现在队头元素为: %d\n",data);
    }
    
    //清空队列
    printf("清空队列后");
    clearQueue(&queue);
    printf("队列是否为空?(1:空 0:否) %d\n",isEmptyQueue(queue));
    return 0;
}

输出结果

输出结果.png

如有不对的地方,请指正,谢谢您的阅读~

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,192评论 6 511
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,858评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,517评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,148评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,162评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,905评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,537评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,439评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,956评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,083评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,218评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,899评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,565评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,093评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,201评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,539评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,215评论 2 358

推荐阅读更多精彩内容