一 概念和结构
概念:只允许在一端进行插入操作,在另一端进行删除操作的特殊线性表,队列具有先进先出的特点。
入队列:进行插入操作的一端叫做队尾
出队列:进行删除操作的一端叫做队头
可以把队列想象成购票的队伍,游客想要买票就需要从队尾进入到队列进行排队,想要出队就需要排到队头。
出栈 __________________ 入栈
<------- OOOOOOOOO <---------O
二 队列的实现
如何选型:
数组:
队头\]\[ \]\[ \]\[ 队尾\] 入队:O(1);出队:O(N) \[队尾\]\[ \]\[ \]\[ 队头\] 入队:O(N);出队:O(1) **单链表:** \[队头\]-\>\[\]-\>\[队尾\] 入队O(N);出队O(1) \[队尾\]-\>\[\]-\>\[队头\] 入队O(1);出队O(N) **双链表:** 一个结点会有三个数据,所耗空间太大,所以不考虑
假设队列底层用链表实现,如何将入队操作优化成O(1);
这里就需要用到结构体嵌套的知识点了,假设队列的结构中我们只关注两个结点,一个头结点,一个尾结点,这样入队的操作直接使用尾结点。
cpp
struct Queue
{
结点*head;
结点*ptail;
}
到这里就可以发现,head和ptail就是指向结点的指针,那么该"结点"就需要单独定义一次,也就是结点的结构
cpp
struct QueueNode
{
int data;
struct QueueNode*next;//指向下一个结点
}
所以总结下来,就是利用结构嵌套实现队列。

接下来是功能的实现
cpp
//初始化
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);
//销毁队列
void QueueDestrory(Queue * pq);
//队列有效元素个数
int QueueSize(Queue* pq);
1.初始化
直接把两个结点置空即可。
cpp
//初始化
void QueueInit(Queue* pq)
{
assert(pq);
pq->phead = pq->ptail = NULL;
}
2.入队--队尾
先开辟新空间,然后注意判断队列是否为空即可
若为空,直接让head 和 ptail 指向新结点
若不为空,连接队尾即可,记得把ptail重置到新队尾。
cpp
//入队--队尾
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->ptail = newnode;
}
else
{
//队列非空
pq->ptail->next = newnode;
pq->ptail = pq->ptail->next;
}
}
3.队列判空
直接判断头结点即可。
cpp
//队列判空
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->phead == NULL;
}
4.出队--队头
要注意的是,当head==ptail时,也就是只有一个结点的时候,free掉head后,ptail也会被free掉,若不把他置为空,他就会变为野指针。所以此时要phead=patil=NULL
cpp
//出队--队头
void QueuePop(Queue* pq)
{
assert(!QueueEmpty(pq));
//只有一个节点,phead和ptail都套置为空
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;
}
}
5.取队头数据
直接取phead的data即可。
cpp
//取队头数据
QDataType QueueFront(Queue* pq)
{
assert(!QueueEmpty(pq));
return pq->phead->data;
}
6.取队尾数据
直接取ptail的data即可。
cpp
//取队尾数据
QDataType QueueBack(Queue* pq)
{
assert(!QueueEmpty(pq));
return pq->ptail->data;
}
7.销毁队列
与单链表的销毁一样,逐个销毁即可,最后要把phead和ptail都置为空。
cpp
//销毁队列
void QueueDestrory(Queue* pq)
{
assert(pq);
QueueNode* pcur = pq->phead;
while (pcur)
{
QueueNode* next = pcur->next;
free(pcur);
pcur = next;
}
pq->phead = pq->ptail = NULL;
}
8.队列有效元素个数
分为两种方法
第一种遍历链表,时间复杂度为O(1),适合不经常调用的情况使用。
第二种的时间复杂度是O(1),适合经常调用的情况下使用。
cpp
//队列有效元素个数
int QueueSize(Queue* pq)
{
assert(pq);
//法一:遍历链表--适合不常调用QueueSize
int size = 0;
QueueNode* pcur = pq->phead;
while (pcur)
{
size++;
pcur = pcur->next;
}
return size;
}
cpp
int QueueSize(Queue* pq)
{
assert(pq);
//法二:适合常调用QueueSize
return size;
}
这里要注意的是,若使用法二,那么在队列的结构中就需要增加size元素,也就是用空间换时间。那么一些改变队列元素的工具也需要修改:




