一链式栈
1 > 链式存储的栈,称为链式栈
2 > 对于单链表而言,我们可以使用,使用头插头删完成一个栈,或者尾插尾删完成链式栈
3 > 头插头删:链表的头部就是栈顶,链表的尾部就是栈底(常用)
4 > 尾插尾删:链表的尾部就是栈顶,链表的头部就是栈底
二、队列
2.1 队列介绍
1 > 队列也是操作受限的线性表:所有操作只能在端点处进行,其删除和插入必须在不同端进行
2 > 允许插入操作的一端称为队尾,允许删除操作的一端称为队头
3 > 特点:先进先出(FIFO)
4 > 分类:顺序存储的队列称为顺序队列 ;链式存储的队列,称为链式队列
2.2 顺序队列
1 > 使用一片连续存储的空间存储队列,并且给定两个变量,分别记录队头和队尾下标
2 > 普通顺序队列使用中,存在“假溢满”现象
假溢满:队列中明明还有存储空间,但是由于队尾已经达到了数组的最大下标,不能在继续入队元素了
3 > 为了解决“假溢满”现象,我们引入了循环顺序队列
2.3 循环顺序队列
1 > 循环顺序队列:通过相关操作,当对头或队尾达到数组最大下标时,可以返回到下标为0的位置
2 > 结构体类型:一个数组存储队列,两个变量分别存储队头和队尾的下标
注意:需要人为浪费一个存储空间,用于判满
#define MAX 8 // 队列最大长度
typedef int datatype; // 数据元素类型
// 定义队列类型
typedef struct
{
datatype data[MAX]; // 存储队列的容器
int front; // 记录队头下标
int tail; // 记录队尾下标
} SeqQueue, *SeqQueuePtr;
3 > 创建队列
// 创建队列
SeqQueuePtr queue_create()
{
// 在堆区申请一个队列的大小
SeqQueuePtr Q = (SeqQueuePtr)malloc(sizeof(SeqQueue));
if (NULL == Q)
{
printf("创建失败\n");
return NULL;
}
// 初始化
bzero(Q->data, sizeof(Q->data)); // 初始化数组
Q->front = Q->tail = 0; // 队列为空
printf("队列创建成功\n");
return Q;
}
4 > 判空判满
// 队列判空
int queue_empty(SeqQueuePtr Q)
{
return Q->front == Q->tail;
}
// 队列判满
int queue_full(SeqQueuePtr Q)
{
return (Q->tail + 1) % MAX == Q->front;
}
5 > 入队:将数据放入队尾所在位置
// 入队
void queue_push(SeqQueuePtr Q, datatype e)
{
// 判断逻辑
if (NULL == Q || queue_full(Q))
{
printf("入队失败\n");
return;
}
// 入队逻辑
Q->data[Q->tail] = e;
// 队列变化:队尾后移
Q->tail = (Q->tail + 1) % MAX;
printf("入队成功\n");
}
6 > 遍历队
// 遍历队列
void queue_show(SeqQueuePtr Q)
{
// 判断逻辑
if (NULL == Q || queue_empty(Q))
{
printf("遍历失败\n");
return;
}
// 遍历逻辑
printf("从队头到队尾元素分别是:");
for (int i = Q->front; i != Q->tail; i = (i + 1) % MAX)
{
printf("%d\t", Q->data[i]);
}
printf("\n");
}
7 > 出队
// 出队
void queue_pop(SeqQueuePtr Q)
{
// 判断逻辑
if (NULL == Q || queue_empty(Q))
{
printf("出队失败\n");
return;
}
// 出队逻辑
printf("%d出队成功\n", Q->data[Q->front]);
// 队头后移
Q->front = (Q->front + 1) % MAX;
}
8 > 求队列的大小:只用队头和队尾完成
// 求队列的大小
int queue_size(SeqQueuePtr Q)
{
// 在不使用循环的情况下求大小
// 判断逻辑
if (NULL == Q)
{
printf("失败\n");
return -1;
}
return (Q->tail - Q->front + MAX) % MAX; // 核心语句
}
9 > 销毁队列
// 销毁队列
void queue_destroy(SeqQueuePtr Q)
{
if (NULL != Q)
{
free(Q);
Q = NULL;
}
printf("销毁成功\n");
}
2.4 所有代码
1 > loopseqqueue.h
#ifndef LOOPSEQQUEUE_H
#define LOOPSEQQUEUE_H
#include <myhead.h>
#define MAX 8 // 队列最大长度
typedef int datatype; // 数据元素类型
// 定义队列类型
typedef struct
{
datatype data[MAX]; // 存储队列的容器
int front; // 记录队头下标
int tail; // 记录队尾下标
} SeqQueue, *SeqQueuePtr;
// 创建队列
SeqQueuePtr queue_create();
// 队列判空
int queue_empty(SeqQueuePtr Q);
// 队列判满
int queue_full(SeqQueuePtr Q);
// 入队
void queue_push(SeqQueuePtr Q, datatype e);
// 遍历队列
void queue_show(SeqQueuePtr Q);
// 出队
void queue_pop(SeqQueuePtr Q);
// 求队列的大小
int queue_size(SeqQueuePtr Q);
// 销毁队列
void queue_destroy(SeqQueuePtr Q);
#endif
2 > loopseqqueue.c
#include "loopseqqueue.h"
// 创建队列
SeqQueuePtr queue_create()
{
// 在堆区申请一个队列的大小
SeqQueuePtr Q = (SeqQueuePtr)malloc(sizeof(SeqQueue));
if (NULL == Q)
{
printf("创建失败\n");
return NULL;
}
// 初始化
bzero(Q->data, sizeof(Q->data)); // 初始化数组
Q->front = Q->tail = 0; // 队列为空
printf("队列创建成功\n");
return Q;
}
// 队列判空
int queue_empty(SeqQueuePtr Q)
{
return Q->front == Q->tail;
}
// 队列判满
int queue_full(SeqQueuePtr Q)
{
return (Q->tail + 1) % MAX == Q->front;
}
// 入队
void queue_push(SeqQueuePtr Q, datatype e)
{
// 判断逻辑
if (NULL == Q || queue_full(Q))
{
printf("入队失败\n");
return;
}
// 入队逻辑
Q->data[Q->tail] = e;
// 队列变化:队尾后移
Q->tail = (Q->tail + 1) % MAX;
printf("入队成功\n");
}
// 遍历队列
void queue_show(SeqQueuePtr Q)
{
// 判断逻辑
if (NULL == Q || queue_empty(Q))
{
printf("遍历失败\n");
return;
}
// 遍历逻辑
printf("从队头到队尾元素分别是:");
for (int i = Q->front; i != Q->tail; i = (i + 1) % MAX)
{
printf("%d\t", Q->data[i]);
}
printf("\n");
}
// 出队
void queue_pop(SeqQueuePtr Q)
{
// 判断逻辑
if (NULL == Q || queue_empty(Q))
{
printf("出队失败\n");
return;
}
// 出队逻辑
printf("%d出队成功\n", Q->data[Q->front]);
// 队头后移
Q->front = (Q->front + 1) % MAX;
}
// 求队列的大小
int queue_size(SeqQueuePtr Q)
{
// 在不使用循环的情况下求大小
// 判断逻辑
if (NULL == Q)
{
printf("失败\n");
return -1;
}
return (Q->tail - Q->front + MAX) % MAX; // 核心语句
}
// 销毁队列
void queue_destroy(SeqQueuePtr Q)
{
if (NULL != Q)
{
free(Q);
Q = NULL;
}
printf("销毁成功\n");
}
3 > main.c
#include <myhead.h>
#include "loopseqqueue.h"
int main(int argc, const char *argv[])
{
// 调用创建队列函数
SeqQueuePtr Q = queue_create();
if (NULL == Q)
{
return -1;
}
// 调用入队函数
queue_push(Q, 520);
queue_push(Q, 1314);
queue_push(Q, 666);
queue_push(Q, 999);
// 调用遍历函数
queue_show(Q);
// 调用出队函数
queue_pop(Q);
queue_pop(Q);
queue_pop(Q);
queue_pop(Q);
queue_pop(Q);
queue_show(Q);
// 销毁队列
queue_destroy(Q);
Q = NULL;
return 0;
}
2.5 链式队列
1 > 链式存储的队列称为链式队列
2 > 实现原理:
单向链表头插尾删实现:链表的头部就是队尾,链表的尾部就是队头
单向链表头删尾插实现:链表的头部就是队头,链表的尾部就是队尾
但是:上述操作中,都要用到链表尾部节点,都需要遍历整个链表完成,效率较低
此时,我们可以引入尾指针的概念,专门指向最后一个节点的指针。
3 > 将一个头指针和一个尾指针封装成一个队列
4 > 队列类型
// 定义数据元素类型
typedef int datatype;
// 定义结点类型
typedef struct Node
{
union
{
datatype data; // 数据域
int len; // 长度
};
struct Node *next; // 指针域
} Node, *NodePtr;
// 定义队列类型
typedef struct
{
NodePtr head; // 头指针
NodePtr tail; // 尾指针
} Queue, *QueuePtr;
5 > 创建队列
注意:先创建出队列、然后创建链表、将队列的两个指针指向链表
// 创建队列
QueuePtr queue_create()
{
// 堆区申请一个队列的空间
QueuePtr Q = (QueuePtr)malloc(sizeof(Queue));
if (NULL == Q)
{
printf("创建失败\n");
return NULL;
}
// 此时 Q->head 和Q->tail是两个野指针
// 创建一个链表
Q->head = (NodePtr)malloc(sizeof(Node));
if (Q->head == NULL)
{
printf("创建失败\n");
free(Q); // 释放队列空间
return NULL;
}
// 给头结点初始化
Q->head->len = 0;
Q->head->next = NULL;
// 将两个指针指向头结点
Q->tail = Q->head;
printf("创建成功\n");
return Q;
}
6 > 队列判空
只需要队头和队尾都指向头结点即可
// 判空
int queue_empty(QueuePtr Q)
{
return Q->head == Q->tail;
}
7 > 入队
// 入队
void queue_push(QueuePtr Q, datatype e)
{
// 判断逻辑
if (NULL == Q)
{
printf("入队失败\n");
return;
}
// 申请结点封装数据
NodePtr p = (NodePtr)malloc(sizeof(Node));
if (NULL == p)
{
printf("入队失败\n");
return;
}
// 初始化
p->data = e;
p->next = NULL;
// 尾插
Q->tail->next = p; // 将结点连接到链表上
// 更新尾指针
Q->tail = p;
// 表长变化
Q->head->len++;
printf("入队成功\n");
}
8 > 遍历队列
// 遍历队列
void queue_show(QueuePtr Q)
{
// 判断逻辑
if (NULL == Q || queue_empty(Q))
{
printf("遍历失败\n");
return;
}
// 定义遍历指针,从第一个结点出发
NodePtr q = Q->head->next;
while (q != NULL)
{
// 只要结点不为空,就输出数据域
printf("%d\t", q->data);
q = q->next; // 向后遍历
}
printf("\n");
}
9 > 出队
出队时,需要注意,如果所有节点全部出队,则需要将尾指针重新指向头结点
// 出队
void queue_pop(QueuePtr Q)
{
// 判断逻辑
if (NULL == Q || queue_empty(Q))
{
printf("出队失败\n");
return;
}
// 出队:头删
NodePtr p = Q->head->next; // 标记
Q->head->next = p->next; // 孤立
printf("%d出队成功\n", p->data);
free(p); // 删除
p = NULL;
// 判断是否已经全部删除
if (Q->head->next == NULL)
{
// 尾指针重新指向头结点
Q->tail = Q->head;
}
// 表长变化
Q->head->len--;
}
10 > 销毁队列
// 销毁队列
void queue_destroy(QueuePtr Q)
{
// 判断逻辑
if (NULL == Q)
{
return;
}
// 释放所有的结点
while (!queue_empty(Q))
{
queue_pop(Q); // 不断将结点出队
}
// 释放头结点
free(Q->head);
Q->head = Q->tail = NULL; // 防止野指针
// 释放队列空间
free(Q);
Q = NULL;
printf("释放成功\n");
}
11 > 求队列长度
// 求队列长度
int queue_size(QueuePtr Q)
{
// 判断逻辑
if (NULL == Q)
{
return -1;
}
return Q->head->len;
}
2.6 所有代码
1 > looplinkqueue.h
#ifndef LOOPLINKQUEUE_H
#define LOOPLINKQUEUE_H
#include <myhead.h>
// 定义数据元素类型
typedef int datatype;
// 定义结点类型
typedef struct Node
{
union
{
datatype data; // 数据域
int len; // 长度
};
struct Node *next; // 指针域
} Node, *NodePtr;
// 定义队列类型
typedef struct
{
NodePtr head; // 头指针
NodePtr tail; // 尾指针
} Queue, *QueuePtr;
// 创建队列
QueuePtr queue_create();
// 判空
int queue_empty(QueuePtr Q);
// 入队
void queue_push(QueuePtr Q, datatype e);
// 遍历队列
void queue_show(QueuePtr Q);
// 出队
void queue_pop(QueuePtr Q);
// 求队列长度
int queue_size(QueuePtr Q);
// 销毁队列
void queue_destroy(QueuePtr Q);
#endif
2 > looplinkqueue.c
#include "looplinkqueue.h"
// 创建队列
QueuePtr queue_create()
{
// 堆区申请一个队列的空间
QueuePtr Q = (QueuePtr)malloc(sizeof(Queue));
if (NULL == Q)
{
printf("创建失败\n");
return NULL;
}
// 此时 Q->head 和Q->tail是两个野指针
// 创建一个链表
Q->head = (NodePtr)malloc(sizeof(Node));
if (Q->head == NULL)
{
printf("创建失败\n");
free(Q); // 释放队列空间
return NULL;
}
// 给头结点初始化
Q->head->len = 0;
Q->head->next = NULL;
// 将两个指针指向头结点
Q->tail = Q->head;
printf("创建成功\n");
return Q;
}
// 判空
int queue_empty(QueuePtr Q)
{
return Q->head == Q->tail;
}
// 入队
void queue_push(QueuePtr Q, datatype e)
{
// 判断逻辑
if (NULL == Q)
{
printf("入队失败\n");
return;
}
// 申请结点封装数据
NodePtr p = (NodePtr)malloc(sizeof(Node));
if (NULL == p)
{
printf("入队失败\n");
return;
}
// 初始化
p->data = e;
p->next = NULL;
// 尾插
Q->tail->next = p; // 将结点连接到链表上
// 更新尾指针
Q->tail = p;
// 表长变化
Q->head->len++;
printf("入队成功\n");
}
// 遍历队列
void queue_show(QueuePtr Q)
{
// 判断逻辑
if (NULL == Q || queue_empty(Q))
{
printf("遍历失败\n");
return;
}
// 定义遍历指针,从第一个结点出发
NodePtr q = Q->head->next;
while (q != NULL)
{
// 只要结点不为空,就输出数据域
printf("%d\t", q->data);
q = q->next; // 向后遍历
}
printf("\n");
}
// 出队
void queue_pop(QueuePtr Q)
{
// 判断逻辑
if (NULL == Q || queue_empty(Q))
{
printf("出队失败\n");
return;
}
// 出队:头删
NodePtr p = Q->head->next; // 标记
Q->head->next = p->next; // 孤立
printf("%d出队成功\n", p->data);
free(p); // 删除
p = NULL;
// 判断是否已经全部删除
if (Q->head->next == NULL)
{
// 尾指针重新指向头结点
Q->tail = Q->head;
}
// 表长变化
Q->head->len--;
}
// 求队列长度
int queue_size(QueuePtr Q)
{
// 判断逻辑
if (NULL == Q)
{
return -1;
}
return Q->head->len;
}
// 销毁队列
void queue_destroy(QueuePtr Q)
{
// 判断逻辑
if (NULL == Q)
{
return;
}
// 释放所有的结点
while (!queue_empty(Q))
{
queue_pop(Q); // 不断将结点出队
}
// 释放头结点
free(Q->head);
Q->head = Q->tail = NULL; // 防止野指针
// 释放队列空间
free(Q);
Q = NULL;
printf("释放成功\n");
}
3 > main.c
#include "looplinkqueue.h"
int main(int argc, const char *argv[])
{
// 调用创建函数
QueuePtr Q = queue_create();
if (NULL == Q)
{
return -1;
}
// 调用入队函数
queue_push(Q, 111);
queue_push(Q, 222);
queue_push(Q, 333);
queue_push(Q, 444);
// 调用遍历函数
queue_show(Q);
// 调用出队函数
queue_pop(Q);
queue_pop(Q);
// 释放队列空间
queue_destroy(Q);
Q = NULL;
return 0;
}