前言:在之前的学习中我们已经了解了栈的有关知识,今天我们将学习的是跟栈恰恰相反的一种数据结构,名为“队列”。
目录
1.队列的概念
队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出(FIFO(First In First Out) 入队列:进行插入操作的一端称为队尾 出队列:进行删除操作的一端称为队头
首先我们通过图片进行认识:
通过上图我们不难发现这和我们之前学习的栈的工作原理完全相反的,栈中是新进来的元素后出去,而队列则是先进来的元素先出去!
紧接着我们通过动态展示来认识工作流程:
动态图
2.队列的结构体实现
代码如下:
// 链式结构:表示队列
typedef int QDataType;
typedef struct QueueNode
{
QDataType data;
struct QueueNode* next;
}QNode;
typedef struct Queue
{
QNode* head;//对头指针
QNode* tail;//队尾指针
int size;
}Queue;
3.接口实现
队列也可以数组和链表的结构实现,使用链表的结构实现更优一些,因为如果使用数组的结构,出队列在数组头上出数据,效率会比较低,队列的主要操作和栈大差不差,包括初始化,入队和出队等,具体如下。
// 初始化队列
void QueueInit(Queue* q);
// 队尾入队列
void QueuePush(Queue* q, QDataType data);
// 队头出队列
void QueuePop(Queue* q);
// 获取队列头部元素
QDataType QueueFront(Queue* q);
// 获取队列队尾元素
QDataType QueueBack(Queue* q);
// 获取队列中有效元素个数
int QueueSize(Queue* q);
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0
int QueueEmpty(Queue* q);
// 销毁队列
void QueueDestroy(Queue* q);
3.1队列初始化
void QueueInit(Queue* pq)
{
assert(pq);
pq->head = NULL;
pq->tail = NULL;
pq->size = 0;
}
3.2队尾入队列
void QueuePush(Queue* pq, QDataType x)
{
assert(pq);
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (newnode == NULL)
{
perror("malloc fail");
exit(-1);
}
newnode->data = x;
newnode->next = NULL;
if (pq->tail == NULL)
{
pq->head = pq->tail = newnode;
}
else
{
pq->tail->next = newnode;
pq->tail = newnode;
}
pq->size++;
}
3.3队头出队列
void QueuePop(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
if (pq->head->next == NULL)
{
free(pq->head);
pq->head = pq->tail = NULL;
}
else
{
QNode* del = pq->head;
pq->head = pq->head->next;
free(del);
}
pq->size--;
}
3.4获取队列头部元素
QDataType QueueFront(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->head->data;
}
3.5获取队列队尾元素
QDataType QueueBack(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->tail->data;
}
3.6获取队列中有效元素个数
int QueueSize(Queue* pq)
{
assert(pq);
return pq->size;
}
3.7检测队列是否为空,如果为空返回非零结果,如果非空返回0
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->head == NULL && pq->tail == NULL;
}
3.8销毁队列
void QueueDestroy(Queue* pq)
{
assert(pq);
QNode* cur = pq->head;
while (cur)
{
QNode* del = cur;
cur = cur->next;
free(del);
}
pq->head = pq->tail = NULL;
pq->size = 0;
}
注意:我们这里的cur的判断条件是不能等于空,而不是不能等于尾,因为如果判断条件为cur不等于尾的话,当我们循环到cur等于尾时就不执行销毁操作了,及尾元素没有被删除掉,这里的判断条件为cur!=NULL就不会出现这个问题!!
4.循环队列的概念
4.1概念和结构
循环队列的引入:
当前队列的空间占满时,就不能在插入更多的元素,否则就会导致溢出现象,即因数组越界而导致的程序非法操作错误。然而事实上,此时队列的实际可用空间并未占满,我们称这种现象为“假溢出”,这是由于“队尾入队,队头出队”这种受限制的操作导致的。为了解决这个问题,一个较为巧妙的办法就是将顺序队列变为一个环状的空间,称之为“循环队列”。
说明:头尾指针以及队列元素之间的关系不变,只是在循环队列中,头尾指针“依环状增1”的操作可用“模”运算实现。通过这种取模的方式,头尾指针就可以在顺序表空间内以头尾链接的方式“循环移动”!!!
具体结构图如下:
4.2队列空间的判断
首先来看空的循环队列:
满的循环队列
队满和队空的判断一般有两种处理方法:
1.少用一个元素空间,即队列空间大小为 m 时,有 m -1个元素就认为是队满。这样判断队空的条件不变,即当头、尾指针的值相同时,则认为队空;而当尾指针在循环意义上加1后是等于头指针,则认为队满。因此,在循环队列中队空和队满的条件是:
队空的条件: Q . front == Q . rear
队满的条件:( Q . rear +1)% MAXQSIZE = Q .front2.另设一个标志位以区别队列是"空"还是"满"。
5.选择题练习
1.数组Q[n]用来表示一个循环队列,f为当前队列头元素的前一位置,r为队尾元素的位置,假定队列中元素的个数小于n,计算队列中元素的公式()。
A.r-f
B.(n+f-r)% n
C.n+r-f
D.(n+r-f)% n
解答:
对于非循环队列,尾指针和头指针的差值即为队列长度,而对于循环队列,差值可能是负的,所以需要将差值加上MAXQSIZE(本题即为n),然后与MAXQSIZE(本题即为n)求余,所以选择D。