数据结构(二)栈与队列---栈的应用:四则运算实现

2025-08-23 02:24:16 5470

栈的应用:四则运算实现

注:我们这里使用链栈来实现,当然前面的顺序栈同样可以实现,而且更加容易理解。这里我们使用链栈来练习

(一)预备知识

前缀、中缀、后缀表达式(逆波兰表达式)

union联合体使用详解

前缀表达式、中缀表达式、后缀表达式都是四则运算的表达方式,用以四则运算表达式求值

中缀表达式

中缀表达式就是常见的运算表达式,如(3+4)×5-6

前缀表达式

前缀表达式又称波兰式,前缀表达式的运算符位于操作数之前

比如:- × + 3 4 5 6

后缀表达式(逆波兰表达式)<这是我们使用的>

后缀表达式又称逆波兰表达式,与前缀表达式相似,只是运算符位于操作数之后

比如:3 4 + 5 × 6 -

(二)中缀转后缀

例如,将中缀表达式“1+((2+3)×4)-5”转换为后缀表达式的过程如下:

扫描到的元素s2(栈底->栈顶)s1 (栈底->栈顶)说明

1

1

数字,直接入栈

+

1

+

s1为空,运算符直接入栈

(

1

+ (

左括号,直接入栈

(

1

+ ( (

同上

2

1 2

+ ( (

数字

+

1 2

+ ( ( +

s1栈顶为左括号,运算符直接入栈

3

1 2 3

+ ( ( +

数字

)

1 2 3 +

+ (

右括号,弹出运算符直至遇到左括号

×

1 2 3 +

+ ( ×

s1栈顶为左括号,运算符直接入栈

4

1 2 3 + 4

+ ( ×

数字

)

1 2 3 + 4 ×

+

右括号,弹出运算符直至遇到左括号

-

1 2 3 + 4 × +

-

-与+优先级相同,因此弹出+,再压入-

5

1 2 3 + 4 × + 5

-

数字

到达最右端

1 2 3 + 4 × + 5 -

s1中剩余的运算符

因此结果为“1 2 3 + 4 × + 5 -”

步骤:

初始化两个栈:运算符栈s1和储存中间结果的栈s2;

从左至右扫描中缀表达式;

遇到操作数时,将其压s2;

遇到运算符时,比较其与s1栈顶运算符的优先级:

如果s1为空,或栈顶运算符为左括号“(”,则直接将此运算符入栈;

否则,若优先级比栈顶运算符的高,也将运算符压入s1(注意转换为前缀表达式时是优先级较高或相同,而这里则不包括相同的情况);

否则,将s1栈顶的运算符弹出并压入到s2中,再次转到(4-1)与s1中新的栈顶运算符相比较;

遇到括号时:

如果是左括号“(”,则直接压入s1;

如果是右括号“)”,则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,此时将这一对括号丢弃;

重复步骤2至5,直到表达式的最右边;

将s1中剩余的运算符依次弹出并压入s2;

依次弹出s2中的元素并输出,结果的逆序即为中缀表达式对应的后缀表达式(转换为前缀表达式时不用逆序)

(三)计算后缀表达式结果

从左至右扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(次顶元素 op 栈顶元素),并将结果入栈;重复上述过程直到表达式最右端,最后运算得出的值即为表达式的结果

例如后缀表达式“3 4 + 5 × 6 -”:

从左至右扫描,将3和4压入堆栈;

遇到+运算符,因此弹出4和3(4为栈顶元素,3为次顶元素,注意与前缀表达式做比较),计算出3+4的值,得7,再将7入栈;

将5入栈;

接下来是×运算符,因此弹出5和7,计算出7×5=35,将35入栈;

将6入栈;

最后是-运算符,计算出35-6的值,即29,由此得出最终结果。

(四)代码实现

//栈中数据类型

typedef struct Data

{

int flag; //1为字符,2为浮点数

union

{

double num;

char sign;

}number;

}data;

#define _CRT_SECURE_NO_WARNINGS

#include

#include

#define OK 1

#define ERROR 0

#define TRUE 1

#define FALSE 0

#define STACK_INIT_SIZE 100 //定义栈的初始大小

#define STACK_INCR_SIZE 10 //定义栈的增长大小

#define MAXSIZE 100 //中缀表达式的长度

typedef struct Data

{

int flag; //1为字符,2为浮点数

union

{

double num;

char sign;

}number;

}data;

typedef data ElemType;

typedef int Status;

typedef struct

{

ElemType *base; //栈底指针

ElemType *top; //栈顶指针

int stackSize; //最大容量,这是可修改的

}sqStack;

//四个基础操作

Status InitStack(sqStack *s); //初始化操作,建立一个空栈

Status ClearStack(sqStack *s); //将栈清空

Status StackEmpty(sqStack s); //若栈存在,返回true,否则返回false

int StackLength(sqStack s); //返回栈S的元素个数

Status GetTop(sqStack s, ElemType *e); //若是栈存在且非空,用e返回S的栈顶元素

Status Push(sqStack *s, ElemType e); // 若是栈存在,则插入新的元素e到栈S中并成为栈顶元素

Status Pop(sqStack *s, ElemType *e); //若是栈存在且非空,删除栈顶元素,并用e返回其值

Status DestroyStack(sqStack *s); //若是栈存在,则销毁他

void PrintStack(sqStack s); //打印所有数据,测试用,会依次将数据出栈

//初始化操作,建立一个空栈

Status InitStack(sqStack *s)

{

s->base = (ElemType *)malloc(STACK_INIT_SIZE*sizeof(ElemType));

if (!s->base)

return ERROR;

s->top = s->base; //最开始,栈顶就是栈底

s->stackSize = STACK_INIT_SIZE;

return OK;

}

//将栈清空,将栈顶指针移动到栈底即可,容量大小不要修改,数据不需要清空,数据入栈会覆盖

Status ClearStack(sqStack *s)

{

if (s == NULL)

return ERROR;

s->top = s->base;

return OK;

}

//若栈存在,返回true,否则返回false

Status StackEmpty(sqStack s)

{

if (s.base == s.top)

return TRUE;

return FALSE;

}

//返回栈S的元素个数

int StackLength(sqStack s)

{

int length = s.top - s.base; //指针之间运算,是按照其中数据大小字节来算的

return length;

}

//若是栈存在且非空,用e返回S的栈顶元素,注意:只是获取栈顶数据,不出栈

Status GetTop(sqStack s, ElemType *e)

{

if (!e || StackEmpty(s) || !s.base)

return ERROR;

*e = *(s.top - 1);

return OK;

}

//入栈操作:若是栈存在,则插入新的元素e到栈S中并成为栈顶元素

Status Push(sqStack *s, ElemType e)

{

ElemType* newStack;

if (!s->base)

return ERROR;

if (s->top-s->base>=s->stackSize) //栈满,需要再分配

{

newStack = (ElemType *)realloc(s->base, (s->stackSize + STACK_INCR_SIZE)*sizeof(ElemType)); //重新分配大小

if (!newStack) //若是分配失败,会返回NULL

{

free(s->base);

exit(0); //分配失败,直接退出

}

s->base = newStack;

//分配后需要将栈顶指针进行移动到新的位置

s->top = s->base + s->stackSize;

}

*(s->top) = e;

s->top++;

return OK;

}

//若是栈存在且非空,删除栈顶元素(只需要将栈顶指针下移即可),并用e返回其值

Status Pop(sqStack *s, ElemType *e)

{

if (!s->base || !e || StackEmpty(*s))

return ERROR;

*e = *(--s->top);

return OK;

}

//若是栈存在,则销毁他(直接将栈底指针释放即可,置为空)

Status DestroyStack(sqStack *s)

{

if (!s->base) //若是栈存在

{

s->stackSize = 0;

free(s->base);

s->base = s->top = NULL;

}

return OK;

}

//打印数据

void PrintStack(sqStack s)

{

data d;

int len = StackLength(s);

for (int i = 0; i < len;i++)

{

Pop(&s, &d);

if (d.flag == 1)

printf("%c", d.number.sign);

else

printf("%lf", d.number.num);

}

}

栈的实现和基本操作实现

//四则运算需要的函数

Status MatchBrack(char* str); //匹配括号是否正确

Status RotateStack(sqStack* s); //将栈中的数据翻转

Status GetMidStack(sqStack *s, char* str); //获取中缀表达式,将字符串转换到栈中

Status GetBackStack(sqStack *s); //获取后缀表达式

Status GetBackValue(sqStack *s, double* val); //获取后缀表达式计算出来的结果

//括号匹配

Status MatchBrack(char* str)

{

char *cur = str;

sqStack BrkSK;

data d;

char ch;

Status sta = OK;;

if (!str || !InitStack(&BrkSK))

return ERROR;

while (*cur!='\0')

{

if (*cur=='('||*cur=='['||*cur=='{')

{

d.flag = 1;

d.number.sign = *cur;

Push(&BrkSK,d);

}

else if (*cur == ')' || *cur == ']' || *cur == '}' )

{

if (!GetTop(BrkSK, &d))

{

sta = ERROR;

break;

}

ch = d.number.sign;

if ((*cur == ')'&&ch == '(') || (*cur == ']'&&ch == '[') || (*cur == '}'&&ch == '{'))

Pop(&BrkSK, &d);

else

{

sta = ERROR;

break;

}

}

cur++;

}

DestroyStack(&BrkSK);

return sta;

}

//获取中缀表达式

Status GetMidStack(sqStack *s, char* str)

{

char* cur, *start, *end;

cur = start = end = str;

char fnum[10] = { 0 };

data d;

if (!s || !str)

return ERROR;

while (*cur != '\0')

{

if (!isdigit(*cur) && *cur != '.')

{

if (cur > str&&isdigit(*(cur - 1)))

{

end = cur;

memset(fnum, 0, 10);

memcpy(fnum, start, (end - start));

d.flag = 2;

d.number.num = atof(fnum);

Push(s, d);

}

d.flag = 1;

d.number.sign = *cur;

if (isdigit(*(cur + 1)))

start = cur + 1;

Push(s, d);

}

cur++;

}

if (start > end) //最后以数字结尾,需要再进行判断

{

end = cur;

memset(fnum, 0, 10);

memcpy(fnum, start, (end - start));

d.flag = 2;

d.number.num = atof(fnum);

Push(s, d);

}

return OK;

}

//将栈中的数据翻转

Status RotateStack(sqStack* s)

{

sqStack tempSk, *freeSk;

if (!InitStack(&tempSk) || !s)

return ERROR;

data d;

int length = StackLength(*s);

for (int i = 0; i < length; i++)

{

Pop(s, &d);

Push(&tempSk, d);

}

freeSk = s;

*s = tempSk;

DestroyStack(freeSk);

}

//获取后缀表达式

Status GetBackStack(sqStack *s)

{

sqStack OperaSk, ResSk, *tempSk;

OperaSk.base = ResSk.base = NULL;

data d, top;

char pc; //存储单个运算符

int length = StackLength(*s);

if (!s)

return ERROR;

if (!InitStack(&OperaSk) || !InitStack(&ResSk))

{

DestroyStack(&OperaSk);

DestroyStack(&ResSk);

return ERROR;

}

for (int i = 0; i < length; i++)

{

Pop(s, &d);

if (d.flag == 2)

Push(&ResSk, d);

if (d.flag == 1)

{

if (GetTop(OperaSk, &top))

pc = top.number.sign;

if (d.number.sign == ')')

{

//将'('之上的运算符全部弹出到ResSk

while (pc != '(')

{

Pop(&OperaSk, &d);

Push(&ResSk, d);

GetTop(OperaSk, &top);

pc = top.number.sign;

}

Pop(&OperaSk, &d); //将(一起弹出

continue;

}

if (StackEmpty(OperaSk) || pc == '(' || d.number.sign == '(') //若是OperaSk为空或者栈顶为(或者获取的运算符为(,我们直接将这个运算符压栈

Push(&OperaSk, d);

else if ((pc == '-' || pc == '+') && (d.number.sign == '*' || d.number.sign == '/')) //若是栈顶的优先级低,也压栈,但是要先将栈顶的

Push(&OperaSk, d);

else if (pc == d.number.sign || (pc == '-'&&d.number.sign == '+') || (pc == '+'&&d.number.sign == '-') || (pc == '*'&&d.number.sign == '/') || (pc == '/'&&d.number.sign == '*')) //当栈顶优先级和当前运算符一致,先弹出到ResSk,在进行压栈

{

Pop(&OperaSk, &top);

Push(&ResSk, top);

Push(&OperaSk, d);

}

else //当获取的运算符优先级低于栈顶优先级,先将栈顶运算符移栈到ResSk,然后再将当前运算符与下一次进行比较,压栈到OperaSK栈

{

Pop(&OperaSk, &top); //获取OperaSK栈顶数据

Push(&ResSk, top); //将数据放入ResSK栈

Push(s, d); //由于当前数据没有完成入栈,我们将它放回原来栈中,再次进行比较

length++;

}

}

}

while (!StackEmpty(OperaSk)) //将运算符栈中的剩余的数据全部移到结果栈

{

Pop(&OperaSk, &d);

Push(&ResSk, d);

}

tempSk = s;

*s = ResSk;

DestroyStack(tempSk);

DestroyStack(&OperaSk);

return OK;

}

//获取后缀表达式计算出来的结果

Status GetBackValue(sqStack *s, double* val)

{

sqStack OpValSk; //获取运算结果的栈

int length = StackLength(*s);

double op1, op2, value = 0;

data d, top;

if (!s || !InitStack(&OpValSk))

return ERROR;

for (int i = 0; i < length; i++)

{

Pop(s, &d);

if (d.flag == 2) //数字

Push(&OpValSk, d);

else //运算符,取两个数运算后,放回栈中

{

Pop(&OpValSk, &top);

op2 = top.number.num;

Pop(&OpValSk, &top);

op1 = top.number.num;

switch (d.number.sign)

{

case '+':

value = op1 + op2;

break;

case '-':

value = op1 - op2;

break;

case '*':

value = op1 * op2;

break;

case '/':

value = op1 / op2;

break;

default:

break;

}

d.flag = 2;

d.number.num = value;

Push(&OpValSk, d);

}

}

*val = value;

DestroyStack(&OpValSk);

return OK;

}

int main()

{

sqStack sk;

double value;

ElemType e;

char str[MAXSIZE] = { 0 };

sk.base = sk.top = NULL; //用于判断是否存在

//初始化空栈

InitStack(&sk);

//接收输入的中缀表达式

printf("please enter expression:");

scanf("%s", str);

//先进行括号匹配

if (!MatchBrack(str))

return 0;

//将字符串中的中缀表达式分割,转换为中缀表达式存放在栈中'1+2+3'--->3.0 + 2.0 + 1.0每一个都是一个栈空间数据

GetMidStack(&sk, str);

//将上面的中缀表达式翻转,变为正常的

RotateStack(&sk);

//下面将中缀表达式转后缀表达式

GetBackStack(&sk);

//将上面的后缀表达式翻转,变为正常的

RotateStack(&sk);

//获取后缀表达式结果

GetBackValue(&sk, &value);

//PrintStack(sk);

printf("Operation values:%lf", value);

//PrintStack(sk);

DestroyStack(&sk);

system("pause");

return 0;

}

(五)结果演示

(六)反思

实现了小数运算,但是不足的是对方法不熟悉,导致耗费太多时间