1.概 念 与 结 构
1.1.1 概念
只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First in First out)的特点。
1.1.2 入队列:
进行插入数据的一端称为队尾
1.1.3 出队列:
进行删除操作的一端称为队头
1.2 队列的底层结构类型的选择
队列可以基于数组和链表的结构来实现,使用链表的结构更优一点, 因为如果使用数组的结构,出队列在数组头上出数据,效率比较复杂。
2.队 列 的 实 现
根据前面的数据结构的实现我们实现队列的时候也是需要用到三个文件的,分别是Queue.h(队列的结构的定义以及函数的声明的文件),Queue.c(函数功能的实现文件),test.c(函数功能的测试文件)。
2.1 Queue.h(队列的结构的定义以及函数的声明的文件)
#include <stdio.h> #include <stdlib.h> #include <assert.h> #include <stdbool.h>
2.1.1 队列结构的定义
//队列结构的定义 typedef int QDatatype; typedef struct QueueNode { QDatatype data; struct QueueNode* next; }QueueNode; //是基于链表实现的,所以底层结构是链表,所以队列就是包装之后的链表 typedef struct Queue { QueueNode* phead; QueueNode* tail; int size; }Queue;
【定义结构的思路】:
我们选择的是基于单链表来定义并实现队列。所以我们就是相当于把链表进行包装了一下。故队列的结构是由一个一个的节点组成的;为了方便入队列,出队列我们需要把第一个有效节点和尾节点的位置保存下来;除此之外,我们为了方便记录对于一列中元素的个数,用一个size整型变量进行标记队列中元素的个数。 因此在这里队列就是链表包装后的结构。
2.1.2 函数的声明
//初始化 void QueueInit(Queue* pq); //入队列 void QueuePush(Queue* pq, QDatatype x); //判空 bool QueueEmpty(Queue* pq); //出队列 void QueuePop(Queue* pq); //获取队头数据 QDatatype QueueFront(Queue* pq); //获取队尾数据 QDatatype QueueBack(Queue* pq); //获取队列中有效的元素个数 int QueueSize(Queue* pq); //队列的销毁 void QueueDestroy(Queue* pq);
2.2 Queue.c(函数功能的实现文件)
#include "Queue.h"
2.2.1 队 列 的 初 始 化
//初始化 void QueueInit(Queue* pq) { assert(pq); pq->phead = pq->tail = NULL; pq->size = 0; }
【实现思路】:
先判断所传地址的有效性,再将指向头结点和指向尾节点的指针置为空,最后将队列中 元素个数置为0。
2.2.2 入 队 列
//入队列 一端入(队尾),一端出数据 void QueuePush(Queue* pq, QDatatype x) { assert(pq); QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode)); if (newnode == NULL) { perror("malloc fail"); exit(1); } newnode->data = x; newnode->next = NULL; //分为两种情况 //队列为空 if (pq->phead == NULL) { pq->phead = pq->tail = newnode; } //队列为非空 else { pq->tail->next = newnode; pq->tail = newnode; } pq->size++; }
【实现思路】:
先进行判断所传地址的有效性,通过malloc 函数进行申请一个新节点,将所要插入数据放到新节点里面,然后将新节点里的next置为空即可。
队列为空是所插入的数据(新节点)既是头又是尾,此时需要改变头节点;然而当队列非空时,插入的数据(新节点)直接向队尾插入就行,头结点并不需改变(先将新节点和原来的链表结构连接起来,再将指向为节点的指针指向新节点的位置)。
2.2.3 队列判空
//判空 bool QueueEmpty(Queue* pq) { assert(pq); return pq->size == 0;//pq->phead == NULL && pq->tail == NULL; }
【实现思路】:
先判断所传地址的有效性。 在这里判空的时候可以直接判断对列入中有效的元素个数,也可以进行判断指向队头和队尾节点的指针(地址)是否为空。
2.2.4 队列的数据的删除
//出队列 void QueuePop(Queue* pq) { assert(pq); assert(!(QueueEmpty(pq))); //分为两种情况 //分为队列中只有一个节点 if (pq->phead == pq->tail&&pq->phead->next==NULL) { free(pq->phead); pq->phead = pq->tail = NULL; } //队列中有两个节点 else { QueueNode* next = pq->phead->next; free(pq->phead); pq->phead = next; } pq->size--; }
【实现思路】:
先判断所春地址的有效性,再进行队列判空,如果队列为空就不能进行数据的删除,队列非空就可以进行删除操作。
当队列中只有一个节点的时候,删除(释放节点)一次之后队列中就没有数据了,此时就需要将头节点和尾节点都置为空;当队列中节点的个数大于一个时,需要先将头节点中next 所指向的像一个节点的位置保存在next中,然后再释放掉头节点,再将头节点指向所保存的next的位置。
2.2.5 获取队头数据
//获取队头数据 QDatatype QueueFront(Queue* pq) { assert(pq); return pq->phead->data; }
【实现思路】:
先判断所传地址的有效性,再直接将头结点中的值返回即可。
2.2.6 获取队尾数据
//获取队尾数据 QDatatype QueueBack(Queue* pq) { assert(pq); return pq->tail->data; }
【实现思路】:
先判断所传节点的有效性,再直接将尾节点中的值返回即可。
2.2.7 获取队列中的有效元素的个数
//获取队列中有效的元素个数 int QueueSize(Queue* pq) { assert(pq); return pq->size; }
【实现思路】:
先判断所传节点的有效性,再直接将队列中size返回即可。
2.2.8 队列的销毁
//队列的销毁 void QueueDestroy(Queue* pq) { assert(pq); assert(!QueueEmpty(pq)); QueueNode* pcur = pq->phead; while (pcur) { QueueNode* next = pcur->next; free(pcur); pcur = next; } pq->phead = pq->tail = NULL; pq->size = 0; }
【实现思路】:
先判断所传地址的有效性,再进行队列判空。销毁链表就可以从队头向队尾逐个释放节点。
先用pcur来指向当前的节点,然后用循环来释放每一个节点。在释放每一个节点的之前先将当前节点的下一个节点保存在next中,然后再释放掉当前节点,再将当前节点pcur指向next的位置。所有的节点释放完成之后,将指向队列头结点(队头)和尾节点(队尾)的指针都置为空,将队列的有效元素个数置为0。
2.3 test.c(函数功能的测试文件)
#include "Queue.h" //ܵIJԲ void test() { Queue q; QueueInit(&q); QueuePush(&q, 1); QueuePush(&q, 92); QueuePush(&q, 53); QueuePush(&q, 74); /*while (!QueueEmpty(&q)) { printf("%d ", QueueFront(&q)); QueuePop(&q); } printf("\n");*/ printf("head:%d ", QueueFront(&q)); printf("tail:%d\n", QueueBack(&q)); printf("size:%d \n", QueueSize(&q)); QueuePop(&q); printf("head:%d ", QueueFront(&q)); printf("tail:%d\n", QueueBack(&q)); //QueuePop(&q); printf("size:%d \n", QueueSize(&q)); QueueDestroy(&q); } int main() { test(); return 0; }
该函数功能的测试文件,每写完一个函数就进行函数功能测试,养成良好的代码习惯。