在学习线性表后,可以把队列看成一种特殊的线性表,只是这种线性表只支持尾插与头删。满足数据先进先出的特点
例如把1,2,3这三个数据依次插入队列,此时的队列1为队头,3为队尾
若将把4入队,1为队头,4为队尾(相当于尾插)
若将数据出队,由于队列只能头删,所以1出队;2为对头,4为队尾
下面是实现这样结构的代码
链式结构实现队列
根据在内存上存储空间的连续性,实现队列的结构分为链表和顺序表。顺序表的头删要挪动数据,时间开销大,考虑时间的开销,链表是一种更适合实现队列的结构。
链表要实现尾插,可以创建一个尾指针指向尾节点,避免遍历链表花费多余时间。所以一个队列的链式结构中,要有一个头指针与一个尾指针,分别指向头节点与尾节点。
队列中的节点要包含一个数据域和一个指针域。
结构声明
typedef int QDataType;//队列中数据的类型为QDataType
typedef struct QueueNode
{
QDataType data;//数据域
struct QueueNode* next;//指针域
}QueueNode;//为书写方便,将struct QueueNode重新命名为QueueNode
//声明一个包含两个指针队列
typedef struct Queue
{
QueueNode* head;
QueueNode* tail;
}Queue;
队列的主要接口
队列的常用接口如下
void QueueInit(Queue* pq);//创建一个队列时,将其初始化
void QueueDestory(Queue* pq);//当不使用队列时,将其销毁
void QueuePush(Queue* pq, QDataType x);//将数据为x的节点入队
void QueuePop(Queue* pq);//队列的出队
bool QueueEmpty(Queue* pq);//队列的判空,空返回true,非空返回false
int QueueSize(Queue* pq);//求队列长度
QDataType QueueFront(Queue* pq);//返回队头元素
QDataType QueueTail(Queue* pq);//返回队尾元素
队列初始化与销毁
//初始化,将队列的头尾指针置空
void QueueInit(Queue* pq)
{
assert(pq);//断言pq不为空指针,若指向队列的指针为空,则找不到该队列,不能更改该队列内容
pq->head = NULL;
pq->tail = NULL;
}
void QueueDestory(Queue* pq);
{
assert(pq);
QueueNode* curr = pq->head;
while (curr)
{
QueueNode* next = curr->next;
free(curr);
curr = next;
}
pq->head = NULL;
pq->tail = NULL;
}
队列的入队与出队
队列为空时,调用初始化函数后,head与tail都指向空。所以队列为空时,插入节点后要将head与tail都指向该节点。
若再插入数据,此时队列不为空,插入数据就要将尾节点的指针指向要插入的节点。
再将tail指向要插入的节点
所以入队分为两种情况
void QueuePush(Queue* pq, QDataType x)
{
assert(pq);
//先创建新的节点
QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));
newnode->next = NULL;
newnode->data = x;
//若尾指针为空,则队列为空,插入节点时,将head与tail都指向该节点
if (pq->tail == NULL)
{
pq->tail = newnode;
pq->head = newnode;
}
else//队列不为空
{
pq->tail->next = newnode;//通过尾指针找到尾节点,将尾节点的next指向新节点
pq->tail = newnode;//再将尾指针指向新节点
}
}
出队时释放队列中的头节点,并且将头指针指向头节点的下一个节点
但当队列中只有一个节点时,这样删除元素就会出现问题
first是指向5这个节点的指针,second则指向空,释放first之后,head指向second,也就是空,但tail指针依然指向5这个节点,其内存空间已经还给了操作系统,tail成为野指针
(细节点:p指向一块程序向操作系统申请的内存空间,大小为4字节,free函数释放空间,是将p指向的空间上的数据重置,并没有改变p的值,所以这是p是一个野指针,在free函数释放指针所指向空间后,通常要带上把指针指空的操作,防止野指针的出现。)
所以要加上一个判断:当head为空时,tail也要置为空
void QueuePop(Queue* pq)
{
assert(pq);
assert(pq->head)//若队列为空不能删除
QueueNode* first = pq->head;//保存头节点与下一个节点
QueueNode* second = first->next;
free(first);//free掉first之后将head指向second
pq->head = second;
if (pq->head == NULL)
{
pq->tail = NULL;
}
}
队列判空与求队列长度
判空操作:判断头指针是否为空;
求长度:创建curr指针,count计数,用curr去遍历整个队列,遇到节点count加1,直到curr为空
这两个接口较简单,坑点少,直接贴代码
//判空的代码不用if语句写,pq->head == NULL,这个表达式为真,表达式结果为非0,表达式结果为假,结果为0
bool QueueEmpty(Queue* pq)
{
assert(pq);
return (pq->head == NULL);
}
int QueueSize(Queue* pq)
{
assert(pq);
QueueNode* curr = pq->head;//curr刚开始时指向队列的头
int count = 0;
while (curr)//用curr遍历队列
{
count++;
curr = curr->next;
}
return count;
}
取队列头节点与尾节点的数据
由于头尾指针的存在,通过头尾指针,能很快完成取头或取尾的操作
QDataType QueueFront(Queue* pq)
{
assert(pq);
assert(pq->head);//队列不能为空
return (pq->head->data);
}
QDataType QueueTail(Queue* pq)
{
assert(pq);
assert(pq->head);//队列不能为空
return (pq->tail->data);
}