链式栈(LinkStack)的实现与多场景应用
一、链式栈的核心设计与实现
链式栈通过链表节点存储数据,避免了顺序栈的容量限制问题。其核心结构包括栈节点(存储数据和指针)和栈管理结构(维护栈顶指针和元素数量)。
1. 头文件定义(linkstack.h)
根据不同应用场景,DATATYPE 数据类型会进行适配,以下是基础结构定义:
c
#ifndef _LINKSTACK_H_
#define _LINKSTACK_H_
// 栈节点结构
typedef struct stacknode
{
DATATYPE data; // 存储的数据
struct stacknode *next; // 指向栈底方向的节点
} LinkStackNode;
// 栈管理结构
typedef struct
{
LinkStackNode *top; // 栈顶指针
int clen; // 栈中元素数量
} LinkStack;
// 栈操作函数声明
LinkStack* CreateLinkStack(); // 创建栈
int PushLinkStack(LinkStack*ls,DATATYPE*newdata); // 入栈
int PopLinkStack(LinkStack*ls); // 出栈(基础版)
DATATYPE* GetTopLinkStack(LinkStack*ls); // 获取栈顶元素
int GetSizeLinkStack(LinkStack*ls); // 获取栈大小
int IsEmptyLinkStack(LinkStack*ls); // 判断栈是否为空
int DestroyLinkStack(LinkStack*ls); // 销毁栈
#endif
2. 核心实现(linkstack.c)
链式栈的基本操作实现如下,重点关注内存管理和指针操作:
c
#include "linkstack.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
// 创建栈
LinkStack* CreateLinkStack()
{
LinkStack* ls = (LinkStack*)malloc(sizeof(LinkStack));
if (NULL == ls)
{
printf("CreateLinkStack: malloc failed\n");
return NULL;
}
ls->top = NULL; // 初始化栈顶为空
ls->clen = 0; // 初始元素数量为0
return ls;
}
// 入栈操作
int PushLinkStack(LinkStack* ls, DATATYPE* newdata)
{
if (NULL == ls || newdata == NULL)
{
printf("PushLinkStack: invalid parameter\n");
return 1;
}
// 创建新节点
LinkStackNode* newnode = (LinkStackNode*)malloc(sizeof(LinkStackNode));
if (NULL == newnode)
{
printf("PushLinkStack: malloc node failed\n");
return 1;
}
// 存储数据并更新指针
memcpy(&newnode->data, newdata, sizeof(DATATYPE));
newnode->next = ls->top; // 新节点指向原栈顶
ls->top = newnode; // 栈顶指针指向新节点
ls->clen++;
return 0;
}
// 出栈操作
int PopLinkStack(LinkStack* ls)
{
if (IsEmptyLinkStack(ls))
{
return 1; // 栈空时出栈失败
}
LinkStackNode* tmp = ls->top; // 暂存栈顶节点
ls->top = ls->top->next; // 栈顶指针下移
free(tmp); // 释放原栈顶节点内存
ls->clen--;
return 0;
}
// 获取栈顶元素
DATATYPE* GetTopLinkStack(LinkStack* ls)
{
if(IsEmptyLinkStack(ls))
{
return NULL;
}
return &ls->top->data;
}
// 销毁栈
int DestroyLinkStack(LinkStack *ls)
{
// 依次弹出所有元素
int len = GetSizeLinkStack(ls);
for (int i = 0; i < len; i++)
{
PopLinkStack(ls);
}
free(ls); // 释放栈管理结构
return 0;
}
二、链式栈的多场景应用
1. 括号匹配检查工具
应用场景 :检查代码或文本中的括号(()[]{})是否匹配,包括类型匹配和位置追踪。
实现要点:
- 适配
DATATYPE存储括号字符及位置信息 - 遇到左括号时入栈,遇到右括号时与栈顶左括号匹配
- 处理三种错误:右括号无匹配、括号类型不匹配、左括号未闭合
c
// 括号检查专用DATATYPE定义
typedef struct {
char bracket; // 括号字符
int line; // 行号
int col; // 列号
} DATATYPE;
// 核心检查逻辑(main.c片段)
for (int i = 0; i < strlen(data); i++)
{
char curr_char = data[i];
// 处理行列号计算(略)
if (is_left_bracket(curr_char))
{
// 左括号入栈,记录位置
DATATYPE left_bracket;
left_bracket.bracket = curr_char;
left_bracket.line = line;
left_bracket.col = col - 1;
PushLinkStack(bracket_stack, &left_bracket);
}
else if (is_right_bracket(curr_char))
{
if (IsEmptyLinkStack(bracket_stack))
{
printf("错误:右括号无匹配,位置第%d行第%d列\n", line, col-1);
}
else
{
DATATYPE* top_left = GetTopLinkStack(bracket_stack);
if (!is_bracket_match(top_left->bracket, curr_char))
{
printf("错误:类型不匹配,左括号'%c'在第%d行第%d列\n",
top_left->bracket, top_left->line, top_left->col);
}
PopLinkStack(bracket_stack);
}
}
}
// 检查剩余未闭合左括号(略)
2. 简单表达式计算器
应用场景 :计算只包含 +、-、*、/ 的整数表达式(如 2*3+5)。
实现要点:
- 使用两个栈:数字栈存储操作数,运算符栈存储运算符
DATATYPE用联合体存储数字或运算符- 出栈时按"后入先算"原则计算结果
c
// 表达式计算专用DATATYPE定义
typedef union {
int num; // 存储数字
char op; // 存储运算符
} DATATYPE;
// 计算逻辑(main.c片段)
while (!IsEmptyLinkStack(op_stack))
{
// 弹出两个操作数和一个运算符
DATATYPE num2_data, num1_data, op_data;
PopLinkStack(num_stack, &num2_data);
PopLinkStack(num_stack, &num1_data);
PopLinkStack(op_stack, &op_data);
// 计算并将结果入栈
int result = calculate(num1_data.num, num2_data.num, op_data.op);
DATATYPE res_data;
res_data.num = result;
PushLinkStack(num_stack, &res_data);
}
3. 人员信息管理
应用场景:通过栈实现人员信息的入栈、出栈和遍历。
实现要点:
DATATYPE定义为人员信息结构体- 展示栈的基本数据存储能力
c
// 人员信息专用DATATYPE定义
typedef struct person
{
char name[32];
char sex;
int age;
int score;
} DATATYPE;
// 操作示例(main.c片段)
DATATYPE data[] = {
{"zhangsan", 'f', 20, 80}, {"lisi", 'm', 21, 82},
{"wangmazi", 'm', 22, 85}
};
// 入栈
for (int i = 0; i < 3; i++)
{
PushLinkStack(ls, &data[i]);
}
// 出栈并打印
for (int i = 0; i < GetSizeLinkStack(ls); i++)
{
DATATYPE tmp;
memcpy(&tmp, GetTopLinkStack(ls), sizeof(DATATYPE));
PopLinkStack(ls);
printf("name:%s age:%d\n", tmp.name, tmp.age);
}
三、总结
链式栈通过动态链表实现,相比顺序栈更灵活,无需预先指定容量。本文通过三个案例展示了其扩展性:
- 括号检查:利用栈的"后入先出"特性匹配成对元素
- 表达式计算:通过双栈协同处理运算优先级(简化版)
- 信息管理:作为通用数据容器存储结构化数据
核心设计技巧是通过 DATATYPE 的适配实现泛用性,在实际开发中可根据需求扩展更多操作(如栈元素遍历、批量入栈等)。链式栈的内存管理需特别注意,确保所有节点都能被正确释放,避免内存泄漏。