队列的实现
目录
1.概念与结构
2.队列的实现
2.1 初始化
2.2 入队列
2.3 判断队列是否为空
2.4 出队列
2.5 取队头数据和取队尾数据
2.6 队列有效元素个数
2.7 销毁
3.总结上述代码
我们解决算法题的时候常常会有不止一种方法来实现,所以不用的数据结构常常给人不同的想法,今天我们来看看队列的实现。
1.概念与结构
概念:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出的性质。
入队列:进行插入操作的一端称为队尾。
出队列:进行删除操作的一端称为对头。

下面进行实现的时候可以根据这张图片来进行操作。
队列底层结构选型:队列也可以数据和链表的结构实现,使用链表的结构实现更优一些,因为如果使用数组的结构,出队列在数组头上出数据,效率会比较低。
数组来实现时:插入数据的时间复杂度为O(1),删除数据的时间复杂度为O(n)。
链表来实现时:插入数据的时间复杂度为O(n),删除数据的时间复杂度为O(1)。
这样比较可能并没有对比性,如果时间复杂度比不了,那就比较空间复杂度。
数组的初始化空间有三项:
struct List
{SLDataType* a;int size;int capacity;
};
链表的初始化空间有两项:
struct List
{SLTDataType data;struct List* next;
};
这样一比较,就是链表的空间复杂度就比数组的空间复杂度更优一些。所以我们选择链表作为队列底层结构的选型。
2.队列的实现
首先,我们还是和之前实现的数据结构一样,创建一个头文件放函数的声明,创建一个.c文件放函数的实现,创建一个.c文件放主体函数。
首先我们要先进行队列结构的代码编写,如下:
//队列结构
struct Queue
{*phead;*ptail;
};
这样写那队列的节点类型就不知道了,而要定义队列节点的类型就只能再创建一个结构体。
typedef int QDataType;//队列节点结构
typedef struct QueueNode
{QDataType data;struct QueueNode* next;
}QueueNode;//队列结构
typedef struct Queue
{QueueNode* phead;QueueNode* ptail;
}Queue;
我们把队列重命名就可以得到以上代码。
队列的结构都解释完了,那么下面就开始初始化了。
2.1 初始化
跟之前链表定义一样,初始化就直接写了。
Queue.h
//初始化
void QueueInit(Queue* pq);
Queue.c
//初始化
void QueueInit(Queue* pq)
{assert(pq);pq->phead = pq->ptail = NULL;
}
写完这些代码后,我们再去test.c文件里去执行看看问题。

看调试结果得知,初始化完成。
2.2 入队列
先进行代码的编写,再根据代码结合图片进行解释。
Queue.h
//入队列--队尾
void QueuePush(Queue* pq, QDataType x);
Queue.c
//入队列--队尾
void QueuePush(Queue* pq, QDataType x)
{assert(pq);//创建一个新节点QueueNode* newnode = (QueueNode*)malloc(sizeof(QDataType));if (newnode == NULL){perror("malloc");exit(1);}newnode->data = x;newnode->next = NULL;//队列为空if (pq->phead == NULL){pq->phead = pq->ptail = newnode;}else{pq->ptail->next = newnode;pq->ptail = pq->ptail->next;}
}
首先,入队列需要保证形参不能为空。进行创建一个新的节点newnode时需要用动态内存管理中的函数malloc函数。再进行判断newnode节点是否成功创建,如果不成功,则打印错误信息并退出。然后让newnode节点的数据进行整理。既然从队尾入队列,那么就有两种情况,第一种就是如果队列为空,那么对头和队尾都应该等于newnode节点。第二种就是队列中有数据,那么只让队尾保存的next指针指向newnode节点,再让队尾往后走就行了。这样就是入队成功。
test.c
//入队列--队尾QueuePush(&q, 1);QueuePush(&q, 2);QueuePush(&q, 3);

如图调试可得:对头为1,队尾为3,入队列成功。
2.3 判断队列是否为空
这里需要用到布尔类型,使用布尔类型时需要头文件#include<stdbool.h>。
Queue.h
//判断队列是否为空
bool QueueEmpty(Queue* pq);
Queue.c
//判断队列是否为空
bool QueueEmpty(Queue* pq)
{assert(pq);return pq->phead == NULL;
}
判断队列是否为空,这里传队列的时候pq不能为空则找到队列。如果队列为空时,则对头为NULL。
2.4 出队列
Queue.h
//出队列--对头
void QueuePop(Queue* pq);
Queue.c
//出队列--对头
void QueuePop(Queue* pq)
{assert(!QueueEmpty(pq));//队列中只有一个节点if (pq->phead == pq->ptail){free(pq->phead);pq->phead = pq->ptail = NULL;}else{QueueNode* next = pq->phead->next;free(pq->phead);pq->phead = next;}
}
出队列首先要确定的是队列不能为空,所以加了断言报错。队列中有两种情况,第一种是队列中只有一个节点,那就是对头和队尾都是同一个节点,我们把它释放掉,成为野指针后再置为NULL。第二种是队列中不止一个节点,我们需要出队列释放头结点时,需要将头节点的下一个节点进行保存,如果没保存释放掉头节点的话,就找不到下一个节点了。所以定义一个节点next为头节点的下一个节点,释放头节点后,并重新将头节点指向next节点。



