【数据结构】队列:数组/链表实现插入/删除 c++代码(含思路详解 循环数组问题 队列是否为空的判断问题详解)

目录

认识队列

使用数组实现

思路

插入

删除

循环数组 

代码如下

使用链表实现

思路

队列是否为空的判断问题

代码如下


P22-P24笔记,视频地址👇

http://www.bilibili.com/video/BV1Fv4y1f7T1?p=24&vd_source=02dfd57080e8f31bc9c4a323c13dd49c

认识队列

队列就是一种遵循着先进先出 FIFO(first in first out)原则的数据结构。

它和栈是不一样的,回顾一下,栈是先进后出,且只从一端进出的数据结构,而队列是一端进一端出的数据结构。想一下生活中有很多这样的例子:去银行取钱的时候,大家都从后面排着队,在前面的先办理,办理完之后从前面就出去了;食堂打饭、超市结账......都是遵循着队列这种原则,是不是亲切了很多呀hh.

老师举的一个例子,好有趣啊哈哈哈。为了不成为一台粗鲁的打印机,它会按照请求的先后形成一个队列,一个一个完成请求。也是队列的应用。

那么这样的入队出队的操作也就是队列的插入和删除操作咯,也是我们要实现的。插入的一端称为队尾,删除的一端称为队头,也是很符合我们日常逻辑的~

在栈中push入栈 pop出栈是常被大家使用的,在队列里enqueue入队和dequeue出队则是比较常用的。我们这里就采用后者啦。

使用数组实现

思路

我们首先要创建两个节点分别来标示队头front和队尾rear(表示的都是下标)。我将其起始值,也就是队列为空时的初始值设为-1(当然可以设置为其他的字符,只要不和后面数组的下标引起冲突歧义即可)我们的队列就是我们规定的front-rear之间

接下来主要就是插入和删除这两个核心函数的实现思路啦。

插入

插入的话,主要就是从队尾rear插入,找到更新的队尾rear的值即可。我们梳理一下逻辑,会遇到哪些情况?

1.有可能此时队列为空吧?当队列为空时,插入数据就是直接把front和rear这两个节点的值同时更改为0即可(我们希望从数组下标为0开始)

2.有可能当我们插入时,由于数组空间提前规定,会出现队列已满的情况。此时我们就直接return即可,队列满了也没办法嘛(当然除非重新创建一个更大的数组将该数组的数据都复制到新的数组中,哎这话术好熟悉是之前讲链表的时候提到的hh)

3.正常插入啦。来处理除了以上两种特殊情况,其他都是直接更新rear的值来实现插入。

由于不管是以上第一种还是第三种的插入情况,最终都是把下标为rear的位置赋上新的值,所以这个赋值的语句就写在if情况判断语句之后啦。

删除

同理,我们列出删除数据所具有的不同情况。与插入数据相对应:删除主要就是找到更新的front值即可。

1.当队列为空时,就直接return

2.当队列仅有一个数据时,我们删除数据之后需要将front和rear的值都进行更改为队列为空的状态,所以需要单独列出这种情况。

3.就是正常情况啦。直接将front更新为下一个下标就是把该数据从队列中删除了。因为我们的队列就是从我们规定的front-rear之间嘛。

清楚了插入删除的操作思路之后,相信代码很容易就能实现。只是在我们举例子实现过程中,我们发现

循环数组 

由于是数组实现,我们发现如果我像图中那样规定,从下标为2开始存储数据,由于队列就是我们规定的front-rear之间 的元素,那么我们发现数组空间大小为10,到达数组最后一个位置的时候rear已经没办法再添了,可是这样会造成前面两个空间的浪费。

如何让rear在到达数组最后一个空间之后,又能指向数组中没有使用的位置呢?于是我们引入循环数组。就像图中的循环数组,这样围成一个圈之后,我们可以看到rear的下一个值应该赋值为0,再下一个值应该赋值为1.

其实我好像没有足够时间想出来,直接进度条就告诉我了hhh。

rear = (rear + 1 + SIZE) % SIZE  这里SIZE是一个宏定义(其实我还不是很了解这具体是个什么意思,只知道他大多用于定义一个在程序中会提到很多次的变量,这样直接在宏定义里修改值整个程序中这个变量的值就修改了),SIZE 这里表示该数组的空间大小。先了解到够目前阶段使用即可哈。

正常情况下我们的rear值是遵循着怎样的更改方式呢?rear+1指向下一个下标呗。可是如果想实现循环数组,把数组中没有使用的空间也进行充分利用,那么我们应该(rear+1)%SIZE,想一下哈,正常情况下,一般取余之后还是rear+1的值。

只是当rear+1=SIZE时,(rear+1)%SIZE=0,rear会指向下标0。当然如果队列头是从下标为0开始的,此时也就front=rear了,会结束插入;倘若不是从0开始,那么就直到front=rear,也就结束了。

