数据结构|【洋哥带你玩转线性表(四)——链式队列】



文章目录

  • 前言
  • 一、队列的定义及结构
  • 二、队列接口的实现
    • 1.初始化队列
    • 2.销毁队列
    • 3.数据入队
    • 4.数据出队
    • 5.取队头元素
    • 6.取队尾元素
    • 7.判断队列是否为空
    • 8.求队列中元素个数
  • 三、循环队列
  • 总结

前言
队列:只允许在一端进行数据的插入操作,在另一端进行数据的删除操作的特殊线性表,队列具有先入先出的特点,进行插入操作的一端称为队尾,进行删除的一端称为对头。
队列在定义的时候需要定义两个结构体,其中一个结构体代表队列内部的结点,一个是指向下一个结点的指针域,用于存放下一个结点的地址,另一个时结点的数据域用于存放结点内部的数据;另外一个结构体中定义了两个与结点同类型的指针,分别指向首结点和尾结点。
其实我们在时间的过程当中不难发现,用链表来实现的队列其实它的各方面功能和单链表及其相似,入队列的时候需要使用尾插法,出队列的时候则需要使用头删法,等等一系列操作都和链表非常类似,其实这部分的难度要远低于链表,如果对这方面有不了解的小伙伴们,建议查阅我之前写过的文章。
提示:以下是本篇文章正文内容,下面案例可供参考
一、队列的定义及结构 1.队列的概念及结构:
队列:只允许在一端进行数据的插入操作,在另一端进行数据的删除操作的特殊线性表,队列具有先入先出的特点,进行插入操作的一端称为队尾,进行删除的一端称为对头。
数据结构|【洋哥带你玩转线性表(四)——链式队列】
文章图片

2.队列的实现:
队列可以使用数组来实现,也可以使用链表来实现,使用链表的结构表现会更优一些,因为我们刚才提到过队列只能在一端插入数据,在另一端删除数据。这就意味着入队时尾插法,而出队则是头删法,如果使用数组来实现的话,在进行头删的时候会非常麻烦,必须将数组中从第二个元素开始,每一个元素向前移动将第一个元素的位置进行覆盖,才可以实现头删,但是如果使用链表来实现的话,头删法就会非常容易。
用数组来实现队列,相当于我们之前提到过的线性表的功能,如果还有对于顺序表不理解的小伙伴们,可以查阅我之前的文章,里面有关于顺序表的详细介绍,希望可以帮到你!
对头队尾
front=null,rear=null; (空队列)
(出队)数据域(A) | 指针域 ===> 数据域(B) | 指针域 ===> (入队)
数据结构|【洋哥带你玩转线性表(四)——链式队列】
文章图片
???????
3.队列的定义:
说明:
其实关于队列的定义已经在前言部分有过说明了,第一个结构体是定义结点,结点分为数据域和指针域,那么第二个结构体中是定义两个指向结点的指针,一个指向首结点,另一个指向尾结点,用于进行队列的增删改查的系列操作。
拓展:
也有一些小伙伴私信过我,对于结构体的使用这一部分内容不太了解,在这里再说明一下,之所以要定义 DataType 是因为利用重命名关键字 typedef 将 int 类型定义为 DataType ,是因为如果在之后想要将队列中的结点中的数据类型定义为char、double 等其他类型的话只需要将DataType进行修改即可,如果不这么做的话,就需要对全文当中的 int 类型进行修改,这将会是一件非常麻烦的事情,还有一件很重要的事情,就是对于指针的理解,指针就是地址,我们平常所说的指针指向地址中的这个“指针”是指针变量,并不是真正意义上的指针,这一点要理解。
typedef int DataType; typedef struct QueueNode { struct QueueNode* next; DataType data; }QNode; typedef struct QueueList { QNode* head; QNode* tail; }Queue;

