【数据结构与算法】受限线性表 — 栈
文章目录
- 【数据结构与算法】受限线性表 --- 栈
- 前言
- 一、栈的基本概念
- 二、栈的顺序存储
- 三、栈的分文件编写
- 四、栈的链式存储
- 五、栈的应用案例-就近分配
- 六、 中缀表达式转后缀表达式以及基于后缀表达式运算
- 总结
前言
本篇文章就栈的基本概念,栈的顺序存储,栈的分文件编写,栈的链式存储,栈的应用案例-就近分配, 中缀表达式转后缀表达式以及基于后缀表达式运算。
一、栈的基本概念
概念:
首先它是一个线性表,也就是说,栈元素具有线性关系,即前驱后继关系。只不过它是一种特殊的线性表而已。定义中说是在线性表的表尾进行插入和删除操作,这里表尾是指栈顶,而不是栈底。
特性
它的特殊之处在于限制了这个线性表的插入和删除的位置,它始终只在栈顶进行。这也就使得:栈底是固定的,最先进栈的只能在栈底。
操作:
栈的插入操作,叫做进栈,也成压栈。(如下图所示)
栈的删除操作,叫做出栈,也有的叫做弾栈,退栈。(如下图所示)
二、栈的顺序存储
- 初始化栈
#define MAX 1024
struct SStack
{
void* data[MAX]; //栈的数组
int m_size; //栈的大小
};
typedef void* SeqStack;
//初始化栈
SeqStack init_SeqStack() {
struct SStack* myStack = malloc(sizeof(struct SStack));
if (myStack == NULL)
{
return NULL;
}
//初始化数组
memset(myStack->data, 0, sizeof(void*) * MAX);
//初始化栈大小
myStack->m_size = 0;
return myStack;
}
- 入栈
//入栈
void push_SeqStack(SeqStack stack, void* data) {
//入栈本质 --- 数组尾插
if (stack == NULL)
{
return;
}
if (data == NULL)
{
return;
}
struct SStack* mystack = stack;
if (mystack->m_size == MAX)
{
return;
}
mystack->data[mystack->m_size] = data;
mystack->m_size++;
}
- 出栈
//出栈
void pop_SeqStack(SeqStack stack)
{
//出栈本质 ---数组尾删
if (stack == NULL)
{
return;
}
struct SStack* mystack = stack;
if (mystack->m_size == 0)
{
return;
}
mystack->data[mystack->m_size - 1] = NULL;
mystack->m_size--;
}
- 返回栈顶
// 返回栈顶
void* top_SeqStack(SeqStack stack)
{
if (stack == NULL)
{
return NULL;
}
struct SStack* mystack = stack;
if (mystack->m_size == 0)
{
return NULL;
}
return mystack->data[mystack->m_size - 1];
}
- 返回栈大小
// 返回栈大小
int size_SeqStack(SeqStack stack)
{
if (stack == NULL)
{
return -1;
}
struct SStack* mystack = stack;
return mystack->m_size;
}
- 判断是否是空栈
//判断栈是否为空
int isEmpty_SeqStack(SeqStack stack)
{
if (stack == NULL)
{
return -1; //返回-1 代表真 空栈
}
struct SStack* mystack = stack;
if (mystack->m_size == 0)
{
return 1;
}
return 0; //返回0 代表假 不是空栈
}
- 销毁栈
//销毁栈
void destroy_SeqStack(SeqStack stack)
{
if (stack == NULL)
{
return;
}
free(stack);
stack = NULL;
}
- 测试代码
struct Person
{
char name[64];
int age;
};
void test01()
{
//初始化栈
SeqStack myStack = init_SeqStack();
//创建数据
struct Person p1 = { "aaa", 10 };
struct Person p2 = { "bbb", 20 };
struct Person p3 = { "ccc", 30 };
struct Person p4 = { "ddd", 40 };
struct Person p5 = { "eee", 50 };
//入栈
push_SeqStack(myStack, &p1);
push_SeqStack(myStack, &p2);
push_SeqStack(myStack, &p3);
push_SeqStack(myStack, &p4);
push_SeqStack(myStack, &p5);
printf("栈的元素个数为:%d\n", size_SeqStack(myStack));
while (isEmpty_SeqStack(myStack) == 0)
{
struct Person* p = top_SeqStack(myStack);
printf("姓名:%s 年龄:%d\n", p->name, p->age);
//出栈
pop_SeqStack(myStack);
}
printf("栈的元素个数为:%d\n", size_SeqStack(myStack));
//销毁栈
destroy_SeqStack(myStack);
}
int main() {
test01();
return 0;
}
三、栈的分文件编写
- seqStack.h
#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define MAX 1024
//struct SStack
//{
// void * data[MAX]; //栈的数组
//
// int m_Size; //栈大小
//};
typedef void* SeqStack;
//初始化栈
SeqStack init_SeqStack();
//入栈
void push_SeqStack(SeqStack stack, void* data);
//出栈
void pop_SeqStack(SeqStack stack);
//返回栈顶
void* top_SeqStack(SeqStack stack);
//返回栈大小
int size_SeqStack(SeqStack stack);
//判断栈是否为空
int isEmpty_SeqStack(SeqStack stack);
//销毁栈
void destroy_SeqStack(SeqStack stack);
- seqStack.c
#include "seqStack.h"
struct SStack
{
void* data[MAX]; //栈的数组
int m_Size; //栈大小
};
//初始化栈
SeqStack init_SeqStack()
{
struct SStack* myStack = malloc(sizeof(struct SStack));
if (myStack == NULL)
{
return NULL;
}
//初始化数组
memset(myStack->data, 0, sizeof(void*) * MAX);
//初始化栈大小
myStack->m_Size = 0;
return myStack;
}
//入栈
void push_SeqStack(SeqStack stack, void* data)
{
//入栈本质 --- 数组尾插
if (stack == NULL)
{
return;
}
if (data == NULL)
{
return;
}
struct SStack* mystack = stack;
if (mystack->m_Size == MAX)
{
return;
}
mystack->data[mystack->m_Size] = data;
mystack->m_Size++;
}
//出栈
void pop_SeqStack(SeqStack stack)
{
//出栈本质 --- 数组尾删
if (stack == NULL)
{
return;
}
struct SStack* mystack = stack;
if (mystack->m_Size == 0)
{
return;
}
mystack->data[mystack->m_Size - 1] = NULL;
mystack->m_Size--;
}
//返回栈顶
void* top_SeqStack(SeqStack stack)
{
if (stack == NULL)
{
return NULL;
}
struct SStack* mystack = stack;
if (mystack->m_Size == 0)
{
return NULL;
}
return mystack->data[mystack->m_Size - 1];
}
//返回栈大小
int size_SeqStack(SeqStack stack)
{
if (stack == NULL)
{
return -1;
}
struct SStack* mystack = stack;
return mystack->m_Size;
}
//判断栈是否为空
int isEmpty_SeqStack(SeqStack stack)
{
if (stack == NULL)
{
return -1;//返回-1代表真 空栈
}
struct SStack* mystack = stack;
if (mystack->m_Size == 0)
{
return 1;
}
return 0; //返回0 代表 不是空栈
}
//销毁栈
void destroy_SeqStack(SeqStack stack)
{
if (stack == NULL)
{
return;
}
free(stack);
stack = NULL;
}
- 栈的顺序存储.c
# include "seqStack.h"
// 测试
struct Person
{
char name[64];
int age;
};
void test01()
{
//初始化栈
SeqStack myStack = init_SeqStack();
//创建数据
struct Person p1 = { "aaa", 10 };
struct Person p2 = { "bbb", 20 };
struct Person p3 = { "ccc", 30 };
struct Person p4 = { "ddd", 40 };
struct Person p5 = { "eee", 50 };
//入栈
push_SeqStack(myStack, &p1);
push_SeqStack(myStack, &p2);
push_SeqStack(myStack, &p3);
push_SeqStack(myStack, &p4);
push_SeqStack(myStack, &p5);
printf("栈的元素个数为:%d\n", size_SeqStack(myStack));
while (isEmpty_SeqStack(myStack) == 0)
{
struct Person* p = top_SeqStack(myStack);
printf("姓名:%s 年龄:%d\n", p->name, p->age);
//出栈
pop_SeqStack(myStack);
}
printf("栈的元素个数为:%d\n", size_SeqStack(myStack));
//销毁栈
destroy_SeqStack(myStack);
}
int main() {
test01();
return 0;
}
四、栈的链式存储
- 初始化
//节点结构体
struct stackNode {
struct stackNode* next;
};
//栈的结构体
struct LStack
{
struct stackNode pHeader;
int m_size;
};
typedef void* LinkStack;
//初始化
LinkStack init_LinkStack() {
struct LStack* myStack = malloc(sizeof(struct LStack));
if (myStack == NULL)
{
return NULL;
}
myStack->pHeader.next = NULL;
myStack->m_size = 0;
return myStack;
}
- 入栈
//入栈
void push_LinkStack(LinkStack stack, void * data)
{
// 入栈本质 链表头插
if (stack == NULL)
{
return;
}
if (data == NULL)
{
return;
}
struct LStack* mystack = stack;
//将用户数据 取出前4字节用
struct stackNode* New = data;
//更新链表指向
New->next = mystack->pHeader.next;
mystack->pHeader.next = New;
//更新链表长度
mystack->m_size++;
}
- 出栈
//出栈
void pop_LinkStack(LinkStack stack)
{
//出栈本质链表头删
if (stack == NULL)
{
return;
}
struct LStack* mystack = stack;
if (mystack->m_size == 0)
{
return;
}
//更新指针指向 缓存第一个有数据的节点
struct stackNode* pFirst = mystack->pHeader.next;
mystack->pHeader.next = pFirst->next;
//更新栈大小
mystack->m_size--;
- 返回栈顶元素
// 返回栈顶元素
void* top_LinkStack(LinkStack stack)
{
if (stack == NULL)
{
return NULL;
}
struct LStack* mystack = stack;
if (mystack->m_size == 0)
{
return NULL;
}
return mystack->pHeader.next;
}
- 返回栈的个数
// 返回栈的个数
int size_LinkStack(LinkStack stack)
{
if (stack == NULL)
{
return -1;
}
struct LStack* mystack = stack;
return mystack->m_size;
}
- 判断栈是否为空
//判断栈是否为空
int isEmpty_LinkStack(LinkStack stack)
{
if (stack == NULL)
{
return -1;
}
struct LStack* mystack = stack;
if (mystack->m_size == 0)
{
return 1;
}
return 0;
}
- 销毁
//销毁
void destroy_LinkStack(LinkStack stack)
{
if (stack == NULL)
{
return;
}
free(stack);
stack = NULL;
}
- 测试代码:
//测试
struct Person
{
void* node;
char name[64];
int age;
};
void test01()
{
//初始化栈
LinkStack myStack = init_LinkStack();
//创建数据
struct Person p1 = { NULL,"aaa", 10 };
struct Person p2 = { NULL,"bbb", 20 };
struct Person p3 = { NULL,"ccc", 30 };
struct Person p4 = { NULL,"ddd", 40 };
struct Person p5 = { NULL,"eee", 50 };
//入栈
push_LinkStack(myStack, &p1);
push_LinkStack(myStack, &p2);
push_LinkStack(myStack, &p3);
push_LinkStack(myStack, &p4);
push_LinkStack(myStack, &p5);
printf("链式存储-- 栈的元素个数为: %d\n", size_LinkStack(myStack));
while (isEmpty_LinkStack(myStack) == 0) //栈不为空, 查看栈顶元素,出栈
{
struct Person* p = top_LinkStack(myStack);
printf("姓名: %s 年龄:%d\n", p->name, p->age);
//出栈
pop_LinkStack(myStack);
}
printf("链式存储-- 栈的元素个数为: %d\n", size_LinkStack(myStack));
destroy_LinkStack(myStack);
}
五、栈的应用案例-就近分配
几乎所有的编译器都具有检测括号是否匹配的能力,那么如何实现编译器中的符号成对检测?
如下字符串: 5+5*(6)+9/3*1)-(1+3(
算法思路
从第一个字符开始扫描
当遇见普通字符时忽略,
当遇见左括号时压入栈中
当遇见右括号时从栈中弹出栈顶符号,并进行匹配
匹配成功:继续读入下一个字符
匹配失败:立即停止,并报错
结束:
成功: 所有字符扫描完毕,且栈为空
失败:匹配失败或所有字符扫描完毕但栈非空
#include "seqStack.h"
/*
从第一个字符开始扫描
当遇见普通字符时忽略,
当遇见左括号时压入栈中
当遇见右括号时从栈中弹出栈顶符号,并进行匹配
匹配成功:继续读入下一个字符
匹配失败:立即停止,并报错
结束:
成功: 所有字符扫描完毕,且栈为空
失败:匹配失败或所有字符扫描完毕但栈非空
*/
int isLeft(char ch)
{
return ch == '(';
}
int isRight(char ch)
{
return ch == ')';
}
void printError(char* str, char* errMsg, char* pos)
{
printf("错误信息:%s\n", errMsg);
printf("%s\n", str);
//计算打印空格数量
int num = pos - str;
for (int i = 0; i < num; i++)
{
printf(" ");
}
printf("^\n");
}
void test01()
{
char* str = "5+5*(6)+9/3*1-(1+3(";
char* p = str;
//初始化栈
SeqStack myStack = init_SeqStack();
while (*p != '\0')
{
//如果是左括号,入栈
if (isLeft(*p))
{
//入栈
push_SeqStack(myStack,p);
}
//如果是又括号
if (isRight(*p))
{
//栈中有元素 出栈
if (size_SeqStack(myStack) > 0)
{
pop_SeqStack(myStack);
}
else
{
//右括号没用匹配到对应的左括号,立即停止,并报错
printError(str, "右括号没有匹配到对应的左括号!", p);
break;
}
}
p++;
}
//遍历结束 判断是否有 左括号没有匹配到对应的右括号
while (size_SeqStack(myStack) > 0)
{
printError(str, "左括号没有匹配到对应的右括号!", top_SeqStack(myStack));
//出栈
pop_SeqStack(myStack);
}
//销毁栈
destroy_SeqStack(myStack);
myStack = NULL;
}
int main()
{
test01();
return 0;
}
总结
当需要检测成对出现但又互不相邻的事物时可以使用栈“后进先出”的特性
栈非常适合于需要“就近匹配”的场合
六、 中缀表达式转后缀表达式以及基于后缀表达式运算
中缀转后缀算法:
遍历中缀表达式中的数字和符号:
对于数字:直接输出
对于符号:
左括号:进栈
运算符号:与栈顶符号进行优先级比较
若栈顶符号优先级低:此符号进栈
(默认栈顶若是左括号,左括号优先级最低)
若栈顶符号优先级不低:将栈顶符号弹出并输出,之后进栈
右括号:将栈顶符号弹出并输出,直到匹配左括号,将左括号和右括号同时舍弃
遍历结束:将栈中的所有符号弹出并输出
实例
5 + 4 => 5 4 +
1 + 2 * 3 => 1 2 3 * +
8 +( 3 – 1 ) * 5 => 8 3 1 – 5 * +
计算规则
遍历后缀表达式中的数字和符号
对于数字:进栈
对于符号:
从栈中弹出右操作数
从栈中弹出左操作数
根据符号进行运算
将运算结果压入栈中
遍历结束:栈中的唯一数字为计算结果
例如:8 3 1 – 5 * +
总结
到这里这篇文章的内容就结束了,谢谢大家的观看,如果有好的建议可以留言喔,谢谢大家啦!