队列
队列只允许在一端进行插入数据,一端进行删除数据的特殊线性表
队尾:插入数据的一端(入队列)
队头:删除数据的一端(出队列)
队列只能先进先出
入栈(一种):1 2 3 4 出栈(多种):可以边进边出
入队列(一种):1 2 3 4 出队列(一种):1 2 3 4
因为它只能队尾进,队头出,边进边出也是这一种
队列的实际应用
1.队列用于保持公平性
比如:抽号码,先抽的人先排队(使用),后抽的人后排队(使用)
假设我不知道新的号,要取队尾数据加一
取队头数据,再取队中或队尾数据,相减,得到前面有多少个人,比如取3和取8,(8 - 3 = 5人)
2.生产者消费者模型:
生产者:取号的人,比如扫一下码就多插入一个号
消费者:机器,有人使用了,把号删除
比如:医院排号
生产号:排号(插入号)
医院:消费号(删除号)
3.如果医院两个窗口同时叫到一个人
那这个人怎么办?
这时候就有锁的概念了(锁在后面的操作系统锁的竞争和并发也会学习),访问队列先获取锁,获取锁的人先使用,锁占用会阻塞,等到前面解锁了再进去
有多线程的概念,会同时取号
(比如3号,8号窗口同时取3号患者)
后面会详细学习,不用担心
4.广度优先遍历
二叉数,图和队列都会涉及到广度优先遍历
以点的形式一圈一圈向外遍历,例如水纹
举个例子,qq好友推荐
这些连线叫做边
要给小徐推荐好友就要找到它好友的好友
以小徐为点,要找到小徐好友的好友
1.小徐进队列,之后小徐出队列,小明和小王就进队列
2.小明和小王出队列,小张和小花就进队列,就找到了小徐的好友的好友
小徐的好友出队列,那么小徐的好友的好友进队列
怎么判断好友的好友出来了?
计数,这个我也不太懂,等后面详细学习广度优先遍历再来补充
队列的实现
队列选择数组还是链表来实现呢?
数组:数组出队列不好实现,每次出队列都要挪动数据
所以我们选择链表实现队列
链表:链表头删和尾插都很好,有一个头指针和一个尾指针
队列的结构
//队列的节点的结构
typedef int QDataType;
typedef struct QueueNode
{
QDataType val;
struct QueueNode* next;
}QNode;
//队头删除
//void QueuePop(QNode** pphead, QNode** pptail, QDataType x);
//队尾插入
//void QueuePush(QNode** pphead, QNode** pptail);
typedef struct Queue
{
QNode* phead;//指向节点的指针
QNode* ptail;
int size;//标队列的大小
}Queue;
队列有两个指针,头指针和尾指针,为了头删和尾插
把两个指针封装成一个结构体,就不用使用多个参数(头尾指针)和二级指针了
结构体指针就可以改变结构体中的值
也可以改变结构体中指针的值(指向)
为什么是使用二级指针呢?
假设开始队列为空,phead和ptail都指向NULL
要改变一级指针的指向(值)要使用二级指针,解引用改变一级指针
初始化和销毁
//初始化
void QueueInit(Queue* pq);
//销毁
void QueueDestroy(Queue* pq);
//初始化
void QueueInit(Queue* pq)
{
assert(pq);
//没有pq这个结构体就不用初始化了
pq->phead = NULL;
pq->ptail = NULL;
pq->size = 0;
}
//销毁
void QueueDestroy(Queue* pq)
{
assert(pq);
//没有pq这个结构体就不用销毁了
QNode* cur = pq->phead;
while (cur)
{
QNode* next = cur->next;
free(cur);
cur = next;
}
//把节点一个一个地释放掉
pq->phead = NULL;
pq->ptail = NULL;
pq->size = 0;
}
队头删除和队尾插入
//队头删除
void QueuePop(Queue* pq);
//队尾插入
void QueuePush(Queue* pq,QDataType x);
//队头删除
void QueuePop(Queue* pq)
{
assert(pq);
assert(pq->size != 0);
//判断删空了的情况
if (pq->phead->next == NULL)
{
//一个节点
free(pq->phead);
pq->phead = pq->ptail = NULL;
}
else
{
//多个节点
QNode* cur = pq->phead->next;
free(pq->phead);
pq->phead = cur;
}
//忘记处理一个节点和多个的情况了,剩一个节点不处理的话会出现,pq->ptail会成为野指针
pq->size--;
}
//队尾插入
void QueuePush(Queue* pq, QDataType x)
{
assert(pq);
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (newnode == NULL)
{
perror("QueuePush:malloc");
return;
}
//空间开辟成功
newnode->next = NULL;
newnode->val = x;
if (pq->ptail == NULL)
{
//没有节点
pq->phead = pq->ptail = newnode;
}
else
{
//有空间(节点)
pq->ptail->next = newnode;
pq->ptail = newnode;
}
pq->size++;
}
队头删除必须区分是不是一个节点和多个节点的情况
取队头数据和队尾数据
//取队头数据
QDataType QueueTop(Queue* pq);
//取队尾数据
QDataType QueueTail(Queue* pq);
//取队头数据
QDataType QueueTop(Queue* pq)
{
assert(pq);
assert(pq->size != 0);
//assert(pq->phead);或者头指针是空,没有数据
//队列大小不为空,才能取队头的数据
return pq->phead->val;
}
//取队尾数据
QDataType QueueTail(Queue* pq)
{
assert(pq);
assert(pq->size != 0);
//assert(pq->ptail);或者尾指针是空,没有数据
//队列大小不为空,才能取队尾的数据
return pq->ptail->val;
}
取队列的大小
//队列的大小
int QueueSize(Queue* pq);
//队列的大小
int QueueSize(Queue* pq)
{
assert(pq);
return pq->size;
//size就是队列的大小
//如果不设计size,每次都要遍历链表算出size的大小(增减数据size会变化)
}
队列的判空
//队列的判空
bool QueueEmpty(Queue* pq);
//队列的判空
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->size == 0;
//size为0返回true,真为空
//size为非0返回false,假为有数据,非空
}