为了使(rear+1)括号里数据为正数,于是写成(rear + 1 + SIZE) % SIZE 是更严谨的,而且对结果没有影响,因为多加的SIZE%SIZE=0.

对应着删除数据中front的更新就是这样咯 front = (front + 1 + SIZE) % SIZE

ok明白啦?

代码如下

#include <iostream>
#include <cstring>
using namespace std;
#define SIZE 10
int a[SIZE];
int front = -1, rear = -1;
void Enqueue(int x)
{
    //空队列
    if (front == -1 && rear == -1)
    {
        front = 0;
        rear = 0;
    }
    //队列已满
    else if ((rear + 1 + SIZE) % SIZE == front)//如果rear指针的下一个位置等于front指针的当前位置,那么表示队列已满,无法进行插入操作
    {
        return;
    }
    //正常插入
    else
    {
        rear = (rear + 1 + SIZE) % SIZE;
    }
    a[rear] = x;
}
void Dequeue()
{
    if (front == -1 && rear == -1)
        return;
    else if (front == rear)//而在删除时,如果front指针的当前位置等于rear指针的当前位置,那么表示队列为空
    {
        front = -1;
        rear = -1;
    }
    else
    {
        front = (front + 1 + SIZE) % SIZE;
    }
}
int main()
{
    Enqueue(2);
    Enqueue(20);
    Enqueue(25);
    Enqueue(7);
    Enqueue(25);
    Dequeue();
    Dequeue();
    for (int i = front; i != rear; i = (i + 1) % SIZE)
    {
        cout << a[i] << " ";
    }
    cout << a[rear] << endl;
    return 0;
}

使用链表实现

思路

链表就没有数组那么复杂的分几种情况啦。因为链表是需要节点才创建

对照过来,应该设置两个front和rear节点初始值为NULL。目的是为了索引队头和队尾。 

队头很好理解,头节点嘛。只是队尾这个,因为我们插入节点是从队尾插入的,但是我们知道链表我们只知道头节点,根据头节点遍历来找到其他节点,但是这样时间复杂度太高了,我们就要考虑其他的实现方法。

其实我最开始想的不好(双向链表,被弹幕战友们吐槽了hhh),其实只需要时刻存储着rear尾节点就行了

插入函数中当队列为空即front=rear=NULL时(这里可以只判断front为NULL),就直接将插入的节点赋值给front和rear;其他情况下还是直接更新队尾rear即可咯。

删除函数当队列为空,front=NULL(这里也可以判断两个均为NULL),return;当front=rear说明删除该元素之后,队列即为空了,所以要单列该情况,同时更改front和rear均为空;其他情况就正常更新front的指向。

队列是否为空的判断问题

在上面对两个函数队列为空的情况判断中有一些问题。这里解释一下。由于front和rear初始值为NULL,在不断的插入元素,删除元素中,front队首和rear队尾,只有在删除元素直至队列为空时,才会重新赋值为NULL。所以只通过判断front或rear是否为NULL来作为队列为空的条件是可行的。

这是由于我们下面的这段代码,避免了只判断一个节点为空所可能出现的各种错误。

front = rear = temp;

代码如下

#include <iostream>
using namespace std;
struct Node
{
    int data;
    Node *next;
};

struct Node *front = NULL;
struct Node *rear = NULL;

void Enqueue(int x)
{
    struct Node *temp=new Node();
    temp->data = x;
    temp->next = NULL;
    // 如果队列为空
    if (front == NULL)
    {
        front = rear = temp;
        return;
    }
    rear->next = temp;
    rear = temp;
}

void Dequeue()
{
    struct Node *temp = front;
    // 队列为空
    if (front == NULL)
    {
        cout << "error" << endl;
        return;
    }
    // 队列不为空且仅有一个数据
    if (front == rear)
    {
        front = rear = NULL;
    }
    else
    {
        front = front->next;
    }
    // 释放删除数据所占存储空间
    free(temp);
}

// 返回队首元素
int Front()
{
    if (front == NULL)
    {
        printf("Queue is empty\n");
        return 0;
    }
    return front->data;
}
// 打印队列元素
void Print()
{
    struct Node *temp = front;
    while (temp != NULL)
    {
        printf("%d ", temp->data);
        temp = temp->next;
    }
    printf("\n");
}

int main()
{
    Enqueue(2);
    Print();
    Enqueue(4);
    Print();
    Enqueue(6);
    Print();
    Dequeue();
    Print();
    Enqueue(8);
    Print();
    return 0;
}

队列至此就结束了。终于要到树了!!加油!!

如果有问题欢迎指出,非常感谢!!

也欢迎交流建议哦。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值