栈操作与表达式解析:从基础到实践
在计算机科学中,栈是一种常用的数据结构,它遵循后进先出(LIFO)的原则。本文将通过一系列函数的实现,探讨栈在括号匹配、中缀表达式转换为后缀表达式以及后缀表达式求值中的应用。
栈操作函数
为了支持栈的使用,我们定义了以下函数:
createStack(int maxSize)
:创建一个具有指定最大容量的栈。countStack(Stack* stack)
:返回栈中元素的数量。push(Stack* stack, Datatype data)
:向栈中压入一个元素。pop(Stack* stack)
:从栈中弹出一个元素。peek(Stack* stack)
:查看但不移除栈顶元素。destoryStack(Stack* stack)
:销毁栈并释放内存。
函数说明:
-
createStack(int maxSize)
- 参数:
int maxSize
- 栈的最大容量。 - 功能: 创建一个新的栈结构,并分配内存。
- 返回值:
Stack*
- 返回新创建的栈的指针。
- 参数:
-
countStack(Stack* stack)
- 参数:
Stack* stack
- 指向栈结构的指针。 - 功能: 返回栈中当前元素的数量。
- 返回值:
int
- 栈中元素的数量。
- 参数:
-
push(Stack* stack, Datatype data)
- 参数:
Stack* stack
- 指向栈结构的指针。Datatype data
- 要压入栈的数据。
- 功能: 将一个元素压入栈中。
- 注意: 如果栈已满,则不执行压栈操作,并打印“栈满”。
- 参数:
-
pop(Stack* stack)
- 参数:
Stack* stack
- 指向栈结构的指针。 - 功能: 从栈中弹出一个元素。
- 注意: 如果栈为空,则不执行弹出操作,并打印“空栈”。
- 参数:
-
peek(Stack* stack)
- 参数:
Stack* stack
- 指向栈结构的指针。 - 功能: 返回栈顶的元素,但不移除它。
- 返回值:
Datatype
- 栈顶的元素。 - 注意: 如果栈为空,则不返回任何值,并可能引发断言失败。
- 参数:
-
destoryStack(Stack* stack)
- 参数:
Stack* stack
- 指向要销毁的栈结构的指针。 - 功能: 释放栈占用的内存。
- 参数:
//创建栈
Stack* createStack(int maxSize)
{
Stack* stack = (Stack*)malloc(sizeof(Stack));
assert(stack);
stack->data = (Datatype*)malloc(sizeof(Datatype) * maxSize);
assert(stack->data);
stack->top = -1;
stack->maxSize = maxSize;
return stack;
}
//获取栈中元素个数
int countStack(Stack* stack)
{
assert(stack);
return stack->top+1;
}
//数据入栈
void push(Stack* stack, Datatype data)
{
assert(stack);
if (countStack(stack)==stack->maxSize)
{
printf("栈满\n");
return;
}
else
{
stack->top++;
stack->data[stack->top] = data;
}
}
//数据出栈
void pop(Stack* stack)
{
if (countStack(stack) == 0)
{
printf("空栈\n");
return;
}
else
{
stack->top--;
}
}
//获取栈顶元素
Datatype peek(Stack* stack)
{
assert(stack);
assert(countStack(stack));
return stack->data[stack->top];
}
//销毁栈
void destoryStack(Stack* stack)
{
free(stack->data);
free(stack);
}
在正式进入表达式求值,强烈建议大家阅读《大话数据结构》中关于该篇章的说明
中缀表达式转换为后缀表达式:transform(char* infix, char* suffix)
中缀表达式转换为后缀表达式是编程中的一个重要概念,它简化了表达式的计算过程。我们通过实现transform
函数,利用栈来处理运算符的优先级和括号,将中缀表达式转换为后缀表达式。
-
transform(char* infix, char* suffix)
- 参数:
char* infix
- 指向包含中缀表达式的字符串的指针。char* suffix
- 指向用于存储后缀表达式的字符串的指针。
- 功能: 将中缀表达式转换为后缀表达式。
- 注意: 如果输入的中缀表达式不正确,可能会打印错误消息。
- 参数:
-
is_operator(char ch)
- 参数:
char ch
- 要检查的字符。 - 功能: 判断一个字符是否是运算符(
+
,-
,*
,/
)。 - 返回值:
bool
- 如果是运算符返回true
,否则返回false
。
- 参数:
-
priority(char ch)
- 参数:
char ch
- 要检查的运算符字符。 - 功能: 返回运算符的优先级。
- 返回值:
int
- 运算符的优先级,0
表示括号,1
表示加或减,2
表示乘或除。
- 参数:
后缀表达式求值:calculate(char* suffix)
后缀表达式的计算相对直观,我们通过实现calculate
函数,使用栈来存储操作数,并在遇到运算符时执行计算,最终得到表达式的值。
-
fun(char ch, double num1, double num2)
- 参数:
char ch
- 运算符字符。double num1
- 第一个操作数。double num2
- 第二个操作数。
- 功能: 根据运算符和两个操作数计算结果。
- 返回值:
double
- 计算的结果。
- 参数:
-
calculate(char* suffix)
- 参数:
char* suffix
- 指向包含后缀表达式的字符串的指针。 - 功能: 计算后缀表达式的值。
- 返回值:
double
- 表达式计算的结果
- 参数:
函数代码:
//中缀表达式转后缀
//辅助函数:
//1.判断是否为运算符
bool is_operator(char ch)
{
return ch == '+' || ch == '-' || ch == '*' || ch == '/';
}
//2.判断优先级,优先级越高,则返回值越大
int priority(char ch)
{
//如果是,则进行下一步操作
switch (ch)
{
case '+':
case '-':
return 1;
case '*':
case '/':
return 2;
case '(':
case ')':
return 0;
}
}
//转换函数:参数说明:infix数组:键盘获取的中缀表达式,suffix数组:空的数组用来存放转换后的结果并且带出
void transform(char* infix, char* suffix)
{
//此处需要用两个指针分别遍历两个数组
char* in = infix;
char* suf = suffix;
//创建一个新的栈:栈在此处的作用就是一个规则(输入输出)
Stack* stack = createStack(100);
//开始遍历数组
while (*in != '\0')
{
//判断是否是数字:如果是,则存入suffin数组
if (isdigit(*in))
{
while (isdigit(*in)||*in=='.')
{
*suf = *in;
suf++;
in++;
};
//加空格(为了数字之间有间隔)
*suf = ' ';
suf++;
}
//如果是符号( + - * / ),则判断优先级
else if (is_operator(*in))
{
//判断栈是否为空,为空则直接将该符号入栈
if (countStack(stack) == 0)
{
push(stack, *in);
}
//不为空则需要比较优先级:
else
{
//当前字符优先级如果高,则存入
if (priority(*in) > priority(stack->data[stack->top]))
{
stack->data[++stack->top] = *in;
}
//如果in低于或者等于stack中的,则出栈,注意不要把括号也给出了
//为了避免这种情况,我们将括号的优先级置为0
else
{
//什么时候停止出栈:当in的运算符高于stack中的(如果全出完了,则停止)
while (countStack(stack) != 0 && priority(*in) <= priority(stack->data[stack->top]))
{
//将出栈的字符存入suf中
*suf = stack->data[stack->top];
suf++;
*suf = ' ';
suf++;
pop(stack);
}
//然后将该字符存入
push(stack, *in);
}
}
in++;
}
//如果in的是括号,则单独处理
//1.如果等于 ( ,则入栈
else if (*in == '(')
{
push(stack, *in);
in++;
}
//2.如果等于 ),则进行出栈,直到匹配到( 为止
else if (*in == ')')
{
while (countStack(stack) != 0 && stack->data[stack->top] != '(')
{
*suf = stack->data[stack->top];
suf++;
*suf = ' ';
suf++;
pop(stack);
}
//删除最后留下的'('
pop(stack);
in++;
}
else
{
printf("error input\n");
return;
}
}
//最后就将栈中所有操作符都出栈即可
while (countStack(stack) != 0)
{
*suf = stack->data[stack->top];
suf++;
*suf = ' ';
suf++;
pop(stack);
}
//为suf赋为\0
*suf = '\0';
destoryStack(stack);
}
//计算辅助函数(将字符转化为运算符,并且计算结果)
double fun(char ch,double num1,double num2)
{
switch (ch)
{
case '+':
return num1 + num2;
case '-':
return num2 - num1;
case '*':
return num1 * num2;
case '/':
return num2 / num1;
}
}
//后缀表达式求值
double calculate(char* suffix)
{
//指针遍历数组
char* suf = suffix;
Stack* stack=createStack(100);
while (*suf != '\0')
{
//是数字,则进入,直到将该数字全部取出
if (isdigit(*suf))
{
//将字符转化为数字
double num = (double)(*suf - '0');
suf++;
//下一位如果还是数字或者小数点(说明该数字还没有结束),那么进入循环
while (isdigit(*suf) || *suf == '.')
{
//如果是整数,乘10相加运算
if (isdigit(*suf))
{
num = num * 10 + *suf - '0';
suf++;
}
//如果是小数点,则从下一位开始*0.1相加运算,循环计算,直到得出结果
else if (*suf == '.')
{
//跳过小数点
suf++;
//frac在后面小数取出时有进位作用
float frac = 0.1;
//将小数点后面的数全部计算
while (isdigit(*suf))
{
//得到小数
float fl = (*suf - '0') * frac;
//相加
num += fl;
//如果下一位还是小数,则需要/10,例如0.12,先是1,经过一次得到0.1,下一个2变为0.02需要*0.01
frac /= 10;
suf++;
}
}
}
//经过上面的操作,此时一个数字已经被取出,我们放到栈中
push(stack, num);
}
//是运算符(不是空格),则需要出栈两个数字,并且做运算
else if(is_operator(*suf))
{
//出栈两个数
double num1 = stack->data[stack->top];
pop(stack);
double num2 = stack->data[stack->top];
pop(stack);
//计算结果,而后存入栈中
double end = fun(*suf, num1,num2);
push(stack, end);
suf++;
}
//如果是空格,则++
else
{
suf++;
}
}
//最后遍历完成,将栈中结果出栈
double end = stack->data[stack->top];
//销毁栈
destoryStack(stack);
//返回计算结果
return end;
}
测试用例: 9+(3-1)*3+10/2
输出:
结语
栈作为一种基础数据结构,在表达式解析和计算中发挥着重要作用。通过本文的函数实现和讨论,我们可以看到栈如何在实际编程问题中被有效利用。理解栈的工作原理和它在算法设计中的应用,对于任何软件开发者来说都是一个宝贵的技能。