数据结构与算法-栈和队列-队列(Queue)

2 栈和队列(Stack and Queue)

一次把循环队列和链队都搞上来了。

2.3 循环队列(Circular Queue)

实现队列思路

初始化创建空队列时,令 front = rear = 0 , 每当插入新的队列尾元素时,尾指针 rear 增1; 每当删除队列头元素时, 头指针 front 增1。因此,在非空队列中,头指针始终指向队列头元素,而尾指针始终指向队列尾元素的下一个位置。

在这里插入图片描述

循环队列

假设当前队列分配的最大空间为 6,则当队列处于上图(d状态),所示的状态时不可再继续插入新的队尾元素, 否则会出现溢出现象,即因数组越界而导致程序的非法操作错误。

怎样解决这种 “假溢出” 问题呢? 一个较巧妙的办法是将顺序队列变为一个环状的空间,如下图,称之为循环队列。

在这里插入图片描述

头、尾指针以及队列元素之间的关系不变 ,只是在循环队列中,头、尾指针 “依环状增1“ 的操作可用 “模” 运算来实现。 通过取模,头指针和尾指针就可以在顺序表空间内以头尾衔接的方式 ”循环” 移动。

区别队满还是队空

  1. 用一个元素空间,即队列空间大小为m时,有m-1个元素就认为是队满。这样判断队空的条件不变, 即当头、 尾指针的值相同时, 则认为队空;而当尾指针在循环意义上加1后是等千头指针, 则认为队满。
    1. 队空的条件:Q.front = Q.rear
    2. 队满的条件:(Q.rear + 1) % MAXQSIZE = Q.front
  2. 设一个标志位以区别队列是 “空” 还是 “满“。

基础代码:

// 循环队列相关的功能实现
#include <stdio.h>
#include <stdlib.h>

// 声明一些常量
#define OK 1
#define ERROR 0
#define OVERFLOW -2
#define TRUE 1
#define FALSE 0

// Status 是函数返回值类型, 其值是函数结果状态代码。
typedef int Status;
// Boolean 定义布尔型,值就是 TRUE 和 FALSE。
typedef int Boolean;

#define MAXSIZE 100 // 顺序队列存储空间的初始分配量

队列元素结构和队列结构:

// 队列元素类型定义
typedef struct
{
    int data; // 队列元素的数据
} QElemType;

// 顺序队列的结构体定义
typedef struct
{
    QElemType *base; // 存储空间的基地址
    int front;       // 队头指针
    int rear;        // 队尾指针
} SqQueue;

在编写代码过程中,经常需要查看栈内的内容,因此需要一个方法可以遍历栈,并打印其中的内容:

// 打印队列元素
void PrintQueue(SqQueue Q)
{
    int i = Q.front;
    while (i != Q.rear)
    {
        printf("%d ", Q.base[i].data);
        i = (i + 1) % MAXSIZE; // 循环移动
    }
    printf("\n");
}

2.3.1 初始化

循环队列的初始化操作就是动态分配一个预定义大小为 MAXQSIZE 的数组空间。

【算法步骤】

  1. 为队列分配一个最大容量为 MAXQSIZE 的数组空间, base 指向数组空间的首地址。
  2. 头指针和尾指针置为零, 表示队列为空。

【代码实现】

// 初始化队列
Status InitQueue(SqQueue *Q)
{
    Q->base = (QElemType *)malloc(MAXSIZE * sizeof(QElemType));
    if (!Q->base) // 分配失败
        return OVERFLOW;
    Q->front = 0; // 队头指针初始化为 0
    Q->rear = 0;  // 队尾指针初始化为 0
    return OK;
}

2.3.2 求队列长度

对于非循环队列,尾指针和头指针的差值便是队列长度,而对千循环队列,差值可能为负数,所以需要将差值加上M心CQSIZE, 然后与MAXQSIZE求余。

