【C语言数据结构6】--队列的实现

简介: 队列同样是一种特殊的线性表,它和栈的阉割方式不一样。它的插入只允许在队尾进行,它的删除只允许在队头进行。因此它有先进先出的特性(FIFO)。

一、什么是队列

队列同样是一种特殊的线性表,它和栈的阉割方式不一样。它的插入只允许在队尾进行,它的删除只允许在队头进行。因此它有先进先出的特性(FIFO)。

队列和我们日常排队是类似的,相比日常排队,队列是严格禁止插队的。我们可以通过下图来理解队列:

网络异常,图片无法展示
|

一个队列的第一个元素被称为队头,队列的最后一个元素被称为队尾。队列中最常用的两种操作就是入队和出队,也就是俗称的插入、删除操作。在队列中,只能出队队头元素,入队元素只能在队尾之后。

二、队列的表示

队列同样可以用顺序存储和链式存储两种结构来表示。

(1)顺序存储结构

因为我们要关注队头和队尾的位置,因此我们分别设置对头指针和队尾指针,于是我们结构体的定义如下:

#define MAXSIZE 20
typedef int ElemType;
typedef struct{
    ElemType data[MAXSIZE];   //用静态数组存放队列元素
    int front, rear;        //队头和队尾指针
}SqQueue;
复制代码

在使用顺序存储结构实现队列时,我们会构造一个逻辑上循环的队列。后续会有更详细的讲解。

(2)链式存储结构

链式存储结构实现的队列也是一个阉割版的链表,因此它节点的定义和链表是一样的,但是链队列还要额外定义一个队列结构体:

//链队列节点结构体
typedef struct QNode{
    ElemType data;
    struct QNode *next;
}QNode;
//链队列结构体
typedef struct{
    //头尾指针
    QNode *front, *rear;
}LinkedQueue;
复制代码

链队列的结构体中包含了一个头指针和尾指针。

三、循环队列的实现

(1)循环队列

我们先看看如果没有循环队列的概念,我们应该如何用顺序存储结构实现队列。

在初始状态,我们将队头、队尾指针指向0。在操作队列的过程中,我们保证队头指针指向队头的下标,队尾指针指向队尾的下一个位置。有了这些规则后我们对一个长度为6的队列进行5次入队,5次出队操作,如图示:

网络异常,图片无法展示
|

在这个操作过程中,除了队空的情况,队头指针都指向队头元素的位置。而且队空时队头指针等于队尾指针。

我们来关注一下上面的操作我们应该如何判断队满的情况。一种想法是判断尾指针是否等于MAXSIZE-1,但是这样做有个很明显的问题。当我们对一个MAXSIZE=6的队列,入队5次再出队5次后我们的尾指针等于MAXSIZE-1,但是我们队列其实是空的。为了解决这个问题,我们采用循环队列这种逻辑结构。如图示:

网络异常,图片无法展示
|

在物理上,我们还是使用一个连续的数组来存储。在逻辑上我们数组的末尾和数组开头是连续的。比如图中末尾下标是7,起始下标为0。因此我们只需要一个可以满足下面要求的公式即可:

