目录
一、定义
队列:只允许在一端插入数据,在另一端删除数据操作的特殊线性表,队列具有先进先出的特性。
入队列:进行插入操作的一端称为队尾
出队列:进行删除操作的一端称为队头
在实现队列时,使用单链表实现较为合适。
首先,如果使用数组实现,在队首出队后,后面数据需要依次前移,如果数据多,效率会很低。
使用单链表,在队首出队后,只需要将队首指针再指向原先队首元素指向的下一个数据,操作方便。
二、程序实现
1、结构体声明
同样,队列的实现也依赖结构体。不同于栈的结构体,队列结构体内只需要两个数据成员,一个是指向队列头部的队头指针,一个是指向最后一个元素的队尾指针。根据这两个指针执行插入删除操作。
typedef struct Queue{
QueueNode* head;
QueueNode* tail;
}Queue;
同时,队列元素因为是单链表,因此定义结点的结构体。数据空间与指向下一数据元素的指针。
typedef struct QueueNode{
DataType data;
struct QueueNode* next;
}QueueNode;
2、队列初始化及插入元素
队列初始化:因最初队列内无数据,队头指针、队尾指针无数据可指向。因此将对头指针、队尾指针指向NULL。
void QueueInit(Queue* ps)
{
ps->head=ps->tail=NULL;
}
数据元素初始化:在数据插入时进行初始化。在插入时,先利用malloc函数,开辟一个数据元素空间结点,将data设为要求的数据,naxt指向NULL。然后,队列的头、尾指针需要与插入的结点连接。此时需要分析情况。
①如果队列头指针为NULL,说明此前队列内数据为空,队尾指针同样为NULL,插入新结点后仍然只有一个结点,因此头、尾指针均指向插入结点。
②若队列头指针不为NULL,说明队列至少存在一个元素。此时头指针不需要指向新插入结点。但是尾指针需要指向新插入结点。先通过尾指针的next,将链表链接上插入结点,再将尾指针的值设为插入结点的地址。
void QueuePush(Queue* ps,DataType data)
{
QueueNode* newone=(QueueNode*)malloc(sizeof(QueueNode));
if(newone==NULL){
printf("队列扩容失败");
exit(-1);
}else{
newone->data=data;
newone->next=NULL;
if(ps->head==NULL){
ps->head=ps->tail=newone;
}
else{
ps->tail->next=newone;//将链表最后一个结点链接上新插入结点
ps->tail=newone;//将尾指针的值设为新插入结点地址
}
}
}
3、删除数据
删除数据时,同样需要分情况处理。①只有一个数据,删除后头、尾指针均需要指向NULL。②如果有多个数据,尾指针不动,头指针需要指向下一个数据,再free删除的数据的空间。先将要删除的结点的下一个结点的地址保存下来(如果先free的话,第二个结点的地址就找不到了),再free第一个结点。ps->head是当前头结点地址,ps->head->next是第二个结点地址。
void QueuePop(Queue* ps)
{
assert(ps);
assert(ps->head);
if(ps->head->next==NULL){
free(ps->head);
ps->head=ps->tail=NULL;
}else{
QueueNode* temp=ps->head->next;//先将要删除的结点的下一个结点的地址保存下来。ps->head是当前头结点地址,ps->head->next是第二个结点地址。
free(ps->head);释放第一个结点空间
ps->head=temp; //将头指针的值设为原先的第二个结点地址
}
}
4、释放空间
在释放队列时,需要遍历队列内每一个结点,依次释放。定义一个当前结点指针cur,先通过cur->next保存下一节点地址,再释放cur指向的空间。当cur为NULL时结束。然后再将队列的头、尾指针指向NULL。
void QueueDestory(Queue* ps)
{
assert(ps);
QueueNode* cur=ps->head;
while(cur){
QueueNode* temp=cur->next;
free(cur);
cur=temp;
}
ps->head=ps->tail=NULL;
}