// 求队列长度
int QueueLength(SqQueue Q)
{
    return (Q.rear - Q.front + MAXSIZE) % MAXSIZE; // 计算队列长度
}

2.3.3 入队

入队操作是指在队尾插入一个新的元素。

【算法步骤】

  1. 判断队列是否满,若满则返回 ERROR。
  2. 将新元素插人队尾。
  3. 队尾指针加 1。

【代码实现】

// 入队操作
Status EnQueue(SqQueue *Q, QElemType e)
{
    if ((Q->rear + 1) % MAXSIZE == Q->front) // 队列满
        return ERROR;
    Q->base[Q->rear] = e;              // 将元素插入队尾
    Q->rear = (Q->rear + 1) % MAXSIZE; // 队尾指针循环移动
    return OK;
}

2.3.4 出队

出队操作是将队头元素删除。

【算法步骤】

  1. 判断队列是否为空,若空则返回 ERROR。
  2. 保存队头元素。
  3. 队头指针加 1。

【代码实现】

// 出队操作
Status DeQueue(SqQueue *Q, QElemType *e)
{
    if (Q->front == Q->rear) // 队列空
        return ERROR;
    *e = Q->base[Q->front];              // 获取队头元素
    Q->front = (Q->front + 1) % MAXSIZE; // 队头指针循环移动
    return OK;
}

2.3.5 取队头元素

当队列非空时, 此操作返回当前队头元素的值, 队头指针保持不变。

// 获取队头元素
Status GetFront(SqQueue Q, QElemType *e)
{
    if (Q.front == Q.rear) // 队列空
        return ERROR;
    *e = Q.base[Q.front]; // 获取队头元素
    return OK;
}

2.3.6 销毁队列

如果队列不用了,就需要进行销毁,回收已经分配的队列空间。

// 销毁队列
Status DestroyQueue(SqQueue *Q)
{
    if (Q->base) // 如果队列的基地址不为空
    {
        free(Q->base);  // 释放存储空间
        Q->base = NULL; // 将基地址置为 NULL
        Q->front = 0;   // 队头指针置为 0
        Q->rear = 0;    // 队尾指针置为 0
        return OK;
    }
    return ERROR; // 队列已经为空
}

2.4 链队(Linked Queue)

链队是指采用链式存储结构实现的队列。 通常链队用单链表来表示,一个链队显然需要两个分别指示队头和队尾的指针(分别称为头指针和尾指针)才能唯一确定。
在这里插入图片描述

础代码:

// 链队列相关的功能实现
#include <stdio.h>
#include <stdlib.h>

// 声明一些常量
#define OK 1
#define ERROR 0
#define OVERFLOW -2
#define TRUE 1
#define FALSE 0

// Status 是函数返回值类型, 其值是函数结果状态代码。
typedef int Status;
// Boolean 定义布尔型,值就是 TRUE 和 FALSE。
typedef int Boolean;

链队元素结构、链队结点和链队结构:

// 队列元素类型定义
typedef struct
{
    int data; // 队列元素的数据
} QElemType;

// 链队列结点结构体定义
typedef struct QNode
{
    QElemType data;     // 队列元素数据
    struct QNode *next; // 指向下一个结点的指针
} QNode, *QueuePtr;

// 链队列的结构体定义
typedef struct
{
    QueuePtr front; // 队头指针
    QueuePtr rear;  // 队尾指针
} LinkQueue;

在编写代码过程中,经常需要查看栈内的内容,因此需要一个方法可以遍历栈,并打印其中的内容:

// 打印链队列元素
void PrintQueue(LinkQueue Q)
{
    if (Q.front == NULL)
    {
        printf("Queue is empty.\n");
        return;
    }
    QueuePtr current = Q.front->next; // 从队头开始遍历
    while (current != NULL)
    {
        printf("%d ", current->data.data); // 打印队列元素数据
        current = current->next;           // 移动到下一个结点
    }
    printf("\n");
}

2.4.1 初始化

链队的初始化操作就是构造一个只有一个头结点的空队。

