1.栈
栈是数据结构中的一种线性结构,栈的出入数据只能从一端进行,所以栈的出入规律是先进后出(FILO, First In Last Out )。
栈的概念模型:
先进后出规律模型:
栈的应用:
- 算法上的递归应用
- 游戏上的多开界面,比如打开背包之后,又可以打开装备属性的界面,但是这时候背包界面 却没有关闭。
栈的实现
栈创建分析:
栈是一种只有一端出口,并且先进后出的数据结构,实现方面我们可以通过数组或者链表进行实现,他们两种实现各有优劣,小编这边使用的是数组实现,当然为了更加动态的表示栈,我们这边使用动态数组实现。
cpp
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
// 类型声明
typedef int STDataType;
typedef struct Stack
{
STDataType* a; // 动态数组
int top; // 栈顶
int capacity; // 表示空间容量
}ST;
我们需要实现的功能接口如下:
cpp
// 栈的初始化
void StackInit(ST* pst);
// 入栈
void StackPush(ST* pst, STDataType x);
// 出栈
void StackPop(ST* pst);
// 查看栈顶元素
STDataType StackTop(ST* pst);
// 计算栈的元素个数
int StackSize(ST* pst);
// 判空
bool StackEmpty(ST* pst);
// 栈的销毁
void StackDestory(ST* pst);
栈的初始化
我们这边实现栈的初始化有一个分歧,就是我们的top初始化什么?如果初始化为0,那么top代表的就是栈的元素个数,初始化为-1,那么top代表的就是动态数组的下标。
我这边为了方便后续判断栈的满我的top就初始化为0。
代码实现:
cpp
// 栈的初始化
void StackInit(ST* pst)
{
// 判空
assert(pst);
// 初始化
pst->a = NULL;
pst->top = 0;
pst->capacity = 0;
}
栈的入栈
栈为空时我们入栈的概念图,但是我们需要判断栈是否为满,如果满了就重新分配空间,没满就直接插入。
代码实现:
cpp
// 入栈
void StackPush(ST* pst, STDataType x)
{
// 判空
assert(pst);
// 判栈的满
// 我这边定义的top为0,所以代表的是数据的个数,当个数和容量相等时为满
if (pst->top == pst->capacity)
{
// 三目操作符
// 判断原空间是否为0,为0则先分配4个空间,不为0则开辟两倍空间。
int newcapacity = pst->capacity == 0 ? 4 : pst->capacity * 2;
// 动态分配空间
STDataType* tmp = (STDataType*)malloc(newcapacity * sizeof(STDataType));
// 判断是否申请成功
if (NULL == tmp)
{
perror("StackPush()::malloc fail");
return 1;
}
pst->a = tmp;
pst->capacity = newcapacity;
}
// 入栈
pst->a[pst->top] = x;
pst->top++;
}
栈的出栈
栈的出栈实现很简单,我们只需要将top--就行了,这样动态输入就访问不到原栈顶元素了,但是我们还是需要注意如果栈内的元素为0,我们是不可以执行出栈操作的。
代码实现:
cpp
// 出栈
void StackPop(ST* pst)
{
// 判空
assert(pst);
// 栈内数据不可以为0
assert(pst->top > 0);
// 出栈
pst->top--;
}
查看栈顶元素
每次查看栈顶元素需要我们访问最上层数据,在每次访问后需要记录下来返回,过程中top代表的是数据个数,返回下标需要减1。
代码实现:
cpp
// 查看栈顶元素
STDataType StackTop(ST* pst)
{
// 判空
assert(pst);
// 栈内数据不可以为0
assert(pst->top > 0);
return pst->a[pst->top - 1];
}
计算栈的元素个数
由于我们前边的top定义是0,top代表的是栈内数据的个数,我们直接返回top即可。
代码实现:
cpp
// 计算栈的元素个数
int StackSize(ST* pst)
{
// 判空
assert(pst);
return pst->top;
}
判空
判空也很简单我们只需要验证top是否为0即可。
代码实现:
cpp
// 判空
bool StackEmpty(ST* pst)
{
// 判空
assert(pst);
return pst->top == 0;
}
栈的销毁
由于我们的栈内数据是由动态内存申请的,所以我们最后需要释放动态内存在堆区申请的空间,防止空间泄露。
cpp
// 栈的销毁
void StackDestory(ST* pst)
{
// 判空
assert(pst);
free(pst->a);
pst->a = NULL;
pst->capacity = pst->top = 0;
}
栈整个代码实现和测试文件:
Stack.h
cpp
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
// 类型声明
typedef int STDataType;
typedef struct Stack
{
STDataType* a; // 动态数组
int top; // 栈顶
int capacity; // 表示空间容量
}ST;
// 栈的初始化
void StackInit(ST* pst);
// 栈的销毁
void StackDestory(ST* pst);
// 入栈
void StackPush(ST* pst, STDataType x);
// 出栈
void StackPop(ST* pst);
// 查看栈顶元素
STDataType StackTop(ST* pst);
// 计算栈的元素个数
int StackSize(ST* pst);
// 判空
bool StackEmpty(ST* pst);
Stack.c
cpp
#include "Stack.h"
// 栈的初始化
void StackInit(ST* pst)
{
// 判空
assert(pst);
// 初始化
pst->a = NULL;
pst->top = 0;
pst->capacity = 0;
}
// 入栈
void StackPush(ST* pst, STDataType x)
{
// 判空
assert(pst);
// 判栈的满
// 我这边定义的top为0,所以代表的是数据的个数,当个数和容量相等时为满
if (pst->top == pst->capacity)
{
// 三目操作符
// 判断原空间是否为0,为0则先分配4个空间,不为0则开辟两倍空间。
int newcapacity = pst->capacity == 0 ? 4 : pst->capacity * 2;
// 动态分配空间
STDataType* tmp = (STDataType*)malloc(newcapacity * sizeof(STDataType));
// 判断是否申请成功
if (NULL == tmp)
{
perror("StackPush()::malloc fail");
return 1;
}
pst->a = tmp;
pst->capacity = newcapacity;
}
// 入栈
pst->a[pst->top] = x;
pst->top++;
}
// 出栈
void StackPop(ST* pst)
{
// 判空
assert(pst);
// 栈内数据不可以为0
assert(pst->top > 0);
// 出栈
pst->top--;
}
// 查看栈顶元素
STDataType StackTop(ST* pst)
{
// 判空
assert(pst);
// 栈内数据不可以为0
assert(pst->top > 0);
return pst->a[pst->top - 1];
}
// 计算栈的元素个数
int StackSize(ST* pst)
{
// 判空
assert(pst);
return pst->top;
}
// 判空
bool StackEmpty(ST* pst)
{
// 判空
assert(pst);
return pst->top == 0;
}
// 栈的销毁
void StackDestory(ST* pst)
{
// 判空
assert(pst);
free(pst->a);
pst->a = NULL;
pst->capacity = pst->top = 0;
}
Text.c
cpp
#include "Stack.h"
int main()
{
ST s1;
// 栈的初始化
StackInit(&s1);
// 入栈
StackPush(&s1, 1);
StackPush(&s1, 2);
StackPush(&s1, 3);
StackPush(&s1, 4);
// 出栈
/*printf("%d ", StackTop(&s1));
StackPop(&s1);
printf("%d ", StackTop(&s1));
StackPop(&s1);
printf("%d ", StackTop(&s1));
StackPop(&s1);
printf("%d ", StackTop(&s1));*/
// 报错
/*StackPop(&s1);
printf("%d ", StackTop(&s1));*/
// 如果栈的元素不为0,则打印,然后删除
/*while (!StackEmpty(&s1))
{
printf("%d ", StackTop(&s1));
StackPop(&s1);
}*/
// 计算栈的元素个数
int ret = StackSize(&s1);
printf("栈的数据个数为:%d", ret);
// 栈的销毁
StackDestory(&s1);
return 0;
}
2.队列
队列是数据结构中的一种线性结构,队列的出入数据只能从队尾进行,出数据只能从队首进行,所以队列的出入规律是先进先出(FIFO, First In First Out )。
队列的概念模型:
先进先出的规律:
队列的数据是由一端对尾进入,队首出去,所以是先进先出的规律。
栈的应用:
- 我们平时点奶茶时会在小程序上获得一个排序号,这就是通过队列实现的。
- 为了保证公平性的抽号机器
队列的实现
队列创建分析:
同栈一样,队列也属于线性的数据结构,实现队列我们也可以通过数组或者链表来实现。
数组实现:
找首位数据很容易,但是入队列和出队列都要对整体的数据进行挪动太过于麻烦。
链表实现:
入队列和出队列十分方便,但是每次都需要找队尾,我们可以提前定义队尾指针解决这个问题。
综上我决定使用链表实现队列。
队列节点:
cpp
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
// 类型声明
typedef int QDataType;
// 每个队列数据的节点创建
typedef struct QueueNode
{
QDataType data; // 存放数据
struct QueueNode* next; // 指向下一个节点的指针
}QueueNode;
为了方便使用头尾指针我们创建一个存放头尾指针的结构体,同时也具有避免传二级指针的担忧,然后再定义一个size用于记录数据的个数。
cpp
// 存放头尾指针的结构体
typedef struct Queue
{
QueueNode* head;
QueueNode* tail;
int size;
}Queue;
我们需要实现的功能接口如下:
cpp
// 队列的初始化
void QueueInit(Queue* pq);
// 队列的插入
void QueuePush(Queue* pq, QDataType x);
// 队列的删除
void QueuePop(Queue* pq);
// 获取队列头部元素
QDataType QueueFront(Queue* pq);
// 获取队列队尾元素
QDataType QueueBack(Queue* pq);
// 获取队列中有效元素个数
int QueueSize(Queue* pq);
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0
bool QueueEmpty(Queue* pq);
// 销毁队列
void QueueDestroy(Queue* pq);
队列的初始化
头尾指针指向NULL,将size定义为。
代码实现:
cpp
// 队列的初始化
void QueueInit(Queue* pq)
{
// 判空
assert(pq);
pq->head = pq->tail = NULL;
pq->size = 0;
}
队列的插入
代码实现:
cpp
// 队列的插入
void QueuePush(Queue* pq, QDataType x)
{
// 判空
assert(pq);
// 插入
QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));
if (NULL == newnode)
{
preeor("QueuePush()::malloc fail");
return 1;
}
newnode->next = NULL;
newnode->data = x;
// head为空
if (pq->head == NULL)
{
pq->head = pq->tail = newnode;
}
else {
pq->tail->next = newnode;
pq->tail = newnode;
}
pq->size++;
}
队列的删除
代码实现:
cpp
void QueuePop(Queue* pq)
{
// 判空
assert(pq);
assert(pq->head != NULL);
// 删除
// 只有一个节点
if (pq->head->next == NULL)
{
free(pq->head);
pq->head = pq->tail = NULL;
}
else {
Queue* next = pq->head->next;
free(pq->head);
pq->head = next;
}
pq->size--;
}
获取队列头部元素
代码实现:
cpp
QDataType QueueFront(Queue* pq)
{
// 判空
assert(pq);
assert(pq->head != NULL);
return pq->head->data;
}
获取队列队尾元素
代码实现:
cpp
QDataType QueueBack(Queue* pq)
{
// 判空
assert(pq);
assert(pq->head != NULL);
return pq->tail->data;
}
获取队列中有效元素个数
代码实现:
cpp
int QueueSize(Queue* pq)
{
// 判空
assert(pq);
return pq->size;
}
检测队列是否为空,如果为空返回非零结果,如果非空返回0
代码实现:
cpp
bool QueueEmpty(Queue* pq)
{
// 判空
assert(pq);
return pq->size == 0;
}
销毁队列
代码实现:
cpp
void QueueDestroy(Queue* pq)
{
QueueNode* cur = pq->head;
while (cur)
{
QueueNode* next = cur->next;
free(cur);
cur = next;
}
pq->head = pq->tail = NULL;
pq->size = 0;
}
队列整个代码实现和测试文件:
Queue.h
cpp
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
// 类型声明
typedef int QDataType;
// 每个队列数据的节点创建
typedef struct QueueNode
{
QDataType data; // 存放数据
struct QueueNode* next; // 指向下一个节点的指针
}QueueNode;
// 存放头尾指针的结构体
typedef struct Queue
{
QueueNode* head;
QueueNode* tail;
int size;
}Queue;
// 队列的初始化
void QueueInit(Queue* pq);
// 队列的插入
void QueuePush(Queue* pq, QDataType x);
// 队列的删除
void QueuePop(Queue* pq);
// 获取队列头部元素
QDataType QueueFront(Queue* pq);
// 获取队列队尾元素
QDataType QueueBack(Queue* pq);
// 获取队列中有效元素个数
int QueueSize(Queue* pq);
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0
bool QueueEmpty(Queue* pq);
// 销毁队列
void QueueDestroy(Queue* pq);
Queue.c
cpp
#include "Queue.h"
// 队列的初始化
void QueueInit(Queue* pq)
{
// 判空
assert(pq);
pq->head = pq->tail = NULL;
pq->size = 0;
}
// 队列的插入
void QueuePush(Queue* pq, QDataType x)
{
// 判空
assert(pq);
// 插入
QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));
if (NULL == newnode)
{
preeor("QueuePush()::malloc fail");
return 1;
}
newnode->next = NULL;
newnode->data = x;
// head为空
if (pq->head == NULL)
{
pq->head = pq->tail = newnode;
}
else {
pq->tail->next = newnode;
pq->tail = newnode;
}
pq->size++;
}
// 队列的删除
void QueuePop(Queue* pq)
{
// 判空
assert(pq);
assert(pq->head != NULL);
// 删除
// 只有一个节点
if (pq->head->next == NULL)
{
free(pq->head);
pq->head = pq->tail = NULL;
}
else {
Queue* next = pq->head->next;
free(pq->head);
pq->head = next;
}
pq->size--;
}
// 获取队列头部元素
QDataType QueueFront(Queue* pq)
{
// 判空
assert(pq);
assert(pq->head != NULL);
return pq->head->data;
}
// 获取队列队尾元素
QDataType QueueBack(Queue* pq)
{
// 判空
assert(pq);
assert(pq->head != NULL);
return pq->tail->data;
}
// 获取队列中有效元素个数
int QueueSize(Queue* pq)
{
// 判空
assert(pq);
return pq->size;
}
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0
bool QueueEmpty(Queue* pq)
{
// 判空
assert(pq);
return pq->size == 0;
}
// 销毁队列
void QueueDestroy(Queue* pq)
{
QueueNode* cur = pq->head;
while (cur)
{
QueueNode* next = cur->next;
free(cur);
cur = next;
}
pq->head = pq->tail = NULL;
pq->size = 0;
}
Text.c
cpp
#include "Queue.h"
int main()
{
Queue q;
QueueInit(&q);
QueuePush(&q, 1);
QueuePush(&q, 2);
printf("%d ", QueueFront(&q));
QueuePop(&q);
QueuePush(&q, 3);
QueuePush(&q, 4);
while (!QueueEmpty(&q))
{
printf("%d ", QueueFront(&q));
QueuePop(&q);
}
QueueDestroy(&q);
return 0;
}