一、栈
(1)、基本概念
- 说明:栈是一种逻辑结构,是特殊的线性表,特殊在只能在固定的一端操作:只要满足上述条件,那么这种特殊的线性表就会呈现一种"后进先出"的逻辑。这种逻辑就被称为栈,栈在生活中随处可见,比如:堆叠的盘子、弹匣、电梯中的人们、嵌套函数的参数等
- 栈在生活中的示例
- 特点:由于约定了只能线性表固定的一端进行操作,于是栈这种特殊的线性表的"插入"、删除,另起了下面这些特定的名称
- 栈顶:可以进行插入删除的一端
- 栈底:栈顶的对端
- 入栈:将节点插入栈顶之上,也称为压栈,函数名通常为push() --- 增加数据(栈顶)
- 出栈:将节点从栈顶删除,也称为弹栈,函数名通常为pop() --- 删除数据(栈顶)
- 取栈顶:取得栈顶元素,但不出栈,函数名通常为top() --- 查改数据(一般从栈顶到栈底开始遍历)
(2)、栈的存储形式
- 说明:栈只是一种数据逻辑,如果将数据存储于内存则是另一回事。一般而言,可以采用顺序存储形成顺序栈,或采用链式存储形成链式栈
(3)、栈的基本操作
1、顺序栈的管理结构体设计
- 说明:栈顶元素是数组的最后一个元素,而栈底是数组的第一个元素;存储数据的数组,用于实际存放栈中的元素;栈顶下标,用于指示当前栈顶元素的位置;栈的容量,用于记录数组的最大长度,防止溢出。
- 图解:
- 示例代码
// 顺序栈的管理结构体
typedef struct sequential_stack
{
datatype_p data_p; // 指向顺序栈内存的指针
int capacity; // 顺序栈的容量
int top; // 顺序栈的栈顶
}sq_stack_t, *sq_stack_p;
2、初始化顺序栈
- 说明:对顺序栈进行初始化,data_p指向存储数据的地方,让栈顶等于-1
- 图解
- 示例代码
sq_stack_p SEQUENTIAL_STACK_Init(int cap_size)
{
// 申请顺序栈管理结构体的堆内存空间
sq_stack_p p = malloc(sizeof(sq_stack_t));
bzero(p, sizeof(sq_stack_t));
// 如果申请的堆内存空间成功,给堆内存空间赋值
if ( p!= NULL)
{
// 指向顺序栈内存的指针
p->data_p = malloc(sizeof(datatype)*cap_size);
if (p->data_p == NULL)
{
free(p);
return NULL;
}
// 顺序栈的容量
p->capacity = cap_size;
// 顺序栈的栈顶
p->top = -1;
}
return p;
}
3、判断顺序栈是否为空
- 说明:如果栈顶值 == -1,则说明顺序栈目前为空
- 图解
- 示例代码
bool SEQUENTIAL_STACK_IfEmpty(sq_stack_p p)
{
return (p->top ==-1);
}
4、判断顺序栈是否满了
- 说明:如果栈顶值 == capacity - 1,说明顺序栈已经满了
- 图解:
- 示例代码
bool SEQUENTIAL_STACK_IfFull(sq_stack_p p)
{
return (p->top ==p->capacity-1);
}
5、入栈(增加数据)
- 说明:说明:每增加一个数据,栈顶值 + 1,一直加到栈满为止
- 图解:
- 示例代码
int SEQUENTIAL_STACK_Push(sq_stack_p p, datatype data)
{
// 判断顺序栈是否满了
if (SEQUENTIAL_STACK_IfFull(p))
return -1;
// 数据入栈
p->top++; // 每入栈一个数据,顺序栈的栈顶值+1
p->data_p[p->top] = data; // 将数据进行赋值操作
return 0;
}
6、出栈(删除数据)
- 说明:删除栈里面的数据,直到 top == -1 为止
- 图解:
- 示例代码
int SEQUENTIAL_STACK_Pop(sq_stack_p p)
{
// 判断顺序栈是否为空
if (SEQUENTIAL_STACK_IfEmpty(p))
return -1;
// 数据出栈
bzero(&p->data_p[p->top], sizeof(datatype));
p->top--;
return 0;
}
7、取栈顶值(获取栈顶数据)
- 说明:获取栈顶数据
- 图解
- 示例代码
datatype SEQUENTIAL_STACK_GetTopData(sq_stack_p p)
{
// 判断顺序栈是否为空
if (SEQUENTIAL_STACK_IfEmpty(p))
return *((datatype_p)-1);
// 获取栈顶的值(返回指向栈顶数据的地址)
return p->data_p[p->top];
}
8、修改顺序栈的的数据
- 说明:修改栈顶数据
- 图解
- 示例代码
int SEQUENTIAL_STACK_ChangeTopData(sq_stack_p p, datatype data)
{
// 判断顺序栈是否为空
if (SEQUENTIAL_STACK_IfEmpty(p))
return -1;
// 2、修改栈顶的数据
p->data_p[p->top] = data;
return 0;
}
9、遍历数据
- 说明:根据栈顶值遍历,直到top 值小于零为止
- 图解
- 示例代码
int SEQUENTIAL_STACK_Show(sq_stack_p p)
{
// 判断顺序栈是否为空
if (SEQUENTIAL_STACK_IfEmpty(p))
return -1;
// 遍历整个顺序栈
printf("=====================顺序栈里面的数据======================\n");
for (int i = p->top; i>=0; i--)
{
printf("顺序栈里面的第[%d]个数据为 == %d\n", i, p->data_p[i].data);
}
return 0;
}
10、销毁顺序栈
- 说明:先销毁堆区二,再释放堆区一的内存,不然找不到
- 图解
(4)、顺序栈的使用
/**
******************************************************************************
* @file main.c
* @author MChine慕青
* @version V0.0.1
* @date 2025.09.10
* @brief 使用顺序栈实现数据的增删查改功能
* 环境:ubuntu16.04
* 编译:gcc main.c sequential_stack.c
* 执行:./a.out
*
******************************************************************************
* @attention
*
* 本文档只供学习使用,不得商用,违者必究
*
* 有疑问或者建议:3211735057@qq.com
*
******************************************************************************
*/
#include "sequential_stack.h"
int main(int argc, char const *argv[])
{
// (1)、初始化顺序栈
sq_stack_p p = SEQUENTIAL_STACK_Init(128);
if (p == NULL)
{
printf("初始化顺序栈失败!\n");
return -1;
}
// (2)、选择功能(增删查改、退出(销毁顺序栈))
int select = 0;
datatype new_data = {0};
datatype get_data = {0};
datatype change_data = {0};
while (1)
{
// 1、显示整个顺序栈的数据
SEQUENTIAL_STACK_Show(p);
// 2、显示功能选项
printf("请选择以下功能!\n");
printf("1、入栈(增加数据)\n");
printf("2、出栈(删除数据)\n");
printf("3、获取栈顶数据\n");
printf("4、修改栈顶数据\n");
printf("5、退出!\n");
// 3、选择要做的功能
scanf("%d", &select);
while(getchar()!='\n');
switch (select)
{
case 1:
// 提示
printf("请输入要插入的数据:\n");
// 输入数据
scanf("%d", &new_data.data);
while(getchar()!='\n');
// 添加数据到顺序栈中
SEQUENTIAL_STACK_Push(p, new_data);
break;
case 2:
// 删除顺序栈中的数据(栈顶)
SEQUENTIAL_STACK_Pop(p);
break;
case 3:
// 提示
printf("正在获取栈顶数据\n");
// 获取栈顶的数据
get_data = SEQUENTIAL_STACK_GetTopData(p);
printf("get_data.data == %d\n", get_data.data);
break;
case 4:
// 提示
printf("请输入要修改的栈顶数据\n");
// 输入数据
scanf("%d", &change_data.data);
while(getchar()!='\n');
// 修改栈顶的数据
SEQUENTIAL_STACK_ChangeTopData(p, change_data);
break;
case 5:
SEQUENTIAL_STACK_UnInit(p);
printf("系统已退出!\n");
goto exit_sys_label;
break;
}
}
exit_sys_label:
return 0;
}
二、链式栈
(1)、链式栈的设计
1、链式栈的管理结构体设计
2、初始化链式栈管理结构体
3、初始化数据节点
4、判断链式栈是否为空
5、入栈(增加数据)
6、出栈(删除数据)
7、修改栈顶元素
8、遍历数据
9、销毁链式栈
1、链式栈的管理结构体设计
- 图解
- 示例代码
// 节点设计
typedef struct node
{
// 数据域
int data;
// 指针域
struct node *next_p;
}node_t, *node_p;
// 链式栈管理结构体设计
typedef struct link_cir_stack
{
// 链式栈的栈顶指针(头节点、head_node)
node_p top_p;
// 链式栈的当前的元素个数
int num;
}lc_stack_t, *lc_stack_p;
2、初始化链式栈管理结构体
- 图解
- 示例代码
lc_stack_p LINK_CIR_STACK_Init(void)
{
// 给管理结构体申请堆内存空间
lc_stack_p p = malloc(sizeof(lc_stack_t));
bzero(p, sizeof(lc_stack_t));
// 给堆内存空间赋值
if ( p!= NULL)
{
// 链式栈栈顶指针(初始化头节点)
p->top_p = malloc(sizeof(node_t));
if (p->top_p != NULL)
{
p->top_p->next_p = p->top_p;
}
else
{
free(p);
return NULL;
}
// 链式栈当前的元素个数(记录链表有几个数据节点)
p->num = 0;
}
else
{
return NULL;
}
return p;
}
3、初始化数据节点
- 图解
- 示例代码
node_p LINK_CIR_STACK_InitDataNode(datatype data)
{
// 给数据节点申请堆内存空间
node_p p = malloc(sizeof(node_t));
bzero(p, sizeof(node_t));
// 给堆内存空间赋值
if (p!=NULL)
{
// 数据域
p->data = data;
// 指针域
p->next_p = p;
}
return p;
}
4、判断链式栈是否为空
- 图解
- 示例代码
bool LINK_CIR_STACK_IfEmpty(lc_stack_p p)
{
// 管理结构体里面的top_p就是栈顶(头节点)
node_p head_node = p->top_p;
return head_node->next_p == head_node;
}
5、入栈(增加数据)
- 图解
- 示例代码
void LINK_CIR_STACK_Push(lc_stack_p p, node_p new_node)
{
// 管理结构体里面的top_p就是栈顶(头节点)
node_p head_node = p->top_p;
// 先让new_node里面的next_p指向data_node
new_node->next_p = head_node->next_p;
// 再让head_node里面的next_p指向new_node
head_node->next_p = new_node;
// 最后让链表中的数据节点个数+1
p->num++;
}
6、出栈
- 说明:出栈也叫"弹栈",只能删除栈顶数据
- 图解
- 示例代码
int LINK_CIR_STACK_Pop(lc_stack_p p)
{
if (LINK_CIR_STACK_IfEmpty(p))
return -1;
// 管理结构体里面的top_p就是栈顶(头节点)
node_p head_node = p->top_p;
// 相关中间变量赋值
node_p last_node = head_node;
node_p del_node = head_node->next_p;
node_p next_node = del_node->next_p;
// 绕过原链表要删除的节点
last_node->next_p = next_node;
// 释放要删除节点的资源
del_node->next_p = NULL;
free(del_node);
// 管理结构体中的num需要-1
p->num--;
return 0;
}
7、遍历整个整个栈
- 说明:对整个链式栈进行遍历输出
- 图解:
- 示例代码
int LINK_CIR_STACK_Show(lc_stack_p p)
{
if (LINK_CIR_STACK_IfEmpty(p))
return -1;
// 管理结构体里面的top_p就是栈顶(头节点)
node_p head_node = p->top_p;
// 遍历整个链式栈,并逐个打印里面的数据
node_p tmp_p = NULL;
int i = 0;
printf("=====================链式栈中的数据===================\n");
for (tmp_p = head_node->next_p; tmp_p!=head_node; tmp_p=tmp_p->next_p)
{
printf("链式栈中的第%d的节点,数据为:%d\n", i, tmp_p->data.data);
i++;
}
printf("====================================================\n");
return 0;
}
8、销毁整个链式栈
- 说明:
- 图解:
- 示例代码:
void LINK_CIR_STACK_UnInit(lc_stack_p p)
{
// 1、如果链式栈为空,那么直接释放管理结构体内存即可
if (LINK_CIR_STACK_IfEmpty(p))
{
free(p->top_p); // 释放堆区2(释放头节点数据)
free(p); // 释放堆区1
return;
}
// 管理结构体里面的top_p就是栈顶(头节点)
node_p head_node = p->top_p;
// 2、销毁链式栈中的数据节点
node_p tmp_p = NULL;
node_p tmp2_p = NULL;
for (tmp_p=head_node->next_p; tmp_p!=head_node; tmp_p=tmp2_p)
{
tmp2_p = tmp_p->next_p;
free(tmp_p);
}
// 3、释放堆区2(释放头节点数据)
free(head_node);
// 4、释放堆区1
free(p);
}
9、修改栈顶数据
- 说明:由于是栈,只能修改栈顶的数据
- 图解
- 示例代码
datatype_p LINK_CIR_STACK_GetTopData(lc_stack_p p)
{
if (LINK_CIR_STACK_IfEmpty(p))
return NULL;
// 管理结构体里面的top_p就是栈顶(头节点)
node_p head_node = p->top_p;
// 获取栈顶数据
return &(head_node->next_p->data);
}
10、链式栈的使用
- 示例代码
/**
******************************************************************************
* @file main.c
* @author MChine慕青
* @version V0.0.2
* @date 2025.09.17
* @brief 使用链式栈实现数据的增删查改功能
* 前提:需要设置link_cir_stack.h里面的参数
* 环境:ubuntu22.04
* 编译:gcc main.c link_cir_stack.c
* 执行:./a.out
*
******************************************************************************
* @attention
*
* 本文档只供学习使用,不得商用,违者必究
*
* 博客网站:https://blog.csdn.net/2402_83622972?type=blog
* 有疑问或者建议:3211735057@qq.com
*
******************************************************************************
*/
#include "link_cir_stack.h"
int main(int argc, char const *argv[])
{
// (1)、初始化管理结构体
lc_stack_p p = LINK_CIR_STACK_Init();
if ( p == NULL)
{
printf("初始化链式栈管理结构体失败!\n");
return -1;
}
// (2)、选择功能(增删查改、退出(销毁链式栈))
node_p new_node = NULL;
int select = 0;
datatype new_data = {0};
datatype_p get_data = NULL;
datatype change_data = {0};
while (1)
{
// 1、显示整个链式栈的数据
LINK_CIR_STACK_Show(p);
// 2、显示功能选项
printf("请输入以下功能:\n");
printf("1、入栈(增加数据)\n");
printf("2、出栈(删除数据)\n");
printf("3、获取栈顶数据\n");
printf("4、修改栈顶数据\n");
printf("5、退出!\n");
// 3、选择要做的功能
scanf("%d", &select);
while(getchar()!='\n');
switch (select)
{
case 1:
// 提示
printf("请输入要入栈的数据:\n");
// 输入数据
scanf("%d", &new_data.data);
while(getchar()!='\n');
// 生成一个数据节点
new_node = LINK_CIR_STACK_InitDataNode(new_data);
// 将新生成的数据节点添加到链式栈中
LINK_CIR_STACK_Push(p, new_node);
break;
case 2:
// 删除链式栈中的数据(栈顶)
LINK_CIR_STACK_Pop(p);
break;
case 3:
// 提示
printf("正在获取栈顶数据\n");
// 获取栈顶数据
get_data = LINK_CIR_STACK_GetTopData(p);
if (get_data != NULL)
printf("get_data.data == %d\n", get_data->data);
break;
case 4:
// 提示
printf("请输入要修改的栈顶数据\n");
// 输入数据
scanf("%d", &change_data.data);
while(getchar()!='\n');
// 修改栈顶的数据
LINK_CIR_STACK_ChangeTopData(p, change_data);
break;
case 5:
LINK_CIR_STACK_UnInit(p);
printf("系统已退出!\n");
goto exit_sys_label;
break;
}
}
exit_sys_label:
return 0;
return 0;
}
本期内容到这里就结束了,希望这些干货能对大家有所帮助。如果觉得有用的话,不妨动动手指点个赞支持一下,你们的每一个赞都是博主持续创作的动力。
喜欢这类内容的小伙伴也可以关注一下,后续会持续更新更多实用技巧和深度解析。有想了解的主题或问题,欢迎在评论区留言互动,说不定下期内容就是为你量身定制的哦!
最后别忘了转发分享给更多需要的朋友,知识因分享而更有价值。我们下期再见!