在这里插入图片描述

【算法步骤】

  1. 生成新结点作为头结点,队头和队尾指针指向此结点。
  2. 头结点的指针域置空。

【代码实现】

// 初始化链队列
Status InitQueue(LinkQueue *Q)
{
    Q->front = Q->rear = (QueuePtr)malloc(sizeof(QNode));
    if (!Q->front) // 分配失败
        return OVERFLOW;
    Q->front->next = NULL; // 初始化时,队头和队尾指针都指向一个空结点
    return OK;
}

2.4.2 入队

和循环队列的入队操作不同的是,链队在入队前不需要判断队是否满,需要为入队元素动态分配一个结点空间。

【算法步骤】

  1. 为人队元素分配结点空间,用指针p指向。
  2. 将新结点数据域置为 e。
  3. 将新结点插人到队尾。
  4. 修改队尾指针为 p。

【代码实现】

// 入队操作
Status EnQueue(LinkQueue *Q, QElemType e)
{
    QueuePtr newNode = (QueuePtr)malloc(sizeof(QNode));
    if (!newNode) // 分配失败
        return OVERFLOW;
    newNode->data = e;    // 设置新结点的数据
    newNode->next = NULL; // 新结点的 next 指针指向 NULL

    Q->rear->next = newNode; // 将新结点链接到队尾
    Q->rear = newNode;       // 更新队尾指针
    return OK;
}

2.4.3 出队

和循环队列一样,链队在出队前也需要判断队列是否为空,不同的是,链队在出队后需要释放出队头元素的所占空间。

【算法步骤】

  1. 判断队列是否为空,若空则返回 ERROR。
  2. 临时保存队头元素的空间,以备释放。
  3. 修改队头指针,指向下一个结点。
  4. 判断出队元素是否为最后一个元素,若是,则将队尾指针重新赋值,指向头结点。
  5. 释放原队头元素的空间。

【代码实现】

// 出队操作
Status DeQueue(LinkQueue *Q, QElemType *e)
{
    if (Q->front == Q->rear) // 队列空
        return ERROR;

    QueuePtr tempNode = Q->front->next; // 获取队头结点
    *e = tempNode->data;                // 获取队头元素数据
    Q->front->next = tempNode->next;    // 更新队头指针

    if (Q->rear == tempNode) // 如果出队的是最后一个结点
        Q->rear = Q->front;  // 更新队尾指针

    free(tempNode); // 释放出队结点的内存
    return OK;
}

2.4.4 取队头元素

与循环队列一样,当队列非空时,此操作返回当前队头元素的值,队头指针保持不变。

// 获取队头元素
Status GetFront(LinkQueue Q, QElemType *e)
{
    if (Q.front == Q.rear) // 队列空
        return ERROR;
    *e = Q.front->next->data; // 获取队头元素数据
    return OK;
}

2.4.5 求队列长度

因为是动态增减元素,所以需要遍历每一个元素,进行累加。

// 求链队列长度
int QueueLength(LinkQueue Q)
{
    int length = 0;
    QueuePtr current = Q.front->next; // 从队头开始遍历
    while (current != NULL)
    {
        length++;                // 计数
        current = current->next; // 移动到下一个结点
    }
    return length; // 返回队列长度
}

因为需要遍历队列所有元素,所以算法复杂度是O(n)

2.4.6 销毁队列

销毁队列需要销毁队列中的每一个元素,也包含了队头的空元素结点。

// 销毁链队列
Status DestroyQueue(LinkQueue *Q)
{
    QueuePtr current = Q->front; // 从队头开始遍历
    while (current != NULL)
    {
        QueuePtr temp = current; // 保存当前结点
        current = current->next; // 移动到下一个结点
        free(temp);              // 释放当前结点的内存
    }
    Q->front = Q->rear = NULL; // 将队头和队尾指针置为 NULL
    return OK;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

晴空闲雲

感谢家人们的投喂

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值