目录
栈和队列
概述: 栈(Stack)是一种具有特定存储结构的数据结构,它遵循先进后出的原则,即最后进入的元素首先被访问和删除。栈的基本操作包括进栈,出栈,销毁栈。栈分为数组栈和链式栈。
一、数组栈
概述: 数组栈用数组来存数据
1.1栈的数据结构为
typedef int STDtype;
typedef struct stack
{
STDtype* data; // 指向栈数组的指针 定义为指针方便动态扩展内存
int top; // 记录栈的顶点
int capacity; // 记录栈的容量
} ST;
1.2栈的初始化
初始化栈就是把容量数据顶点都置零
// 初始化栈
void InitStack(ST* ps)
{
ps->capacity = 0;
ps->data = NULL;
// top 可以给0或者1 给0意味着top 指向栈顶数据的下一个,所以在用时先给数据再加一
// top 等于 -1时 要先加1 再给数据
ps->top = 0; // ps->top = -1;
}
1.3进栈操作
// 插入栈数据
// 1. 先判断是否满容量 满容量就扩容
// 2. 扩容完检测是否扩容成功,不成功打印错误信息并退出程序
// 3. 扩容成功指向新的空间 更新新的容量大小
// 4. 插入数据
void StackPush(ST* ps, STDtype e)
{
assert(ps);
if (ps->capacity == ps->top) // 当栈顶等于容量时说明 没有容量了
{
// 没有容量有两种情况 1. 没有一个容量,四个容量 2. 容量达到设定容量了
// 申请新的容量 为原来容量的2 倍。
int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
STDtype* temp = realloc(ps->data, sizeof(STDtype) * newCapacity); // // 给ps->data 扩容,如果ps->data 为空则申请空间
if (temp == NULL)
{
printf("realloc faill\n");
exit(-1);
}
ps->data = temp; // 扩容成功 指向新的空间
ps->capacity = newCapacity; // 更新新的容量
}
ps->data[ps->top] = e;
ps->top++;
}
1.4判断栈是否为空
// 判断栈是否为空
bool StackEmpty(ST* ps)
{
return ps->top == 0;
}
注意: 上面的函数返回值为bool C语言中要使用bool 需要包含头文件 #include <stdbool.h>
1.5出栈操作
// 出栈
// 1. 判断栈是否为空,为空则返回0
// 2. 不为空用变量记录栈顶数据
// 3. 栈顶指针减1
// 4. 返回开始记录的栈顶数据
STDtype StackPop(ST* ps)
{
if (StackEmpty(ps)) // 判断是否为空
{
return 0; // 为空返回0
}
else
{
STDtype temp = ps->data[ps->top-1]; // 记录栈顶数据
ps->top--; // 栈顶指针减减
return temp; // 返回记录的栈顶
}
}
1.5销毁栈
// 销毁栈
// 1.释放指针
// 2.将指针和容量指向0
void StackDestory(ST* ps)
{
// 释放指针维护的数据
free(ps->data);
// 数据指针和容量指向0
// 数据指针将其设置为0 意味着将其指向空地址。
ps->data = NULL;
ps->capacity = 0;
}
1.6完整代码
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>
typedef int STDtype;
typedef struct stack
{
STDtype* data;
int top;
int capacity;
} ST;
// 初始化栈
void InitStack(ST* ps)
{
ps->capacity = 0;
ps->data = NULL;
ps->top = 0; // 这里初始化为0 指向栈
}
// 插入栈数据
// 1. 先判断是否满容量 满容量就扩容
// 2. 扩容完检测是否扩容成功,不成功打印错误信息并退出程序
// 3. 扩容成功指向新的空间 更新新的容量大小
// 4. 插入数据
void StackPush(ST* ps, STDtype e)
{
assert(ps);
if (ps->capacity == ps->top) // 当栈顶等于 容量时说明 没有容量了
{
// 没有容量有两种情况 1. 没有一个容量,四个容量 2. 容量达到设定容量了
// 申请新的容量 为原来容量的2 倍。
int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
STDtype* temp = realloc(ps->data, sizeof(STDtype) * newCapacity); // // 给ps->data 扩容,如果ps->data 为空则申请空间
if (temp == NULL)
{
printf("realloc faill\n");
exit(-1);
}
ps->data = temp; // 扩容成功 指向新的空间
ps->capacity = newCapacity; // 更新新的容量
}
ps->data[ps->top] = e;
ps->top++;
}
// 判断栈是否为空
bool StackEmpty(ST* ps)
{
return ps->top == 0;
}
// 出栈
// 1. 判断栈是否为空,为空则返回0
// 2. 不为空用变量记录栈顶数据
// 3. 栈顶指针减1
// 4. 返回开始记录的栈顶数据
STDtype StackPop(ST* ps)
{
if (StackEmpty(ps)) // 判断是否为空
{
return 0; // 为空返回0
}
else
{
STDtype temp = ps->data[ps->top-1]; // 记录栈顶数据
ps->top--; // 栈顶指针减减
return temp; // 返回记录的栈顶
}
}
// 销毁栈
// 1.释放指针
// 2.将指针和容量指向0
void StackDestory(ST* ps)
{
// 释放指针维护的数据
free(ps->data);
// 数据指针和容量指向0
// 数据指针将其设置为0 意味着将其指向空地址。
ps->data = NULL;
ps->capacity = 0;
}
void test01(void)
{
ST ps;
InitStack(&ps); // 初始化栈
// 栈插入数据
StackPush(&ps, 1);
StackPush(&ps, 2);
StackPush(&ps, 3);
StackPush(&ps, 4);
// 若栈不为空则出栈并打印
while (!StackEmpty(&ps))
{
printf("%d ", StackPop(&ps));
}
StackDestory(&ps); // 释放栈空间
}
int main(void)
{
test01();
return 0;
}
二、两栈共享空间
概述: 当两个栈的空间需求相反时,如一个增加另一个减少,适合使用两栈共享空间
让一个栈的栈底为数组的始端即下标为0 处,另外一个为数组的末端即下标为n-1处。如果有元素就增加就像两端靠拢。
2.1数据结构为
// 定义两栈的数据结构
#define MAXSIZE 20
typedef int STDtype;
typedef struct Stack
{
STDtype data[MAXSIZE];
int top1; // 指向栈1的顶指针
int top2; // 指向栈2的顶指针
}ST;
初始top1 可以使它指向-1 也可以使它指向0 指向-1 后再后面的运算时要先移动再运算,top2 也样的。
2.2初始化栈 置空栈
// 初始化栈
// 1. 把栈顶指针top1 指向-1 所以在进栈的时候要先移动指针再 进行赋值操作
// 栈顶指针top2 指向的是MAXSIZE 进栈时也要先移动指针再赋值
void StackInit(ST* ps)
{
ps->top1 = -1;
ps->top2 = MAXSIZE;
}
// 置空栈
// 置空栈就是让他们的顶指针复位
void StackDestory(ST* ps)
{
ps->top1 = -1;
ps->top2 = MAXSIZE;
}
2.3判断栈是否为空
当top1 和 top2 都指向初始值时 此时栈为空
// 判断栈是否为空
// 为空返回1
bool StackEmpty(ST* ps)
{
return (ps->top1 == -1 && ps->top2 == MAXSIZE);
}
2.4插入元素到栈中
// 插入元素到栈中
// 参数1 栈的指针
// 参数2 插入的数据
// 参数3 给第几个栈插入
// 方法
// 1. 先判断容量是否满了 满了就退出
// 2. 判断是插入到哪个栈
// 3. 插入数据
void StackPush(ST* ps, STDtype e, int stackNumber)
{
if (ps->top1 + 1 == ps->top2)
{
printf("栈满不能进栈\n");
exit(-1);
}
if (stackNumber == 1)
{
ps->data[++ps->top1] = e; // 栈顶指针指向的是前一个 每次进栈时要先移动栈顶指针再赋值
}
else if (stackNumber == 2)
{
ps->data[--ps->top2] = e; // 栈顶指针指向的是后一个 每次进栈时要先移动栈顶指针再赋值
}
}
2.5出栈
// 出栈
// 参数1 栈指针
// 参数2 第几个栈
// 方法
// 1. 先判断栈是否为空
// 2. 选择哪个栈出栈
//
STDtype StackPop(ST* ps, int stackNumber)
{
if (StackEmpty(ps))
{
printf("栈为空不能出栈\n");
return -1;
}
if (1 == stackNumber)
{
return ps->data[ps->top1 --]; // 进完栈 栈顶指针指向的是当前的值 所以出完栈后再进行减减
}
else if (2 == stackNumber)
{
return ps->data[ps->top2++]; // 进完栈 栈顶指针指向的是当前的值 所以出完栈后再进行加加
}
}
注意:出栈时的顶指针移动顺序和初始化时的值有关。
2.6完整代码
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdbool.h>
// 定义两栈的数据结构
#define MAXSIZE 20
typedef int STDtype;
typedef struct Stack
{
STDtype data[MAXSIZE];
int top1; // 指向栈1的顶指针
int top2; // 指向栈2的顶指针
}ST;
// 初始化栈
// 1. 把栈顶指针1 指向-1 所以在进栈的时候要先移动指针再 进行赋值操作 栈顶指针2 指向的是MAXSIZE 进栈时也要先移动指针再赋值
void StackInit(ST* ps)
{
ps->top1 = -1;
ps->top2 = MAXSIZE;
}
// 把栈置空
void StackDestory(ST* ps)
{
ps->top1 = -1;
ps->top2 = MAXSIZE;
}
// 判断栈是否为空
// 为空返回1
bool StackEmpty(ST* ps)
{
return (ps->top1 == -1 && ps->top2 == MAXSIZE);
}
// 插入元素到栈中
// 参数1 栈的指针
// 参数2 插入的数据
// 参数3 给第几个栈插入
// 方法
// 1. 先判断容量是否满了 满了就退出
// 2. 判断是插入到哪个栈
// 3. 插入数据
void StackPush(ST* ps, STDtype e, int stackNumber)
{
if (ps->top1 + 1 == ps->top2)
{
printf("栈满不能进栈\n");
exit(-1);
}
if (stackNumber == 1)
{
ps->data[++ps->top1] = e; // 栈顶指针指向的是前一个 每次进栈时要先移动栈顶指针再赋值
}
else if (stackNumber == 2)
{
ps->data[--ps->top2] = e; // 栈顶指针指向的是后一个 每次进栈时要先移动栈顶指针再赋值
}
}
// 出栈
// 参数1 栈指针
// 参数2 第几个栈
// 方法
// 1. 先判断栈是否为空
// 2. 选择哪个栈出栈
//
STDtype StackPop(ST* ps, int stackNumber)
{
if (StackEmpty(ps))
{
printf("栈为空不能出栈\n");
return -1;
}
if (1 == stackNumber)
{
return ps->data[ps->top1 --]; // 进完栈 栈顶指针指向的是当前的值 所以出完栈后再进行减减
}
else if (2 == stackNumber)
{
return ps->data[ps->top2++]; // 进完栈 栈顶指针指向的是当前的值 所以出完栈后再进行加加
}
}
void test01(void)
{
ST ps;
// 初始化栈
StackInit(&ps);
// 给栈插入数据
StackPush(&ps, 1, 1);
StackPush(&ps, 2, 1);
StackPush(&ps, 3, 1);
StackPush(&ps, 3, 2);
StackPush(&ps, 2, 2);
StackPush(&ps, 1, 2);
// 出栈
printf("%d ", StackPop(&ps, 1));
printf("%d ", StackPop(&ps, 1));
printf("%d ", StackPop(&ps, 1));
printf("%d ", StackPop(&ps, 2));
printf("%d ", StackPop(&ps, 2));
printf("%d ", StackPop(&ps, 2));
// 清除栈
StackDestory(&ps);
}
int main(void)
{
/*test01();*/
ST ps;
test01();
// 清除栈
StackDestory(&ps);
return 0;
}
三、队列
概述:只允许在一端插入数据 在另一端删除数据的线性表,队列具有先进先出
入队列:进行插入操作的一端称为队尾
出队列:进行删除一端为队头
3.1队列的数据结构
队列可以用数组来实现也可以用链来实现,但链实现的队列更加节省空间。如下是用链表实现的队列。
// 链队列的数据结构
typedef int QDataType;
typedef struct QueueNode
{
struct QueueNode* next;
QDataType data;
}QueueNode;
通常为了操作队列会定义一个头指针和尾指针,队列在此头结点创建,一般会用结构体封装一下。如下
// 定义头指针和尾指针
typedef struct Queue
{
QueueNode* head;
QueueNode* tail;
}Queue;
3.2队列的初始化
初始化队列就是初始化好头结点和尾结点。如下
// 初始化
void QueueInit(Queue* pq)
{
assert(pq);
pq->head = NULL;
pq->tail = NULL;
}
3.3 销毁队列
依次释放队列的每个元素,和单链表的释放类似
// 释放结点信息
void QueueDestory(Queue* pq)
{
assert(pq);
QueueNode* cur = pq->head; // 定义工作指针从头开始释放结点
while (cur != NULL) // 目前结点不为空就一直执行
{
QueueNode* next = cur->next; // 临时存储下一个结点
free(cur); // 释放当前结点
cur = next; // 指向存储的结点
}
pq->head = pq->tail = NULL; // 释放完给头尾结点指针置空
}
3.4 入队列
入队列就是从队尾插入数据
// 插入数据到队列
// 创建新结点 给结点数据赋值
// 链接新结点到队尾
void QueuePush(Queue* pq, QDataType x)
{
assert(pq);
QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode)); // 创建新的结点
newnode->data = x; // 给结点数据赋值
newnode->next = NULL; // 结点的尾置空
if (pq->head == NULL) // 当插入的是第一个结点时
{
pq->head = pq->tail = newnode; // 头结点和尾结点 都指向这个节点
}
else
{
pq->tail->next = newnode; // 尾结点的下一个指向新结点
pq->tail = newnode; // 更新尾结点为刚刚插入的新结点
}
}
注意: 当第一次插入数据时要判断
3.5判断队列是否为空
判断头是否为空就能得出队列是否为空
// 判断是否为空
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->head == NULL;
}
3.6删除队头一个结点
删除的是头结点或头的后继结点,看队列定义的时候头结点是否也存储信息,如存储就从头开始删除,如不存储就从头的下一结点删除。
如下头结点也是有效结点,所以出队列就是删除现有头结点再更新头结点。
// 删除结点
void QueuePop(Queue* pq)
{
assert(pq);
// 不等于空 等于就保错 提示
assert(!QueueEmpty(pq));
QueueNode* next = pq->head->next; // 临时保存要删结点的下一结点
free(pq->head); // 删除结点
pq->head = next; // 更新头结点到下一结点
if (pq->head == NULL)
{
pq->tail = NULL; // 当删除完结点head 为空时 tail 也应该为空 不然tail 是一个野指针
}
}
3.7返回前一个结点
前一个结点相当于队头的数据
// 返回队列前一个数
QDataType QueueFront(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->head->data;
}
注意:出队列就是先返回队头一个结点,然后再删除队头一个结点。即上面两步的结合组成出队列操作也可以把上面两步合为一步。
3.8返回队尾的元素
// 返回队列后一个数
QDataType QueueBack(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->tail->data;
}
3.9返回队列的大小
队列的大小可以在定义的时候多定义一个变量来记录,也可以现用现求。如下是现用现求。
int QueueSize(Queue* pq)
{
assert(pq);
int size = 0; // 定义记录变量
QueueNode* cur = pq->head; // 定义工作结点
while (cur) // 当当前结点为空时 退出
{
++size; // 记录大小
cur = cur->next; // 更新节点
}
return size; // 返回大小
}
3.10 完整代码
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdbool.h>
#include <assert.h>
// 队列的数据结构
typedef int QDataType;
typedef struct QueueNode
{
struct QueueNode* next;
QDataType data;
}QueueNode;
// 定义头指针和尾指针
typedef struct Queue
{
QueueNode* head;
QueueNode* tail;
}Queue;
// 初始化
void QueueInit(Queue* pq)
{
assert(pq);
pq->head = NULL;
pq->tail = NULL;
}
// 释放结点信息
void QueueDestory(Queue* pq)
{
assert(pq);
QueueNode* cur = pq->head;
while (cur != NULL)
{
QueueNode* next = cur->next;
free(cur);
cur = next;
}
pq->head = pq->tail = NULL;
}
// 插入数据到队列
// 创建新结点 给结点数据赋值
//
void QueuePush(Queue* pq, QDataType x)
{
assert(pq);
QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));
newnode->data = x;
newnode->next = NULL;
if (pq->head == NULL)
{
pq->head = pq->tail = newnode;
}
else
{
pq->tail->next = newnode;
pq->tail = newnode;
}
}
// 判断是否为空
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->head == NULL;
}
// 删除结点前一个节点 从头结点开始删
void QueuePop(Queue* pq)
{
assert(pq);
// 不等于空 等于就保错 提示
assert(!QueueEmpty(pq));
QueueNode* next = pq->head->next;
free(pq->head);
pq->head = next;
if (pq->head == NULL)
{
pq->tail = NULL; // d当head 出完为空时 tail 也应该为空 不然tail 是一个野指针
}
}
// 返回队列前一个数
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;
}
int QueueSize(Queue* pq)
{
assert(pq);
int n = 0;
QueueNode* cur = pq->head;
while (cur)
{
++n;
cur = cur->next;
}
return n;
}
void test01()
{
Queue q; // 创建头指针
QueueInit(&q);
QueuePush(&q, 1);
QueuePush(&q, 2);
QueuePush(&q, 3);
QueuePush(&q, 4);
QueuePush(&q, 5);
QueuePop(&q); // 删除一个节点 从前面开始删
printf("%d \n", QueueSize(&q));
while (!QueueEmpty(&q)) // 遍历队列 遍历一个删除一个
{
QDataType front = QueueFront(&q);
printf("%d ", front);
QueuePop(&q); // 删除一个队列
}
QueueDestory(&q); // 销毁队列
}
int main(void)
{
test01();
return 0;
}
运行结果
4
2 3 4 5
上一篇:C语言数据结构单链表详解
下一篇: