队列
队列的概念
队列,是一种特殊的线性表,只允许在一端进行插入数据操作,在另一端进行删除,进行插入操作的一端称为队尾出队列:进行删除操作的一端称为队头。队列的数据遵守先进后出的原则
队列就和我们日常生活中的排队一样,讲究先来后到
队列的实现
队列可以用数组或链表的结构实现。不过使用链表的结构实现更优一些,因为如果使用数组的结构,出队列在数组头上出数据,效率会比较低。
这里我们使用单链表即可,带不带头无所谓
队列的定义
typedef int QDataType;
//定义队列链表的结点
typedef struct QListNode
{
struct QListNode* next;
QDataType data;
}QNode;
//定义队列
typedef struct Queue
{
QNode* head;
QNode* tail;
int size;
}Queue;
主体结构是struct Queue
,这是我们定义的队列,由指向头尾结点的两个指针和队列长度三个要素组成
//在单链表中是没有这个结构体的。在这里存在的意义是队列需要头尾两个指针,所以就定义了这个结构体。单链表中只要指向头结点的指针即可
指向头尾结点的指针,分别用于实现头删和尾插
这里size存在的意义是:以空间换时间。如果完全依靠QueueSize来获得队列长度的话,必须要遍历队列。而我们现在定义一个size变量,当push或pop时改变。最后在得到队列长度时只需返回size即可,不再需要遍历
然后队列存储数据的主体形式是单链表,所以又定义了结点struct QListNode
队列的初始化
void QueueInit(Queue* qq)
{
assert(qq);
qq->head = NULL;
qq->tail = NULL;
qq->size=0;
}
队尾入队列
void QueuePush(Queue* qq, QDataType val)
{
assert(qq);
//先新建结点
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (newnode == NULL)
{
perror("malloc fail");
exit(-1);
}
newnode->data = val;
newnode->next = NULL;
//队尾入队列
if (qq->head == NULL)
{
qq->head = qq->tail = newnode;
}
else
{
qq->tail->next = newnode;
qq->tail = newnode;
}
qq->size++;
}
这里先要新建结点,再将其从队尾入队列
这里没必要像单链表那样还定义了一个BuyNode函数。整个队列只有这里需要新建结点,所以没必要再额外写个函数了
这里尾插(后续不用二级指针也同理),为什么不用传二级指针呢?因为我们将头指针放在了一个结构体中,然后将结构体地址传了过去。
队头出队列
void QueuePop(Queue* qq)
{
assert(qq);
//队列不能为空
assert(!QueueEmpty(qq));
if (qq->head->next == NULL)
{
free(qq->head);
qq->head = qq->tail = NULL;
}
else
{
//保存队列的第二个节点的地址
QNode* headnext = qq->head->next;
free(qq->head);
qq->head = headnext;
}
qq->size--;
}
这里要关注:head为空时,tail是否为空;或者tail为空时,head是否为空。如果不是,那说明代码出问题了。这一问题体现在代码中是这一句qq->head = qq->tail = NULL;
当队列中最后一个结点被删除时,我们不能只将head置空,tail也需要置空
获取队头数据
QDataType QueueFront(Queue* qq)
{
assert(qq);
//队列不能为空
assert(!QueueEmpty(qq));
return qq->head->data;
}
获取队尾数据
QDataType QueueBack(Queue* qq)
{
assert(qq);
//队列不能为空
assert(!QueueEmpty(qq));
return qq->tail->data;
}
判断队列是否为空
bool QueueEmpty(Queue* qq)
{
assert(qq);
return qq->head == NULL && qq->tail == NULL;
}
返回队列有效长度
int QueueSize(Queue* qq)
{
assert(qq);
return qq->size;
}
销毁队列
void QueueDestroy(Queue* qq)
{
assert(qq);
QNode* cur = qq->head;
while (cur)
{
QNode* curnext = cur->next;
free(cur);
cur = curnext;
}
qq->head = qq->tail = NULL;
qq->size = 0;
}
链表的销毁要通过遍历找到每一个结点,再销毁
再次说一下对malloc的空间置空的问题。如果被释放的空间别人可以访问到则需要置空,如果别人访问不到了,置不置空就无所谓了
所以队列的头尾指针一定要置空,但中间的就无所谓了