赞
踩
在前面我们学习了队列的概念与循环队列,我们知道了循环链表的队列长度事先就得确定好,但是实际中队列长度我们事先大多不知道,所以还是得研究一下动态的队列长度的队列储存与实现。
虽然顺序存储也可以通过realloc来实现扩容,但是顺序存储出队列不太好出——①如果队头位置定在下标0的位置,出队列的效率低;②如果每一次出队列队头位置+1,虽然效率高了,但是空间利用率低下。
所以对于动态的队列长度的队列储存与实现,我们用链式结构实现更优一些,下面我们就来学习队列的链式存储结构与实现。
队列的链式存储结构,就是使用链表实现,可以简称为链队列。注意该链队列是受到限制的,要满足队列先进先出FIFO(first in first out)的原则。
链队列结构的分析:
①实现我们选择单链表,因为节省空间。
②为了方便先进先出的操作,我们将队头指针指向链队列的头结点,队尾指针指向终端结点(单链表我们为什么不定义一个尾指针呢——因为在单链表tail只能解决尾插,还不如不用)。如下图:
不带哨兵位
带哨兵位
③空队列时,不带哨兵位——head = tail = NULL;带哨兵位——head与tail指向哨兵位。
解读:
①链队列由一个个结点组成,所以需要定义链队列结点类型。
②为了方便先进先出的操作,所以定义队首指针与队尾指针。
③为了方便求队列有效数据个数,所以定义一个size储存个数(我们可以使用哨兵位来存储size吗——不可以,如当数据域为char时,129就溢出了。)。
④多个数据组成的复杂类型,最好将其定义为结构体(因为方便传参数,不管要改变结构体的多少成员,我们只需传结构体一个变量即可)。
代码示例:
//无哨兵位链队列的实现 //链队列元素类型的重命名(①方便更改;②见名知意) typedef int QDataType; //链队列结点类型 typedef struct QueueNode { QDataType data;//数据域 struct QueueNode* next;//指向后继结点 }QNode; //链队列的结构类型 typedef struct Queue { QNode* head;//队首指针——指向队头结点 QNode* tail;//队尾指针——指向队尾结点 int size;//保存有效数据个数——方便 }Queue;
0X00队列的初始化
解读: 首先结构体指针pq一定不为空,为了程序的健壮性断言一下。其次初始化队首指针、队尾指针与有效数据个数。
代码示例:
//初始化链队列
void QueueInit(Queue* pq)
{
//断言pq不为空
assert(pq);
//链队列为空时
pq->head = pq->tail = NULL;//不带哨兵位
pq->size = 0;//空队列,有效数据个数为0
}
0X01队列的销毁
解读:
①断言pq不为空。
②在堆区申请的空间,如果不用记得自己释放,避免内存泄漏。
③链队列的销毁与链表的销毁一样,要从头结点逐个向后释放。注意释放cur结点之前要先保存它的后继结点,否则找不到。
④销毁结束之后,防止野指针将队首与队尾指针置为空。并且有效个数也为0。
代码示例:
//销毁链队列 void QueueDesttroy(Queue* pq) { assert(pq); //保存队头位置 QNode* cur = pq->head; //链队列的销毁与链表一样,要从头指针逐个销毁 while (cur) { //保存cur的后继结点 QNode* next = cur->next; //释放cur free(cur); //cur向后迭代 cur = next; } //释放完记得将队首指针与队尾指针置为空 pq->head = pq->tail = NULL; //有效数据个数为0 pq->size = 0; }
0X02入队列
解读:
①断言pq不为空。
②创建新节点,注意判断是否开辟成功,并且malloc之后记得初始化新节点。
③入队列,在队尾指针追加一个元素,即尾插。
④尾插注意分情况:情况1——队列为空,即头插,head与tail都要改变;情况2——队列不为空,即正常尾插,先链接再改变tail。
⑤入队列结束后,记得有效数据个数+1。
代码示例:
//入队列——队尾追加一个元素 void QueuePush(Queue* pq, QDataType x) { assert(pq); //创建新的队列结点 QNode* newnode = (QNode*)malloc(sizeof(QNode)); //判断是否开辟成功 if (NULL == newnode) { //打印错误信息 perror("malloc fail"); return; } //malloc不会初始化,记得自己初始化新节点 newnode->data = x; newnode->next = NULL; //入队列,即尾插 //情况1:队列为空时,队首与队尾都要改变 if (NULL == pq->head) { //head为空,tail也一定为空 assert(pq->tail == NULL); //tail(head)指向newnode pq->tail = pq->head = newnode; } //情况2:队列不为空,只改变队尾 else { //草图:tail newnode(待插) pq->tail->next = newnode; //更新队尾位置 pq->tail = newnode; } //入队之后有效数据个数+1 pq->size++; }
0X03出队列
解读:
①断言pq不为空。
②出队列,即删除队首元素,要断言队列不为空。
③删除队首元素注意tail是否要改变。情况1——只有一个结点时,释放之后head与tail都要置为空;情况2——有两个及以上结点——tail不会变,只改变head,注意要先保存head的后继结点,再释放头结点,最后链接。
④出队列之后,有效数据个数-1。
代码示例:
//出队列——队头删除一个元素 void QueuePop(Queue* pq) { assert(pq); //断言队列不为空 assert(!QueueEmpty(pq)); //出队列,即头删 //情况1:只有一个结点,删除后队首指针与队尾指针都要置为空 if (NULL == pq->head->next) { //草图:head(待删) free(pq->head); //避免野指针,队首指针与队尾指针都要置为空 pq->head = pq->tail = NULL; } //情况2:有两个及以上结点,删除后只改变队首指针 else { //head(待删) headnext QNode* next = pq->head->next; free(pq->head); pq->head = next; } //出队列后,有效数据个数-1 pq->size--; }
0X04获取队列中的元素个数
解读:
①断言pq不为空。
②直接返回size即可,从这里就可以看出我们在队列结构中添加size成员的好处。
代码示例:
**//获取队列中的元素个数
int QueueSize(Queue* pq)
{
assert(pq);
//从这就能看出我们在队列成员中添加size的作用,直接返回size即可
return pq->size;
}**
0X05检查队列是否为空
解读:
①断言pq不为空。
②有两种方式判断队列是否为空:方式1——有效数据个数为0,即表示队列为空;方式2——head与tail都为空,也表示队列为空。
代码示例:
//检查队列是否为空——为空返回真。
bool QueueEmpty(Queue* pq)
{
assert(pq);
//size等于0,即队列为空
return pq->size == 0;
//队首指针与队尾指针为空,也表示队列为空
//return (pq->head == NULL && pq->tail == NULL);
}
0X06获取队首元素
解读:
①断言pq不为空。
②断言队列不为空。
③直接返回队首元素pq->head->data即可。
代码示例:
//获取队首元素
QDataType QueueFront(Queue* pq)
{
assert(pq);
//断言队列不为空
assert(!QueueEmpty(pq));
//返回队首元素
return pq->head->data;
}
0X07获取队尾元素
解读:
①断言pq不为空。
②断言队列不为空。
③直接返回队尾元素pq->tail->data即可。
代码示例:
//获取队尾元素
QDataType QueueBack(Queue* pq)
{
assert(pq);
//断言队列不为空
assert(!QueueEmpty(pq));
//返回队尾元素
return pq->tail->data;
}
总结:
①注意删除与获取队列元素时,都要判断队列不为空。
②入队列注意head是否要改变,出队列tail是否要改变。
③在入队列、出队列、销毁队列、初始化队列时队列的有效数据个数都有改变。
④free释放之后,指针的指向不会改变,防止野指针,记得free之后指针一般置为空等等。
//头文件的声明 #include<stdio.h> #include<stdlib.h> #include<assert.h> #include<stdbool.h> //无哨兵位链队列的实现 //链队列元素类型的重命名(①方便更改;②见名知意) typedef int QDataType; //链队列结点类型 typedef struct QueueNode { QDataType data;//数据域 struct QueueNode* next;//指向后继结点 }QNode; //链队列的结构类型 typedef struct Queue { QNode* head;//队首指针——指向队头结点 QNode* tail;//队尾指针——指向队尾结点 int size;//保存有效数据个数——方便 }Queue; //链队列接口函数的声明 //初始化链队列 void QueueInit(Queue* pq); //销毁链队列 void QueueDesttroy(Queue* pq); //入队列——队尾追加一个元素 void QueuePush(Queue* pq, QDataType x); //出队列——队头删除一个元素 void QueuePop(Queue* pq); //获取队列中的元素个数 int QueueSize(Queue* pq); //检查队列是否为空——为空返回真。 bool QueueEmpty(Queue* pq); //获取队首元素 QDataType QueueFront(Queue* pq); //获取队尾元素 QDataType QueueBack(Queue* pq);
#include"Queue.h" //初始化链队列 void QueueInit(Queue* pq) { //断言pq不为空 assert(pq); //链队列为空时 pq->head = pq->tail = NULL;//不带哨兵位 pq->size = 0;//空队列,有效数据个数为0 } //销毁链队列 void QueueDesttroy(Queue* pq) { assert(pq); //保存队头位置 QNode* cur = pq->head; //链队列的销毁与链表一样,要从头指针逐个销毁 while (cur) { //保存cur的后继结点 QNode* next = cur->next; //释放cur free(cur); //cur向后迭代 cur = next; } //释放完记得将队首指针与队尾指针置为空 pq->head = pq->tail = NULL; //有效数据个数为0 pq->size = 0; } //入队列——队尾追加一个元素 void QueuePush(Queue* pq, QDataType x) { assert(pq); //创建新的队列结点 QNode* newnode = (QNode*)malloc(sizeof(QNode)); //判断是否开辟成功 if (NULL == newnode) { //打印错误信息 perror("malloc fail"); return; } //malloc不会初始化,记得自己初始化新节点 newnode->data = x; newnode->next = NULL; //入队列,即尾插 //情况1:队列为空时,队首与队尾都要改变 if (NULL == pq->head) { //head为空,tail也一定为空 assert(pq->tail == NULL); //tail(head)指向newnode pq->tail = pq->head = newnode; } //情况2:队列不为空,只改变队尾 else { //草图:tail newnode(待插) pq->tail->next = newnode; //更新队尾位置 pq->tail = newnode; } //入队之后有效数据个数+1 pq->size++; } //出队列——队头删除一个元素 void QueuePop(Queue* pq) { assert(pq); //断言队列不为空 assert(!QueueEmpty(pq)); //出队列,即头删 //情况1:只有一个结点,删除后队首指针与队尾指针都要置为空 if (NULL == pq->head->next) { //草图:head(待删) free(pq->head); //避免野指针,队首指针与队尾指针都要置为空 pq->head = pq->tail = NULL; } //情况2:有两个及以上结点,删除后只改变队首指针 else { //head(待删) headnext QNode* next = pq->head->next; free(pq->head); pq->head = next; } //出队列后,有效数据个数-1 pq->size--; } //获取队列中的元素个数 int QueueSize(Queue* pq) { assert(pq); //从这就能看出我们在队列成员中添加size的作用,直接返回size即可 return pq->size; } //检查队列是否为空——为空返回真。 bool QueueEmpty(Queue* pq) { assert(pq); //size等于0,即队列为空 return pq->size == 0; //队首指针与队尾指针为空,也表示队列为空 //return (pq->head == NULL && pq->tail == NULL); } //获取队首元素 QDataType QueueFront(Queue* pq) { assert(pq); //断言队列不为空 assert(!QueueEmpty(pq)); //返回队首元素 return pq->head->data; } //获取队尾元素 QDataType QueueBack(Queue* pq) { assert(pq); //断言队列不为空 assert(!QueueEmpty(pq)); //返回队尾元素 return pq->tail->data; }
#include"Queue.h" int main() { //定义一个队列对象 Queue pq; //初始化队列 QueueInit(&pq); //入队列:1 2 3 4 5 QueuePush(&pq, 1); QueuePush(&pq, 2); QueuePush(&pq, 3); QueuePush(&pq, 4); QueuePush(&pq, 5); //打印队列的个数 printf("%d\n", QueueSize(&pq)); //出队列 while (!QueueEmpty(&pq)) { //打印队首元素 printf("%d ", QueueFront(&pq)); //出队列 QueuePop(&pq); } //销毁队列 QueueDesttroy(&pq); return 0; }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。