y={x+1x<max−10x=max−1y = \begin{cases} x+1&x<max-1 \\ 0&x = max-1 \end{cases}y={x+10x<max1x=max1

其中max就表示数组长度。比如最大长度为8,我们的尾指针指向下标7,如果队列还未满,我们入栈则尾指针指向下标0。这很容易让我们想到模运算。因此在我们移动首尾指针时操作应该如下:

Q->rear = (Q->rear + 1) % MAXSIZE;
Q->front = (Q->front + 1) % MAXSIZE;
复制代码

下面我们就可以着手实现一下循环队列。

(2)队列初始化

队列的初始化我们只需要将首尾指针指向0即可:

int InitSqQueue(SqQueue *Q){
    //首尾指针指向0
    Q->front = Q->rear = 0;
    return 1;
}
复制代码

因此后续我们判断栈是否为空的依据就是首尾指针是否相等。

(3)判断队列是否为空

当队列为空时,我们返回1,当队列非空时返回0:

int EmptySqQueue(SqQueue Q){
    return Q.rear == Q.front ? 1 : 0;
}
复制代码

(4)入队操作

入栈操作我们需要先判断队列是否满,如何将输入放入队尾之后:

int EnSqQueue(SqQueue *Q, ElemType elem){
    //如果队列满了,则返回0
    if((Q->rear + 1) % MAXSIZE == Q->front){
        return 0;
    }
    //将元素插入队尾之后
    Q->data[Q->rear] = elem;
    //移动队尾指针
    Q->rear = (Q->rear + 1) % MAXSIZE;
    return 1;
}
复制代码

(5)出队操作

出队操作和入队相反,我们要判断是否队空,以及操作队头指针:

int DeSqQueue(SqQueue *Q, ElemType *elem){
    //如果队空,则返回0
    if(EmptySqQueue(*Q)){
        return 0;
    }
    //获取队头元素
    *elem = Q->data[Q->front];
    //移动队头元素
    Q->front = (Q->front + 1) % MAXSIZE;
    return 1;
}
复制代码

其它一些操作这里就不实现了。

四、链队列的实现

链队列和链表十分相似,这里我们简单说一下。

(1)初始化

这里我们选择使用带头节点的链队列:

int InitLinkedQueue(LinkedQueue *Q){
    //创建头节点,让首尾指针指向头节点
    Q->front = Q->rear = (QNode*)malloc(sizeof(QNode));
    //如果内存不足,则返回0
    if(!Q->front){
        return 0;
    }
    //初始化头节点的指针域
    Q->front->next = NULL;
    return 1;
}
复制代码

(2)判断队空

在初始化时我们把队列首尾指针指向头节点,因此当首尾指针相同时队空:

int EmptyLinkedQueue(LinkedQueue Q){
    return Q.front == Q.rear ? 1 : 0;
}
复制代码

(3)入队操作

入队操作在队尾进行,因此我们只需要在队尾插入一个元素即可:

int EnLinkedQueue(LinkedQueue *Q, ElemType elem){
    //创建节点
    QNode  *s = (QNode*)malloc(sizeof(QNode));
    //内存不足,则返回0
    if(!s){
        return 0;
    }
    //给节点赋值
    s->data = elem;
    s->next = NULL;
    //在队尾插入节点
    Q->rear->next = s;
    //移动队尾指针
    Q->rear = s;
    return 1;
}
复制代码

(4)出队操作

出队操作有几个需要注意的点:

  1. 队头指针指向的是头节点,因此我们实际要删除的元素是front->next
  2. 在队列只剩一个元素时,我们要修改队尾指针

第一点很好理解,我们直接看代码:

int DeLinkedQueue(LinkedQueue *Q, ElemType *elem){
    //如果队空,则返回0
    if(EmptyLinkedQueue(*Q)){
        return 0;
    }
    //定义p指向要删除的元素
    QNode *p = Q->front->next;
    //获取要删除的元素值
    *elem = p->data;
    //移动队头指针
    Q->front->next = p->next;
    //如果删除了最后一个元素,则需要修改队尾指针
    if(Q->rear == p){
        Q->rear == Q->front;
    }
    free(p);
    return 1;
}
复制代码

我们看下图,此时队列只有一个元素,我们进行出队操作:

网络异常,图片无法展示
|

当我们删除最后一个节点后,尾指针指向了一片已经销毁的内存。而且在队列为空的情况,我们的首位指针也不相同。因此我们需要在删除最后一个节点时对队尾指针进行修改。

而判断是否是最后一个节点的条件就是,头节点(Q.front)的next是否是尾节点。

(5)销毁队列

因为我们是使用malloc函数申请的内存,因此我们还需要手动销毁内存:

int DestroyLinkedQueue(LinkedQueue *Q){
    QNode *p = Q->front, *s;
    //判断p是否移动到队尾的next
    while (p){
        //保存当前节点指针
        s = p;
        //p向后移动
        p = p->next;
        //销毁当前节点
        free(s);
    }
    return 1;
}
复制代码

到此我们就用顺序存储和链式存储两种方式实现了队列。

目录
相关文章
|
2月前
|
算法 数据处理 C语言
C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合
本文深入解析了C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合,旨在帮助读者掌握这一高效的数据处理方法。
58 1
|
2月前
|
存储 算法 搜索推荐
【趣学C语言和数据结构100例】91-95
本文涵盖多个经典算法问题的C语言实现,包括堆排序、归并排序、从长整型变量中提取偶数位数、工人信息排序及无向图是否为树的判断。通过这些问题,读者可以深入了解排序算法、数据处理方法和图论基础知识,提升编程能力和算法理解。
57 4
|
2月前
|
存储 机器学习/深度学习 搜索推荐
【趣学C语言和数据结构100例】86-90
本文介绍并用C语言实现了五种经典排序算法:直接插入排序、折半插入排序、冒泡排序、快速排序和简单选择排序。每种算法都有其特点和适用场景,如直接插入排序适合小规模或基本有序的数据,快速排序则适用于大规模数据集,具有较高的效率。通过学习这些算法,读者可以加深对数据结构和算法设计的理解,提升解决实际问题的能力。
51 4
|
2月前
|
存储 算法 数据处理
【趣学C语言和数据结构100例】81-85
本文介绍了五个经典算法问题及其C语言实现,涵盖图论与树结构的基础知识。包括使用BFS求解单源最短路径、统计有向图中入度或出度为0的点数、统计无向无权图各顶点的度、折半查找及二叉排序树的查找。这些算法不仅理论意义重大,且在实际应用中极为广泛,有助于提升编程能力和数据结构理解。
54 4
|
2月前
|
算法 数据可视化 数据建模
【趣学C语言和数据结构100例】76-80
本文介绍了五种图论算法的C语言实现,涵盖二叉树的层次遍历及广度优先搜索(BFS)和深度优先搜索(DFS)的邻接表与邻接矩阵实现。层次遍历使用队列按层访问二叉树节点;BFS利用队列从源节点逐层遍历图节点,适用于最短路径等问题;DFS通过递归或栈深入图的分支,适合拓扑排序等场景。这些算法是数据结构和算法学习的基础,对提升编程能力和解决实际问题至关重要。
56 4
|
2月前
|
存储 算法 vr&ar
【趣学C语言和数据结构100例】71-75
本文介绍了五个C语言数据结构问题及其实现,涵盖链表与二叉树操作,包括按奇偶分解链表、交换二叉树左右子树、查找节点的双亲节点、计算二叉树深度及求最大关键值。通过递归和遍历等方法,解决了理论与实际应用中的常见问题,有助于提升编程能力和数据结构理解。
47 4
|
2月前
|
存储 算法 C语言
【趣学C语言和数据结构100例】66-70
本书《趣学C语言和数据结构100例》精选了5个典型的数据结构问题及C语言实现,涵盖链表与数组操作,如有序集合的集合运算、有序序列表的合并、数组中两顺序表位置互换、三递增序列公共元素查找及奇偶数重排。通过详细解析与代码示例,帮助读者深入理解数据结构与算法设计的核心思想,提升编程技能。
39 4
|
2月前
|
存储 算法 C语言
【趣学C语言和数据结构100例】51-55
本文介绍了五个关于链表操作的C语言实现案例,包括删除单链表中的重复元素、从两个有序链表中查找公共元素、判断一个链表是否为另一链表的连续子序列、判断循环双链表是否对称及合并两个循环单链表。每个案例都详细解析了算法思路与实现方法,涵盖了链表操作的多种场景,旨在帮助读者深入理解链表数据结构的应用,提升算法设计与编程能力。
48 4
|
1天前
|
存储 C语言 C++
【C++数据结构——栈与队列】顺序栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现顺序栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 1.初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储
110 75
|
1天前
|
存储 C++ 索引
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
【数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】初始化队列、销毁队列、判断队列是否为空、进队列、出队列等。本关任务:编写一个程序实现环形队列的基本运算。(6)出队列序列:yzopq2*(5)依次进队列元素:opq2*(6)出队列序列:bcdef。(2)依次进队列元素:abc。(5)依次进队列元素:def。(2)依次进队列元素:xyz。开始你的任务吧,祝你成功!(4)出队一个元素a。(4)出队一个元素x。
24 13
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】