本文主要是数据结构——队列相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。
点击查看使用工具及版本
Windows | windows11 |
Ubuntu | Ubuntu16.04的64位版本 |
VMware® Workstation 16 Pro | 16.2.3 build-19376536 |
SecureCRT | Version 8.7.2 (x64 build 2214) - 正式版-2020年5月14日 |
开发板 | 正点原子 i.MX6ULL Linux阿尔法开发板 |
uboot | NXP官方提供的uboot,NXP提供的版本为uboot-imx-rel_imx_4.1.15_2.1.0_ga(使用的uboot版本为U-Boot 2016.03) |
linux内核 | linux-4.15(NXP官方提供) |
STM32开发板 | 正点原子战舰V3(STM32F103ZET6) |
点击查看本文参考资料
点击查看相关文件下载
一、队列的概念
队列是限制在两端进行插入操作和删除操作的线性表。
- 队尾:允许进行存入操作的一端称为队尾。
- 队头:允许进行删除操作的一端称为队头。
- 空队:当线性表中没有元素时,称为空队。
- 入队:数据元素进队列的过程称为入队。
- 出队:数据元素出队列的过程称为 出队。
队列的特点是先进先出( FIFO ),数据从队列的一端进,从另一端出,可以由顺序表实现队列的存储结构,称为顺序队列,也可以由链表实现队列的存储结构,称为链式队列。两者的区别仅是顺序表和链表的区别,即在实际的物理空间中,数据集中存储的队列是顺序队列,分散存储的队列是链队列。
队列可以是单端的,也可以是双端的,但是定要遵循的是先进先出的规则,从一端进,从另一端出。
二、顺序队列
1.存储结构
顺序队列是一种顺序存储结构,它借助顺序表实现,由于我们需要在顺序表的一端实现入队,在另一端实现出队,所以需要两个”指针”(为什么加引号呢?严格来说这两个并非是指针变量,而只是代表了顺序队列中指向队头元素和队尾元素所在数组位置下标的变量,一般是整型数据),来指向队头和队尾。
按照预想的情况应该是上图的这种结构, front 指向队头元素, rear 指向队尾元素的下一个位置(原因?只有指向下一个位置才能插入嘛,不然这个数据不就被覆盖了嘛,哈哈),接下来我们想象一下从数据入队到全部出队,会发生什么现象。假设有一个队列最大可以有 5 个元素,那么我们让 5 个数据入队然后再出队,然后来看一下最终的状态。
我们会发现入队的时候尾指针不断向后移动,出队的时候头指针也是不断向后移动,当头尾指针重合时,队列中没有元素,为空队。那么问题产生了,这个时候这个队列是空的,但是队头和队尾已经移动到最后边去了,前边即便空间已经空出来了,由于队列的规则限制,这意味着前边的内存空间是无法使用了,重新申请一个内存空间嘛?显然不太可能喽。那有什么办法吗?可以这样想,我们让队头和队尾指针再重头开始不就行了吗?方案是可行的,那怎么实现呢?
现在要解决的问题就是如何让 front 和 rear 一直自加还能保证只在这 6 个元素的队列中循环,也就是说,不管这两个指针加了多少次,是最后的取值只能在 0-5 之间,这个时候可以想到,不是有个取余数的操作嘛,对 6 取余的话,最后得到的余数一定是小于 6 的,这样,这个问题就解决啦。那我们看看还会不会有问题。
绝了,循环倒是实现了,但是,队满的时候,头尾指针重合了,这不完了。这样的话,我不知道它是空是满,万一这个状态是满的,我又继续入队了,那数据不就没得了?这又怎么办呢?没办法,只好牺牲掉数组的一个数据啦,这样当 rear 到最后一个位置的时候就不能再入队了,因为再入一个数据就已经满队了,再经过规则限定,我们来看看最后的效果吧。
【总结】
(1) front 指向队头元素的位置; rear 指向队尾元素的下一个位置。
(2)在队列操作过程中,为了提高效率,以调整指针代替队列元素的移动,并将数组作为循环队列的操作空间。
(3)为区别空队和满队,满队元素个数比数组元素个数少一个。
2.结构体实现
1 2 3 4 5 6 7 8 9 10 11 12
| #define N 6
typedef int data_t;
typedef struct { data_t data[N]; int front; int rear; }seqqueue;
|
【说明】顺序队列应为为 sequence queue ,所以后续使用 seqqueue 作为顺序队列结构体以及函数命名的关键字。
3.顺序队列基本操作
相关源代码可以在这里下载:
3.1创建
点击查看函数实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
|
seqqueue * seqqueue_create() { seqqueue *sq; sq = (seqqueue *)malloc(sizeof(seqqueue)); if(sq == NULL) { printf("sequence queue malloc failed!\n"); return NULL; } memset(sq->data, 0, sizeof(sq->data)); sq->front = 0; sq->rear = 0; return sq; }
|
3.2释放
点击查看函数实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
seqqueue * seqqueue_free(seqqueue *sq) { if(sq == NULL) { printf("sequence queue is not existed!\n"); return NULL; } free(sq); sq = NULL; printf("sequence queue free completed!\n"); return NULL; }
|
3.3显示
点击查看函数实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
|
int seqqueue_show(seqqueue *sq) { int i = 0; if(sq == NULL) { printf("sequence queue is not existed!\n"); return -1; } if(sq->front == sq->rear) { printf("sequence queue is empty!\n"); return -1; } if(sq->front < sq->rear) { printf("sequence queue:"); for(i = sq->front; i < sq->rear; i++) { printf("%d ", sq->data[i]); } puts(""); } else { printf("sequence queue:"); for(i = 0; i < N; i++) { if(i == sq->rear) printf("null "); else printf("%d ", sq->data[i]); } puts(""); } return 0; }
|
3.4入队
点击查看函数实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
|
int seqqueue_enqueue(seqqueue *sq, data_t value) { if(sq == NULL) { printf("sequence queue is not existed!\n"); return -1; } if((sq->rear + 1) % N == sq->front) { printf("sequence queue is full!\n"); return -1; } sq->data[sq->rear] = value; sq->rear = (sq->rear + 1) % N; return 0; }
|
3.5出队
【注意】这里出队的时候不要做队列是否存在以及是否为空的判断,这两个判断若是有返回值的话程序会认为返回值就是出队的元素,所以队列的存在性以及是否为空,要在使用出队函数前进行判断。
点击查看函数实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| ** * @Function: seqqueue_dequeue * @Description: 出队 * @param sq: 顺序队列地址 * @return : 返回一个数据 * ret,出队数据 */ data_t seqqueue_dequeue(seqqueue *sq) { data_t ret; ret = sq->data[sq->front]; sq->front = (sq->front + 1) % N; return ret; }
|
3.6空满
点击查看判断是否为空函数实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
|
int seqqueue_empty(seqqueue *sq) { if(sq == NULL) { printf("sequence queue is not existed!\n"); return -1; } if(sq->front == sq->rear) return 1; else return 0; }
|
点击查看判断是否为满函数实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
int seqqueue_full(seqqueue *sq) { if(sq == NULL) { printf("sequence queue is not existed!\n"); return -1; } if((sq->rear + 1) % N == sq->front) return 1; else return 0; }
|
3.7清空
点击查看函数实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
int seqqueue_clear(seqqueue *sq) { if(sq == NULL) { printf("sequence queue is not existed!\n"); return -1; } sq->rear = sq->front = 0;
return 0; }
|
三、链式队列
1.存储结构
链式队列的存储结构如下图所示,将从队尾入队,队头出队:
相对于顺序队列,链式队列没有假溢出,因为它不会满,链式队列入队操作会申请内存新建节点,出队会释放相应节点,动态存储,只要能成功申请内存创建新的节点,链式队列就不会满,所以不涉及溢出和队满的情况。(当然了,内存不够的时候也还是会满的啦,不过这个时候就可能需要考虑换更大空间的内存啦,哈哈哈。)
2.结构体实现
对于链式队列,还是借助单链表来实现,然后我们再添加头尾指针指向头尾就好啦。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| typedef int data_t;
typedef struct slqueue_node { data_t data; struct slqueue_node *next; }slqueuenode, * slqueuelink;
typedef struct { slqueuelink front; slqueuelink rear; }slqueue_sq;
|
【说明】单链表实现的队列,英文名字是 single linked list queue ,所以我后边都用 slqueue 作为链式队列结构体及函数命名的关键词啦。
3.链式队列基本操作
相关源代码可以在这里下载:
3.1创建
- (1)链式队列结构体变量 lq 申请内存(包含两个成员: front 和 rear );
- (2)申请一个单链表结构体变量 Head 申请内存空间,也就是头节点的内存空间;
- (3)队列结构体指针变量成员的首尾指针均指向 Head ;
- (4)初始化单链表结构体变量成员
【注意】(2)、(3)两步可合并,省去 Head 变量的定义即有:
1
| lq->front = lq->rear = (slqueuelink)malloc(sizeof(slqueuenode));
|
点击查看函数实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
|
slqueue_lq * slqueue_create() { slqueue_lq *lq; lq = (slqueue_lq *)malloc(sizeof(slqueue_lq)); if(lq == NULL) { printf("single linked list queue malloc failed!\n"); return NULL; } lq->front = lq->rear = (slqueuelink)malloc(sizeof(slqueuenode)); if(lq->front == NULL) { printf("single linked list queue node malloc failed!\n"); free(lq); return NULL; } lq->front->data = 0; lq->front->next = NULL; return lq; }
|
3.2入队
我们从链表尾进行入队操作:
点击查看函数实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
|
int slqueue_enqueue(slqueue_lq *lq, data_t value) { slqueuelink p; if(lq == NULL) { printf("single linked list queue is not existed!\n"); return -1; } p = (slqueuelink)malloc(sizeof(slqueuenode)); if(p == NULL) { printf("single linked list queue node malloc failed!\n"); return -1; } p->data = value; p->next = NULL; lq->rear->next = p; lq->rear = p; return 0; }
|
3.3出队
我们将从链表头位置出队,由于我们创建的链表是有头节点的,头节点仅仅是代表这个队列的地址,所以每一次出队我们表面上是将数据出队,实际上这个数据还存在于出队后的头节点中,释放的空间也是出队前头节点的空间:
【注意】这里出队的时候不要做队列是否存在以及是否为空的判断,这两个判断若是有返回值的话程序会认为返回值就是出队的元素,所以队列的存在性以及是否为空,要在使用出队函数前进行判断。
点击查看函数实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
|
data_t slqueue_dequeue(slqueue_lq *lq) { slqueuelink p; p = lq->front; lq->front = p->next; free(p); p = NULL; return (lq->front->data); }
|
3.4释放
释放的方式与出队一样,只是需要将所有节点出队,并且释放掉最后一个头节点,如下图所示:
点击查看函数实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
|
slqueue_lq * slqueue_free(slqueue_lq *lq) { slqueuelink p; if(lq == NULL) { printf("single linked list queue is not existed!\n"); return NULL; } printf("single linked list queue free:"); while(lq->front) { p = lq->front; lq->front = p->next; printf("%d ", p->data); free(p); } puts(""); free(lq); lq = NULL; return NULL; }
|
3.5清空
清空的过程,也是将所有节点出队,只不过不释放最后一个节点:
点击查看函数实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
|
int slqueue_clear(slqueue_lq *lq) { slqueuelink p; if(lq == NULL) { printf("single linked list queue is not existed!\n"); return -1; } printf("single linked list queue clear free:"); while(lq->front->next != NULL) { p = lq->front; lq ->front = p->next; printf("%d ", p->data); free(p); p = NULL; } puts(""); return 0; }
|
3.6显示
此函数将显示所有节点的数据,包括头节点。
点击查看函数实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
|
int slqueue_show(slqueue_lq *lq) { slqueuelink p; if(lq == NULL) { printf("single linked list queue is not existed!\n"); return -1; } p = lq->front; printf("single linked list queue:"); while(p != NULL) { printf("%d ", p->data); p = p->next; } puts(""); return 0; }
|
3.7判空
此函数将显示所有节点的数据,包括头节点。
点击查看函数实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
|
int slqueue_empty(slqueue_lq *lq) { if(lq == NULL) { printf("single linked list queue is not existed!\n"); return -1; } if(lq->front == lq->rear) return 1; else return 0; }
|