- 概述
数据结构是计算机存储、组织数据的方式。数据结构是指相互之间存在一种火多种特定关系的数据元素的集合。通常情况,选择合适的数据结构可以带来更高的运行或者存储效率。队列是常用的数据结构之一,是一个允许在一端进行插入操作,而在另一端进行删除操作的线性表。在嵌入式软件中主要应用于异步处理的buffer,例如,在CAN、SPI、UART等接口在中断接收到数据,存放在队列中(入队),而实际的处理解析(出队)将在其他线程或者任务中实现。另外还可以应用于FIFO的阻塞的某些特殊的应用环境,例如,报警序列。并且,在硬件的存储芯片中,有一类根据队列结构构造的芯片,那就是FIFO芯片。
- 特征
队列是一种线性结构,允许对两端进行操作,在表的一端只能进行删除操作,此端称为队头;在表的另一端只能进行插入操作,称为队尾。若队列中没有数据,称为空队列。队列按照先进先出的原则处理结点数据,即插入的顺序和取出的顺序是相同的。如下图所示为队列示意图:
- 分类
根据队列的实现方式,可以分为顺序队列及链表队列两种。
- 顺序对列:顺序队列是用数组实现的,首指针在出队的时候移动,尾指针在入队的时候移动,需要考虑队列为空和队列为满两种情况。一般情况下,会将其设置为循环队列。如下图所示:
- 链表队列:顾名思义,链表队列通过链表来实现,其首指针不移动始终指向头节点,尾指针在入队的时候移动,只考虑队列为空的情况(因为链表的长度在程序运行过程中可以不断增加,因此不需要考虑队列满的情况)。如下图所示:
(a)空队列(b)元素x入队列(c)元素y入队列(d)元素x出队列
- 顺序队列
顺序队列,是使用一组地址连续的内存单位依次保存队列中的数据。
- 入队
当向队列中添加一个元素时,将元素直接添加到队尾,不需要移动元素。
- 出队
队列元素的出列是在队头,即下标为0的位置,这就意味着在队头删除元素时,队列中的所有元素都得向前移动,以保证队列的队头不为空。
- 假溢出
由出队列可知,假设当前队列的空间为5,入队a1,a2,a3,a4,a5,出队a1,a2,但此时若接着入队的话,数组的末尾元素已经占用,再向后加就会产生数组越界的错误,但是队列下标为0和1的位置还是空闲的,这种现象叫做“假溢出”,即在顺序队列中,队尾指针已经达到数组的上界时,虽然数组中还有空位置,但是不能再有入队的操作。真溢出则是队列空间能够容纳多少,实际入队就是多少。
解决假溢出的办法就是循环队列,当队列后面满了以后,再从头开始,也即头尾相接的循环。如下图所示:
- 队列满/空
下面来判断队列的空及满,由队列的头指针(front)及尾指针(rear)的相对位置可知,当队列为空时,front == rear;当队列满时,front == rear。那么如何判断此时的队列究竟是满还是空呢?
方法1,设置标志变量flag,当front == rear,且flag == 0时队列为空;当front == rear,且flag == 1时队列为满。标志变量可以由队列内元素个数来判断。方法2,队列为空时,front == rear;当队列为满时,则需保留一个元素空间,也即队列满了,队列空间中还有一个空闲单元。如下图所示:
由图可知,由于队列首尾相接,所以rear与front尽管只相差一个位置,但是rear可能比front大,也可能比front小,所以可以利用队列空间的大小进行取模,若队列空间最大为size,则根据第二种方法,队列满的条件为(rear + 1) % size == front。
- C语言表示
- 存储结构
//----循环队列----
typedef struct
{
QElemType* base;//初始化的动态分配存储空间
int front;//头指针,若队列不空,指向队列头元素
int rear;//尾指针,若队列不空,指向队列尾元素的下一个位置
}SqQueue;
- 基本操作函数原型
Status InitQueue(SqQueue& Q); //构造一个空队列Q
int QueueLength(SqQueue Q); //返回Q的元素个数,即队列的长度
Status EnQueue(SqQueue& Q, QElemType e); //入队,元素e插入队列Q
Status DeQueue(SqQueue& Q, QElemType& e); //出队,若队列不空,删除Q的队头元素,用e返回其值,并返回OK
void PrintQueue(SqQueue Q); //打印队列
- 基本操作函数说明
/*!
\brief Initialize queue
\param[in] queue
\param[out] none
\retval Status
*/
Status InitQueue(SqQueue& Q)
{
Q.base = (QElemType*)malloc(MAXQSIZE * sizeof(QElemType));
if (!Q.base)
{
exit(OVERFLOW);//存储分配失败
}
Q.front = Q.rear = 0;
return OK;
}
/*!
\brief Get the number of elements in the queue
\param[in] queue
\param[out] none
\retval Number of elements
*/
int QueueLength(SqQueue Q)
{
return (Q.rear - Q.front + MAXQSIZE) % MAXQSIZE;
}
/*!
\brief Write element to queue
\param[in] queue
element
\param[out] none
\retval Status
*/
Status EnQueue(SqQueue& Q, QElemType e)
{
if ((Q.rear + 1) % MAXQSIZE == Q.front)
{
return ERROR;
}
Q.base[Q.rear] = e;
Q.rear = (Q.rear + 1) % MAXQSIZE;
return OK;
}
/*!
\brief Remove element from queue
\param[in] queue
element
\param[out] none
\retval Status
*/
Status DeQueue(SqQueue& Q, QElemType& e)
{
if (Q.front == Q.rear)
{
return ERROR;
}
e = Q.base[Q.front];
Q.front = (Q.front + 1) % MAXQSIZE;
return OK;
}
/*!
\brief Printf queue element
\param[in] queue
\param[out] none
\retval none
*/
void PrintQueue(SqQueue Q)
{
printf("队列为:");
int n = Q.front;
for (int i = 0; i < QueueLength(Q); i++)//循环次数
{
printf("%d ", Q.base[n++ % MAXQSIZE]);
}
printf("\n");
}
- 链表队列
链表队列即使用链表来保存队列中各元素的值,为了操作方便,将队列头指针front指向链表的头结点,将队列尾指针rear指向链表的终端结点。如下图所示:
- 入队
链表队列在入队操作时,队尾指针指向下一存储空间的头结点,如下图所示:
- 出队
链表队列在出队操作时,队头指针指向第二个元素的头结点,如下图所示:
- 队列空
当队列为空时,头指针与尾指针都指向同一个结点,如下图所示:
- C语言表示
- 存储结构
//单链队列----队列的链式存储结构
typedef struct QNode
{
QElemType data;
struct QNode* next;
}QNode, * QueuePtr;
typedef struct
{
QueuePtr front;//队头指针
QueuePtr rear;//队尾指针
}LinkQueue;
- 基本操作函数原型
Status InitQueue(LinkQueue& Q); //构造一个空队列Q
Status DestroyQueue(LinkQueue& Q); //销毁队列Q,将Q清为空队列
Status GetHead(LinkQueue Q, QElemType& e); //用e返回Q的队头元素
Status EnQueue(LinkQueue& Q, QElemType e); //入队,元素e插入队列Q
Status DeQueue(LinkQueue& Q, QElemType& e); //出队,若队列不空,删除Q的队头元素,用e返回其值,并返回OK
void PrintQueue(LinkQueue Q); //打印队列
- 基本操作函数说明
/*!
\brief Initialize queue
\param[in] queue
\param[out] none
\retval Status
*/
Status InitQueue(LinkQueue& Q)
{
//构造一个空队列Q
Q.front = Q.rear = (QueuePtr)malloc(sizeof(QNode));
if (!Q.front)
{
exit(OVERFLOW);//分配存储失败
}
Q.front->next = NULL;
return OK;
}
/*!
\brief Destroy queue
\param[in] queue
\param[out] none
\retval Status
*/
Status DestroyQueue(LinkQueue& Q)
{
//销毁队列Q
while (Q.front)
{
Q.rear = Q.front->next;
free(Q.front);
Q.front = Q.rear;
}
return OK;
}
/*!
\brief Get queue header address
\param[in] queue
element
\param[out] none
\retval Status
*/
Status GetHead(LinkQueue Q, QElemType& e)
{
if (Q.front == Q.rear)
{
return ERROR;
}
e = Q.front->next->data;
return OK;
}
/*!
\brief Write element to queue
\param[in] queue
element
\param[out] none
\retval Status
*/
Status EnQueue(LinkQueue& Q, QElemType e)
{
//插入元素e为Q的新的队尾元素
QueuePtr p = (QueuePtr)malloc(sizeof(QNode));
if (!p)
{
exit(OVERFLOW);
}
p->data = e;
p->next = NULL;
Q.rear->next = p;
Q.rear = p;
return OK;
}
/*!
\brief Remove element from queue
\param[in] queue
element
\param[out] none
\retval Status
*/
Status DeQueue(LinkQueue& Q, QElemType& e)
{
//若队列不空,则删除Q的对头元素,用e返回其值,并返回OK;否则返回ERROR
if (Q.front == Q.rear)
return ERROR;
QueuePtr p = Q.front->next;
e = p->data;
Q.front->next = p->next;
if (Q.rear == p)
{
Q.rear = Q.front;
}
free(p);
return OK;
}
/*!
\brief Printf queue element
\param[in] queue
\param[out] none
\retval none
*/
void PrintQueue(LinkQueue Q)
{
printf("队列值为:");
QueuePtr p = Q.front;
while (p->next != NULL)
{
printf("%d ",p->next->data);
p = p->next;
}
printf("\n");
}