前言
最近在学习数据结构(C语言版),碰到了一个题目如下:
栈采用顺序栈存储,试设计算法实现将表达式转换成后缀表达式输出。
例如,输入表达式: a+b/c-(d*e+f)*g
输出其后缀表达式:abc/+de*f+g*-
代码在VS2022中编写的,X64平台,C语言编写,使用了&引用形式,所以在.cpp中运行。代码不一定是最好的,但是可以正常编译与使用的,仅是我个人的一点思考、个人原创,欢迎批评指正与交流讨论。
原理
一.理解后缀表达式的算法:
以题目中的表达式为例:a+b/c-(d*e+f)*g
先算括号内的,且乘除法运算顺序高于加减,可以得到 de*,并将其看作一个整体;
然后继续看括号内,可以得到de*f+,又将其看作一个整体;
之后因为计算机读取顺序是从左往右,所以可以得到bc/,将其看作一个整体;
类似得到abc/+;
de*f+g*;
abc/+de*f+g*-
二.理解栈的特性:
栈是一种特殊且重要的数据存储结构,具有后进先出的特点,关于栈的操作包括:构建栈结构体,初始化栈,出栈,入栈,取栈中元素等。
本题目要求的是使用顺序栈。
栈的运用有:函数的嵌套调用,递归的实现(比如汉诺塔问题),回文问题,多进制的转换,表达式求值,著名的地图四染色问题,迷宫问题等。
三.问题求解:
1.构建栈结构体
//栈结构体
typedef char elem_type;
typedef struct Stack
{
elem_type* base;
elem_type* top;
int stack_size;
}Stack;
2.初始化栈
使用malloc函数动态开辟内存,并将栈头指针(S.top)与栈尾指针(S.base)对齐。
//栈的初始化函数
int InitStack(Stack& S)
{
S.base = (elem_type*)malloc(INIT_SIZE * sizeof(elem_type));
if (!S.base)
return 0;
S.top = S.base;
S.stack_size = INIT_SIZE;
return 1;
}
3.出栈与入栈
此处注意两个点:
(1)出栈语句中e=*--S.top
*与--优先级相同,优先级相同时则分析结合方向,单目运算结合方向为从右向左,且前置减减为“先减后赋值”。
即该语句等价为:
S.top=S.top-1;
e=*S.top;
入栈语句类似分析
(2)realloc函数的使用,可自行回顾,此处不再赘述
//出栈函数
int OutStack(Stack& S,elem_type &e)
{
if (S.base==S.top)
{
return 0;
}
e = *--S.top;
return 1;
}
//入栈函数
int InStack(Stack& S, elem_type e)
{
if (S.top-S.base==S.stack_size)
{
S.base = (elem_type*)realloc(S.base,(S.stack_size+ ADD_SIZE) * sizeof(elem_type));
if (!S.base)
{
return 0;
}
S.top = S.base + S.stack_size;
S.stack_size += ADD_SIZE;
}
*S.top++ = e;
return 1;
}
4.符号的优先级比较函数
input为当前读取的符号,curtop为当前符号栈中栈顶的符号。
看不太懂没关系,请移步主函数。
//符号优先级比较函数
elem_type Compare(elem_type curtop, elem_type input)
{
elem_type order='\0';
switch (input)
{
case'+':
case'-':
switch (curtop)
{
case'+':case'-':case'*':case'/':case')':
order = '>';
break;
case'(':case'#':
order = '<';
break;
}
break;
case'*':
case'/':
switch (curtop)
{
case'+':case'-':case'(':case'#':
order = '<';
break;
case'*':case'/':case')':
order = '>';
break;
}
break;
case'(':
switch (curtop)
{
case'+':case'-':case'*':case'/': case'(':case'#':
order = '<';
break;
}
break;
case')':
switch (curtop)
{
case'+':case'-':case'*':case'/':case')':
order = '>';
break;
case'(':
order = '=';
break;
}
break;
case'#':
switch (curtop)
{
case'+':case'-':case'*':case'/':case')':
order = '>';
break;
case'#':
order = '=';
break;
}
break;
}
return order;
}
5.核心算法(主函数)
我的想法是:
(1)将要转化的字符串从键盘读入字符数组c
(2)构建并初始化两个栈,即数据栈Data(负责存放字母)、符号栈Symbol(负责存放符号)
(3)在c中为字符串首尾分别添加一个‘#’符号,便于判断表达式是否结束
(4)从左向右一个个读取c中添加了‘#’的字符串,将字符与字母分别存放入对应栈,并将当前读取的符号input,与当前符号栈中栈顶的符号curtop进行优先级比较,通过判断返回的‘>’'<''='符号来对符号进行出入栈的操作。将符号入栈到Data栈中,最后从Data栈尾到栈顶为我们需要的输出结果。
int main()
{
Stack Symbol, Data;
elem_type* p;
char c[NUM] = "\0";//字符串数组中全部初始化为符号0,便于之后识别
int i = 0;
int j;
elem_type curtop, input, answer, flag;
//从键盘读取字符串表达式
printf("请输入要转化的中缀表达式:\n");
scanf("%s", c);
/*
printf("测试0:\n");
printf("%s\n", c);
*/
//栈的初始化
InitStack(Symbol);
InitStack(Data);
while (c[i]!='\0')//统计有多少个字符
{
i++;
}
//将数组整体向后移一个
for ( j = i-1; j >= 0; j--)
{
c[j + 1] = c[j];
}
c[0] = '#';//给字符串首追加一个#号
c[i+1] = '#';//给字符串末尾追加一个#号
/*
printf("测试1:\n");
printf("%s\n", c);
*/
i = 0;
while (c[i]!='\0')
{
if (c[i] == '+' || c[i] == '-' || c[i] == '*' || c[i] == '/' || c[i] == '(' || c[i] == ')'||c[i]=='#')//如果是运输符号,则入符号栈
{
if (Symbol.top-Symbol.base==0)//第一个符号一定入符号栈
{
InStack(Symbol, c[i]);
}
else
{
input = c[i];
curtop = *(Symbol.top - 1);
answer = Compare(curtop, input);
if (answer == '<')//如果输入符号比当前栈顶符号优先级高,则输入符号入栈
InStack(Symbol, c[i]);
else if (answer == '>')//如果输入符号比当前栈顶符号优先级低,则栈顶符号入数据栈,并从符号栈出
{
curtop = *(Symbol.top - 1);
answer = Compare(curtop, input);
while (answer == '>'||answer=='=')//连续与之后的符号比较
{
InStack(Data, curtop);
OutStack(Symbol, curtop);
curtop = *(Symbol.top - 1);
answer = Compare(curtop, input);
if (answer=='<')
{
InStack(Symbol, input);
}
if (answer=='=')
{
OutStack(Symbol, curtop);//消括号,消#号
answer = '<';
}
}
}
}
}
else//如果是字母,则入数据栈
InStack(Data, c[i]);
i++;
}
/*
printf("测试2:\n");
while (Data.top-Data.base!=0)
printf("%c", *(--Data.top));
*/
printf("\n转化的后缀表达式为:\n");
while (Data.base!=Data.top)
{
printf("%c", *(Data.base++));//为了方便我直接从保存最终字符串的数据栈的base开始打印
}
}
6.输出结果
这是针对本题目的输出结果
后续我进行了其他表达式的检验,输出正确
源码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define INIT_SIZE 30
#define ADD_SIZE 10
#define NUM 20
//栈结构体
typedef char elem_type;
typedef struct Stack
{
elem_type* base;
elem_type* top;
int stack_size;
}Stack;
//栈的初始化函数
int InitStack(Stack& S)
{
S.base = (elem_type*)malloc(INIT_SIZE * sizeof(elem_type));
if (!S.base)
return 0;
S.top = S.base;
S.stack_size = INIT_SIZE;
return 1;
}
//符号优先级比较函数
elem_type Compare(elem_type curtop, elem_type input)
{
elem_type order='\0';
switch (input)
{
case'+':
case'-':
switch (curtop)
{
case'+':case'-':case'*':case'/':case')':
order = '>';
break;
case'(':case'#':
order = '<';
break;
}
break;
case'*':
case'/':
switch (curtop)
{
case'+':case'-':case'(':case'#':
order = '<';
break;
case'*':case'/':case')':
order = '>';
break;
}
break;
case'(':
switch (curtop)
{
case'+':case'-':case'*':case'/': case'(':case'#':
order = '<';
break;
}
break;
case')':
switch (curtop)
{
case'+':case'-':case'*':case'/':case')':
order = '>';
break;
case'(':
order = '=';
break;
}
break;
case'#':
switch (curtop)
{
case'+':case'-':case'*':case'/':case')':
order = '>';
break;
case'#':
order = '=';
break;
}
break;
}
return order;
}
//出栈函数
int OutStack(Stack& S,elem_type &e)
{
if (S.base==S.top)
{
return 0;
}
e = *--S.top;
return 1;
}
//入栈函数
int InStack(Stack& S, elem_type e)
{
if (S.top-S.base==S.stack_size)
{
S.base = (elem_type*)realloc(S.base,(S.stack_size+ ADD_SIZE) * sizeof(elem_type));
if (!S.base)
{
return 0;
}
S.top = S.base + S.stack_size;
S.stack_size += ADD_SIZE;
}
*S.top++ = e;
return 1;
}
int main()
{
Stack Symbol, Data;
elem_type* p;
char c[NUM] = "\0";//字符串数组中全部初始化为符号0,便于之后识别
int i = 0;
int j;
elem_type curtop, input, answer, flag;
//从键盘读取字符串表达式
printf("请输入要转化的中缀表达式:\n");
scanf("%s", c);
//栈的初始化
InitStack(Symbol);
InitStack(Data);
while (c[i]!='\0')//统计有多少个字符
{
i++;
}
//将数组整体向后移一个
for ( j = i-1; j >= 0; j--)
{
c[j + 1] = c[j];
}
c[0] = '#';//给字符串首追加一个#号
c[i+1] = '#';//给字符串末尾追加一个#号
i = 0;
while (c[i]!='\0')
{
if (c[i] == '+' || c[i] == '-' || c[i] == '*' || c[i] == '/' || c[i] == '(' || c[i] == ')'||c[i]=='#')//如果是运输符号,则入符号栈
{
if (Symbol.top-Symbol.base==0)//第一个符号一定入符号栈
{
InStack(Symbol, c[i]);
}
else
{
input = c[i];
curtop = *(Symbol.top - 1);
answer = Compare(curtop, input);
if (answer == '<')//如果输入符号比当前栈顶符号优先级高,则输入符号入栈
InStack(Symbol, c[i]);
else if (answer == '>')//如果输入符号比当前栈顶符号优先级低,则栈顶符号入数据栈,并从符号栈出
{
curtop = *(Symbol.top - 1);
answer = Compare(curtop, input);
while (answer == '>'||answer=='=')//连续与之后的符号比较
{
InStack(Data, curtop);
OutStack(Symbol, curtop);
curtop = *(Symbol.top - 1);
answer = Compare(curtop, input);
if (answer=='<')
{
InStack(Symbol, input);
}
if (answer=='=')
{
OutStack(Symbol, curtop);//消括号,消#号
answer = '<';
}
}
}
}
}
else//如果是字母,则入数据栈
InStack(Data, c[i]);
i++;
}
printf("\n转化的后缀表达式为:\n");
while (Data.base!=Data.top)
{
printf("%c", *(Data.base++));//为了方便我直接从保存最终字符串的数据栈的base开始打印
}
}
源码公开,如果对你有帮助,请点赞吧👍