4.相关的头文件以及常见的接口:
#pragma once #include #include #includetypedef int DataType; typedef struct QueueNode { struct QueueNode* next; DataType data; }QNode; typedef struct QueueList { QNode* head; QNode* tail; }Queue; void QueueInit(Queue* q); void QueueDestroy(Queue* q); void QueuePush(Queue* q,DataType x); void QueuePop(Queue* q); DataType QueueFront(Queue* q); DataType QueueBack(Queue* q); bool QueueEmpty(Queue* q); int QueueSize(Queue* q);

二、队列接口的实现
1.队列接口的实现 1.初始化队列
void QueueInit(Queue* q) { assert(q); q->head = q->tail = NULL; }

2.销毁队列
说明:
队列的销毁需要依次销毁每一个结点,先从首结点开始,注意这里必须提前定义好首结点的下一个结点,因为这里的队列使用链表来实现的,想要访问第二个结点,就必须先找到第一个结点,一旦未定义好第二个结点,就先释放掉了首结点,那么将无法在访问第二个结点。
void QueueDestroy(Queue* q) { assert(q); QNode* cur = q->head; while (cur) { QNode* next = cur->next; free(cur); cur = next; } q->head = q->tail = NULL; }

3.数据入队
说明:
其实包括之前的链表也好还是顺序表也好,在对于数据的插入和删除这一部分的内容多多少少都会有特殊情况出现,就比如链表当中只有一个结点或者没有结点的时候怎么办 ,这些”临界值“都有可能成为我们要考虑的特殊情况,注意是可能,并不是肯定,具体怎么办要看情况,其实这些类似于我们高中物理所学过的临界值,就像恰好静止、恰好分离、恰好运动等等。
想要具体了解这些情况该怎么办,其实也很简单就只需要把这些特定的情况代入一般情况,看看一般情况下的代码能否正常执行,是否会产生越界访问或野指针等情况。
void QueuePush(Queue* q,DataType x) { assert(q); QNode* newnode = (QNode*)malloc(sizeof(QNode)); if (newnode == NULL) { printf("malloc is fail\n"); exit(-1); } newnode->data = https://www.it610.com/article/x; newnode->next = NULL; if (q->tail == NULL) { q->head = q->tail = newnode; } else { q->tail->next = newnode; q->tail = newnode; } }

4.数据出队
说明:
在之前我们就提过,由于队列的性质所决定,入队需采用尾插法,而出队则需要采用头删法,这里一样会产生特殊情况,解决方法如同上一个接口,但是在这里需要注意,队列一定不能为空,所以这里必须使用断言”assert“来判断一下,关于断言的用法,我们在之前就已经提过了,今天就不在赘述了。
void QueuePop(Queue* q) { assert(q); assert(!QueueEmpty(q)); if (q->head->next == NULL) { free(q->head); q->head = q->tail = NULL; } else { QNode* next = q->head->next; free(q->head); q->head = next; } }

5.取队头元素
DataType QueueFront(Queue* q) { assert(q); assert(!QueueEmpty(q)); return q->head->data; }

6.取队尾元素
DataType QueueBack(Queue* q) { assert(q); assert(!QueueEmpty(q)); return q->tail->data; }

7.判断队列是否为空
bool QueueEmpty(Queue* q) { assert(q); return q->head == NULL; }

8.求队列中元素个数
int QueueSize(Queue* q) { assert(q); QNode* cur = q->head; int size = 0; while (cur) { size++; cur = cur->next; } return size; }

三、循环队列 未完待续,我们下期再见!!! 你们的支持就是我前进的动力,怎么样,你学会了吗? 总结
【数据结构|【洋哥带你玩转线性表(四)——链式队列】】队列相比我们之前提到过的内容都要简单,只要将链表掌握以后,栈和队列都不是问题,所以学好线性表的关键还是顺序表和单链表,如果对于这方面有疑虑的小伙伴欢迎在评论区给我留言或是私信我都可以,也可以查阅我之前的博客,里面的线性表专题希望对您有帮助!

    推荐阅读