调试如图所示,执行一步,便出队一个节点。
2.5 取队头数据和取队尾数据
Queue.h
//取队头数据
QDataType QueueFront(Queue* pq);
//取队尾数据
QDataType QueueBack(Queue* pq);
Queue.c
//取队头数据
QDataType QueueFront(Queue* pq)
{assert(!QueueEmpty(pq));return pq->phead->data;
}
//取队尾数据
QDataType QueueBack(Queue* pq)
{assert(!QueueEmpty(pq));return pq->ptail->data;
}
取队头数据和取队尾数据的前提都是队列不能为空,如果队列为空,就取不到队头和队尾数据了。返回值用QDataType来接收,所以不要用错数据类型和返回值类型。

2.6 队列有效元素个数
Queue.h
//队列有效元素个数
int QueueSize(Queue* pq);
Queue.c
//队列有效元素个数
int QueueSize(Queue* pq)
{assert(pq);QueueNode* pcur = pq->phead;int size = 0;while (pcur){++size;pcur = pcur->next;}return size;
}
要想求队列有效元素个数,则pq不能为空,如果为空,就找不到队列,所以断言报错。定义一个pcur节点的指针指向头节点。再定义一个计数器。进入循环遍历,如果pcur不等于NULL,就让size进行++,pcur往后走。跳出循环则有效个数求出。

2.7 销毁
Queue.h
//销毁
void QueueDestroy(Queue* pq);
Queue.c
//销毁
void QueueDestroy(Queue* pq)
{assert(pq);QueueNode* pcur = pq->phead;while (pcur){QueueNode* next = pcur->next;free(pcur);pcur = next;}pq->phead = pq->ptail = NULL;
}
要销毁链表,就先找到链表,所以先加断言报错。定义一个pcur指针指向头节点。让pcur进行循环遍历,如果pcur不为空,再定义一个next指针指向pcur的下一个节点,因为释放掉pcur之后得需要找到下一个节点。最后让pcur等于next就成功销毁第一个节点。循环往复,pcur为NULL时,销毁完毕。只剩下两个队头和队尾指针为野指针,再对他们置为NULL,销毁完毕。
3.总结上述代码
Queue.h
#pragma once#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>typedef int QDataType;typedef struct QueueNode
{QDataType data;struct QueueNode* next;
}QueueNode;typedef struct Queue
{QueueNode* phead;QueueNode* ptail;
}Queue;//初始化
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);
Queue.c
#include"Queue.h"//初始化
void QueueInit(Queue* pq)
{assert(pq);pq->phead = pq->ptail = NULL;
}//入队列--队尾
void QueuePush(Queue* pq, QDataType x)
{assert(pq);//创建一个新节点QueueNode* newnode = (QueueNode*)malloc(sizeof(QDataType));if (newnode == NULL){perror("malloc");exit(1);}newnode->data = x;newnode->next = NULL;//队列为空if (pq->phead == NULL){pq->phead = pq->ptail = newnode;}else{pq->ptail->next = newnode;pq->ptail = pq->ptail->next;}
}//判断队列是否为空
bool QueueEmpty(Queue* pq)
{assert(pq);return pq->phead == NULL;
}//出队列--队头
void QueuePop(Queue* pq)
{assert(!QueueEmpty(pq));//队列中只有一个节点if (pq->phead == pq->ptail){free(pq->phead);pq->phead = pq->ptail = NULL;}else{QueueNode* next = pq->phead->next;free(pq->phead);pq->phead = next;}
}//取队头数据
QDataType QueueFront(Queue* pq)
{assert(!QueueEmpty(pq));return pq->phead->data;
}
//取队尾数据
QDataType QueueBack(Queue* pq)
{assert(!QueueEmpty(pq));return pq->ptail->data;
}//队列有效元素个数
int QueueSize(Queue* pq)
{assert(pq);QueueNode* pcur = pq->phead;int size = 0;while (pcur){++size;pcur = pcur->next;}return size;
}//销毁
void QueueDestroy(Queue* pq)
{assert(pq);QueueNode* pcur = pq->phead;while (pcur){QueueNode* next = pcur->next;free(pcur);pcur = next;}pq->phead = pq->ptail = NULL;
}
test.c
#include"Queue.h"void test01()
{Queue q;//初始化QueueInit(&q);//入队列--队尾QueuePush(&q, 1);QueuePush(&q, 2);QueuePush(&q, 3);////出队列--队头//QueuePop(&q);//QueuePop(&q);//QueuePop(&q);////取队头数据//int ret = QueueFront(&q);//printf("队头:%d\n", ret);////取队尾数据//int pop = QueueBack(&q);//printf("队尾:%d\n", pop);//队列有效元素个数/*printf("%d\n", QueueSize(&q));*///销毁QueueDestroy(&q);}int main()
{test01();return 0;
}
总结,数据结构就是要别画图理解代码,别看图想解决代码的过程。我们可以一起进步,加油!
