前言
队列是数据结构中内存存储数据的一种方式,他的特点是先进先出,固定一端进和出。
实现思路
一般队列的实现是用链式结构,因为链式结构使得队列在出队列和容量上显得非常便利。所以,一般都是用链表的结构。和其他的数据结构大致方向一样,都是建立三个文件,Queue.h(头文件引用和#define等等)、Queue.c(函数的书写)、test.(测试队列)。
队列的函数主要分为:初始化和销毁、队尾入队列和队头出队列、获取队列尾部元素和获取队列头部元素、获取队列有效数据个数、检测队列是否为空
具体思路
Queue.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>
//队的结点 链式节点
typedef int QDataType;
typedef struct QueueNode
{
struct QueueNode* next;
QDataType data;
}QNode;
typedef struct Queue
{
QNode* head;
QNode* tail;
int size;
}Queue;
//初始化队列
void QueueInit(Queue* pq);
//销毁队列
void QueueDestroy(Queue* pq);
//队尾入队列
void QueuePush(Queue* pq, QDataType x);
//队头出队列
void QueuePop(Queue* pq);
//获取队列头部yuansu
QDataType QueueFront(Queue* pq);
//获取队列尾部元素
QDataType QueueBack(Queue* pq);
//获取队列中有效数据个数
int QueueSize(Queue* pq);
//检测队列是否为空(检空)
bool QueueEmpty(Queue* pq);
队列的创建不同于顺序表和链表,他需要两个结构体,这是有他的功能实现层面要求的。如果你只有一个结构体,那你这个结构体既有全局信息的又有局部信息,这就很臃肿,不符合功能单一这一原则,并前维护起来也很复杂。但如果你创建了两个结构体来维护队列,结构清晰,逻辑严谨,维护起来就很简单,全局信息和局部信息分起来维护,责任清晰,函数实现起来也很简便。
Queue.c
初始化和销毁
初始化
void QueueInit(Queue* pq)
{
pq->head = pq->tail = NULL;
pq->size = 0;
}
和链表一样,只不过我们这里把它包装成了一个 函数,对头指针和尾指针指置空就好了
销毁
void QueueDestroy(Queue* pq)
{
assert(pq);
QNode* cur = pq->head;
while (cur)
{
Queue* next = cur->next;
free(cur);
cur = next;
}
pq->head = pq->tail = NULL;
pq->size = 0;
}
这里的销毁链表一样,一个一个释放。
队尾入队列和队头出队列
队尾入队列
void QueuePush(Queue* pq, QDataType x)
{
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (newnode == NULL)
{
perror("Malloc Failed\n");
return;
}
newnode->next = NULL;
newnode->data = x;
if (pq->head == NULL)
{
assert(pq->tail == NULL);
pq->head = pq->tail = newnode;
}
else
{
pq->tail->next = newnode;
pq->tail = newnode;
}
pq->size++;
}
这里其实就是链表的尾插,但不过我们这里有了尾指针,可以直接插入,不用在遍历一遍了,大大提高了效率。情况的分析也是和链表尾插差不多。
情况一:
头指针就是NULL,所以直接把新的节点赋值给头指针和尾指针就行了。这里需要做一下检查,断言一下头指针为NULL的情况下尾指针是否为为NULL。虽然正常情况是尾指针和头指针一样都为NULL,但这里我们以防万一,防止别人篡改。
情况二:
头指针不为空,如果是链表这里还需要遍历,但我们这里有了尾指针就可以直接插入。
最后不要忘记,size++。
队头出队列
void QueuePop(Queue* pq)
{
assert(pq);
assert(pq->head != NULL);
if (pq->head->next == NULL)
{
free(pq->head);
pq->head = pq->tail = NULL;
}
else
{
QNode* next = pq->head->next;
free(pq->head);
pq->head = next;
}
pq->size--;
}
这里其实就是链表的头删,但这里不同的是。如果我们按照链表的尾插就会出现野指针的情况。所以,我们还需要做一下处理。
处理方式一:
在原先的链表尾插的代码基础上加一个if语句
void QueuePop(Queue* pq)
{
assert(pq);
assert(pq->head != NULL);
QNode* next = pq->head->next;
free(pq->head);
pq->head = next;
if (pq->head == NULL)
{
pq->tail = NULL;
}
pq->size--;
}
处理方式二:
直接分类处理,判断头指针的next是否NULL,如果为空,直接释放头指针再把尾指针和头指针置空。如果不为空,按照链表的方式处理。我的代码就是这种处理方式。
void QueuePop(Queue* pq)
{
assert(pq);
assert(pq->head != NULL);
if (pq->head->next == NULL)
{
free(pq->head);
pq->head = pq->tail = NULL;
}
else
{
QNode* next = pq->head->next;
free(pq->head);
pq->head = next;
}
pq->size--;
}
最后不要忘记,size--。
获取队列尾部元素和获取队列头部元素
按照队列的特点,先进先出,不应该出现尾部元素先出的函数。但是,很多地方的时候都会用到这个函数,很多题目也会用到。所以,我们不妨创建一个。这两个函数都有一个共性,就是检空。我们在获取队列的数据时要检查一下队列是否由数据,防止野指针问题出现。
获取队列尾部元素
QDataType QueueFront(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->head->data;
}
这里尾指针的作用体现的就是淋漓尽致了,不用再像链表一样遍历,直接尾插就欧克了。
获取队列头部元素
QDataType QueueBack(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->tail->data;
}
这里就是加简单的return头指针的data,So Easy!
获取队列有效数据个数
int QueueSize(Queue* pq)
{
assert(pq);
return pq->size;
}
这里就体现了我们为什么要定义size了,就是方便获取队列长度,不用像链表那样,还要一个一个遍历。
检测队列是否为空
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->size == 0;
}
使用bool类型,再进行等号判断,返回值
代码
#include "Queue.h"
void QueueInit(Queue* pq)
{
pq->head = pq->tail = NULL;
pq->size = 0;
}
void QueueDestroy(Queue* pq)
{
assert(pq);
QNode* cur = pq->head;
while (cur)
{
QNode* next = cur->next;
free(cur);
cur = next;
}
pq->head = pq->tail = NULL;
pq->size = 0;
}
void QueuePush(Queue* pq, QDataType x)
{
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (newnode == NULL)
{
perror("Malloc Failed\n");
return;
}
newnode->next = NULL;
newnode->data = x;
if (pq->head == NULL)
{
assert(pq->tail == NULL);
pq->head = pq->tail = newnode;
}
else
{
pq->tail->next = newnode;
pq->tail = newnode;
}
pq->size++;
}
void QueuePop(Queue* pq)
{
assert(pq);
assert(pq->head != NULL);
/*QNode* next = pq->head->next;
free(pq->head);
pq->head = next;
if (pq->head == NULL)
{
pq->tail = NULL;
}*/
if (pq->head->next == NULL)
{
free(pq->head);
pq->head = pq->tail = NULL;
}
else
{
QNode* next = pq->head->next;
free(pq->head);
pq->head = next;
}
pq->size--;
}
QDataType QueueFront(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->head->data;
}
QDataType QueueBack(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->tail->data;
}
int QueueSize(Queue* pq)
{
assert(pq);
return pq->size;
}
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->size == 0;
}
总结
队列的实现和链表的实现很相似,但是不同之处是,他在继承了链表的优点之后又克服了链表的很多缺点,不如在求长度时就很方便,不用再一个一个遍历。