栈的应用 —— 计算表达式

写在前面

本篇文章主要讲讲如何用栈实现中缀表达式的计算

总共分为两部分:

(1)实现中缀表达式转为前缀或后缀表达式,这里用后缀表达式实现

(2)实现后缀表达式的计算

中缀表达式转后缀表达式

(3 × (10 + (8 - 4))) ÷ 2 - (2 + 4) × 3

首先准备一个空的栈,用来存储表达式中的运算符

从左到右依次处理中缀表达式,有以下三种情况:

① 扫描到操作数,直接将该操作数放入后缀表达式

② 扫描到运算符,先查看栈中有没有其他运算符。若有,将栈中优先级高于或等于此次扫描到的运算符低的优先级的运算符依次出栈,加入后缀表达式中,直到遇到 '(' 停止。若没有,直接压入栈中

③ 扫描到界限符,如果是 '(',直接压入栈中;如果是 ')',则将栈中的运算符依次出栈放入到后缀表达式,直到遇到 '(' 后停止,然后把 '(' 出栈,但不放入表达式中

来看看具体步骤

(1) 从左到右依次扫描,第一个是 '(',直接压入栈中

(2) 接着扫描到 3,放入后缀表达式中,此时表达式为:3

(3) '×',栈中没有其他运算符,直接压入栈中

(4) '(',直接压入栈

(5) 10,放入表达式:3 10

(6) '+',检查栈中存在 '×' 运算符,但是运算符之间有 '(',隔着,所以直接把该运算符入栈

(7) '(',入栈

(8) 8,放入表达式:3 10 8

(9) '-',入栈

(10) 4,放入表达式:3 10 8 4

(11) ')',查找栈中的其他运算符,依次出栈放入表达式。将 '-',出栈,放入后缀表达式中,下一个是 '(',把 '(' 出栈丢掉,此时后缀表达式为:3 10 8 4 -

(12) ')',查找栈中的其他运算符,依次出栈放入表达式,此时后缀表达式为:3 10 8 4 - +

(13) ')',把 '×' 出栈,放入表达式:3 10 8 4 - + ×,此时栈为空

(14) '÷',入栈

(15) 2,放入表达式:3 10 8 4 - + × 2

(16) '-',将栈中优先级高于或等于 '-' 的运算符出栈,放入表达式,然后把 '-' 入栈:3 10 8 4 - + × 2 ÷

(17) '(',入栈

(18) 2,放入表达式:3 10 8 4 - + × 2 ÷ 2

(19) '+',入栈

(20) 4,放入表达式:3 10 8 4 - + × 2 ÷ 2 4

(21) ')',把 '+',出栈放入表达式:3 10 8 4 - + × 2 ÷ 2 4 +

(22)'×',入栈

(23)3,放入表达式:3 10 8 4 - + × 2 ÷ 2 4 + 3

(24)表达式处理完毕,把栈中剩余的运算符依次出栈放入后缀表达式:3 10 8 4 - + × 2 ÷ 2 4 + 3 × -

计算机如何计算后缀表达式

3 10 8 4 - + × 2 ÷ 2 4 + 3 × -

首先也需要准备一个空的栈

从左到右依次处理后缀表达式的每一项:

① 扫描到操作数时,把该操作数压入栈中

② 扫描到运算符时,从栈中取出两个操作数,与这个运算符进行算术运算,先出栈的操作数在运算符左边,后出栈的操作数在运算符的右边。计算完之后把新的操作数压入栈中

具体过程如下

(1)3,入栈

(2)10,入栈

(3)8,入栈

(4)4,入栈

(5)'-',从栈中取出两个操作数做减法运算,再把运算结果压入栈中

(6)'+',从栈中取出两个操作数做加法运算,再把运算结果压入栈中

(7)'×',从栈中取出两个操作数做乘法运算,再把运算结果压入栈中

(8)2,入栈

(9)'÷',从栈中取出两个操作数做除法运算,再把运算结果压入栈中

(10)2,入栈

(11)4,入栈

(12)'+',从栈中取出两个操作数做加法运算,再把运算结果压入栈中

(13) 3,入栈

(14)'×',从栈中取出两个操作数做乘法运算,再把运算结果压入栈中

(15)'-',从栈中取出两个操作数做减法运算,再把运算结果压入栈中

(16)表达式处理完毕,栈中剩下的操作数就是最后的运算结果

代码实现中缀表达式的计算

将以上两个步骤合并就可以实现中缀表达式的计算了

方法一: 先把中缀表达式转为后缀表达式,再把处理后的表达式传给计算后缀表达式的函数

方法二: 准备两个空栈A和B,分别存放操作数和运算符。从左往右依次处理表达式,当扫描到操作数时,压入A中。当扫描到运算符或者界限符时,则按照中缀转后缀的处理逻辑压入B中。如果有运算符弹出,就需要从A中弹出两个栈顶元素,并与该运算符进行运算,最后将运算结果压入A。

代码实现

定义两个结构体,包含一个用来存放栈中的各个元素的静态数组以及一个指向栈顶元素的指针。数组的大小均为 30

C 复制代码
#define MaxSize 30

typedef struct {
	char data[MaxSize];
	int top;
}oStack;

typedef struct {
	double data[MaxSize];
	int top;
}fStack;

定义一个计算函数,参数为字符类型。在该函数里面声明一个操作数栈和一个运算符栈并初始化这两个栈

初始化栈:将它们的栈顶指针指向 -1 即可。在进行入栈时先要把栈顶指针 +1,这样 top 始终指向栈顶元素。(也可以让 top = 0,这样 top 指向的是下一个元素要插入的位置,也就是栈顶元素的下一个位置,需要先入栈再把 top +1)

C 复制代码
void initoStack(oStack *S) {
	S->top = -1;
}

void initfStack(fStack *S) {
	S->top = -1;
}

//  判断栈是否为空
bool stackEmpty(oStack S) {
	return S.top == -1;
} 

//  判断栈是否为空
bool StackEmpty(fStack S) {
	return S.top == -1;
}

double calculate(const char *expression) {
    oStack operatorStack;
    initoStack(&operatorStack);
    
    fStack figureStack;
    initfStack(&figureStack);
}

int main() {
    // 传入一个中缀表达式,定义为常量,以免不小心把这个表达式修改了
    const char *infixExpression = "(3×(6+(8-4)))/2-(2+4)×3";
    calculate(infixExpression);
    return 0;
}

定义一个变量 i,用来表示扫描到了中缀表达式哪个位置

用 while 循环遍历该表达式的每一个元素

当字符为数字字符时:

C 复制代码
int i = 0;
// 因为表达式可以看做是一个字符数组,字符数组以 '\0' 结尾,当遍历到 '\0' 时退出循环
while (expression[i] != '\0') {
    // isNumberChar 用于判断是否为数字字符(C语言中的 isdigit 函数)
    if (isNumberChar(expression[i])) {
        // 因为操作数可能是个位、十位、百位....,而每次遍历只能处理一个数,这样计算时就会出错,parseNumber 函数可以从当前位置往后查找,直到遇到非数字字符,然后会把开始查找时的字符和该字符之后,非数字字符之前的所有字符当成一个操作数(C语言中的 atof 函数)
        double num = parseNumber(expression, &i);
        // 把该数字字符入栈
        pushFigure(&figureStack, num);
        //  此时 i 会指向运算符,while 最后有个 i++,这样就会跳过了这个运算符,所以需要把 i--
        i--;
    }
    i++;
}

isNumberChar、parseNumber、pushFigure 函数

C 复制代码
//  判断是否为数字字符:数字字符 '0' - '9' 的ASCLL编码是连续的 
bool isNumberChar(char c) {
	return c >= '0' && c <= '9';
}

//  解析数字字符
double parseNumber(const char *expression, int *index) {
	double num = 0.0;
	int i = *index;
	
//	// 解析整数部分
	while(isNumberChar(expression[i])) {
		num = num * 10 + (expression[i] - '0');
		i++;
	}
//	// 解析小数部分
	if (expression[i] == '.') {
		i++;   // 跳过小数点
		double decimalValue = 0.0;
		double decimalPlaceValue = 0.1;
		while(isNumberChar(expression[i])) {
			decimalValue = decimalValue + (expression[i] - '0') * decimalPlaceValue;
			decimalPlaceValue *= 0.1;
			i++;
		}
		num = num + decimalValue;
	}
        // 由于处理非个位数的数字字符时,后面的数字字符都被当成了一个操作数,如果继续从这个位置遍历的话,就会重复入栈,所以需要跳过已经被入栈的部分
	*index = i;
	return num;
}

//  操作数入栈
bool pushFigure(fStack *S, double x) {
    // 若栈满则报错
	if (S->top == MaxSize - 1) return false;
	S->top++;
	S->data[S->top] = x;
	return true;
}

当字符为界限符时:

C 复制代码
int i = 0;
//  bracketsQuantity 用来记录栈中左括号的数量,遇到左括号加一,遇到右括号减一
int bracketsQuantity = 0;
while (expression[i] != '\0') {
    if (isNumberChar(expression[i])) {
        double num = parseNumber(expression, &i);
        pushFigure(&figureStack, num);
        i--;
    } else if (expression[i] == '(') {
        //  左括号数量 +1
        bracketsQuantity++;
        //  压入运算符栈
        pushOperator(&operatorStack, expression[i]);
    } else if (expression[i] == ')') {
        // 左括号数量 -1
        bracketsQuantity--;
        //  遇到右括号时,检查运算符栈中是否存在左括号,如果没有则代表表达式错误,直接停止计算
        if (bracketsQuantity < 0 || operatorStack.top == -1) {
            return 0.0;
        }
        //  把栈中的运算符依次出栈,直到遇到左括号
        while (operatorStack.top >= 0 && operatorStack.data[operatorStack.top] != '(') {
            tool(&figureStack, popOperator(&operatorStack));
        }
        popOperator(&operatorStack);  // 把 '(' 出栈丢掉
    }
    i++;
}

pushOperator、tool、popOperator 函数

C 复制代码
//  运算符入栈
bool pushOperator(oStack *S, char element) {
        //  栈满报错
	if (S->top > MaxSize - 1)
		return false;
	S->top++;
	S->data[S->top] = element;
}

//  运算符出栈
char popOperator(oStack *S) {
    if (S->top >= 0) {
        return S->data[S->top--];
    }
    //  如果栈空则返回 '\0'
    return '\0';
}

//  从栈中依次取出两个操作数并计算,将计算结果压入栈中
void tool(fStack *S, char c) {
    if ((c == '+' || c == '-' || c == '*' || c == '/')) {
        double num1;
        double num2;
        popFigure(S, &num2);
        popFigure(S, &num1);
        switch (c) {
            case '+':
                pushFigure(S, num1 + num2);
                break;
            case '-':
                pushFigure(S, num1 - num2);
                break;
            case '*':
                pushFigure(S, num1 * num2);
                break;
            case '/':
                pushFigure(S, num1 /num2);
                break;
		}
	}
}

当字符为运算符时:

C 复制代码
int i = 0;
int bracketsQuantity = 0;
while (expression[i] != '\0') {
    if (isNumberChar(expression[i])) {
        double num = parseNumber(expression, &i);
        pushFigure(&figureStack, num);
        i--;
    } else if (expression[i] == '(') {
        bracketsQuantity++;
        pushOperator(&operatorStack, expression[i]);
    } else if (expression[i] == ')') {
        // 左括号数量 -1
        bracketsQuantity--;
        if (bracketsQuantity < 0 || operatorStack.top == -1) {
            return 0.0;
        }
        while (operatorStack.top >= 0 && operatorStack.data[operatorStack.top] != '(') {
            tool(&figureStack, popOperator(&operatorStack));
        }
        popOperator(&operatorStack);
    } else if (is_operator(expression[i])) {  //  is_operator 判断是否是运算符
        //  把运算符栈中优先级大于或等于该运算符的运算符出栈
        while (operatorStack.top >= 0 && precedence(operatorStack.data[operatorStack.top]) >= precedence(expression[i])) {
            tool(&figureStack, popOperator(&operatorStack));
        }
        //  把该运算符入栈
        pushOperator(&operatorStack, expression[i]);
    }
    i++;
}

is_operator、pushOperator、precedence 函数

C 复制代码
//	判断是否为运算符 
bool is_operator(char c) {
	return (c == '+' || c == '-' || c == '*' || c == '/');
}

//  运算符入栈
bool pushOperator(oStack *S, char element) {
	if (S->top > MaxSize - 1)
		return false;
	S->top++;
	S->data[S->top] = element;
}

//	判断运算符优先级 
int precedence(char c) {
    switch(c) {
        case '+':
        case '-':
            return 1;
        case '*':
        case '/':
            return 2;
        default:
            return 0;
	}
}

计算完成后判断运算符栈中还有没有左括号

C 复制代码
while (expression[i] != '\0') {
    ......
    ......
}
if (bracketsQuantity != 0) {
    //  如果有则报错
    return 0.0;
}

把剩下的运算符出栈

C 复制代码
while (expression[i] != '\0') {
    ......
    ......
}

if (bracketsQuantity != 0) {
    //  如果有则报错
    return 0.0;
}

while (!stackEmpty(operatorStack)) {
    tool(&figureStack, popOperator(&operatorStack));
}

栈中剩下的最后一个数就是计算结果,把它返回出去

C 复制代码
double calculate(const char *expression) {
    ......
    ......
    ......
    
    double result;
    popFigure(&figureStack, &result);
    return result;
}

int main() {
    const char *infixExpression = "(3×(6+(8-4)))/2-(2+4)×3";
    double result = calculate(infixExpression);
    
    if (result == 0.0) {
        printf("表达式错误\n");
    } else {
        printf("Result: %.2f\n", result);
    }
    return 0;
}

计算结果正确

换一个表达式进行测试:2+6*(3*4)/(8-4)

试试小数:3.4*2+7/2-(3.3-2.2)

这样一个计算中缀表达式的算法就完成了,但是一个算法需要具备健壮性,所以需要对一些可能出现的错误进行处理,比如表达式中可能出现空格,这会出现什么问题大家可以自己试一下

最后附上完整代码

C 复制代码
#include <stdio.h>
#include <stdbool.h>

#define MaxSize 30

typedef struct {
	char data[MaxSize];
	int top;
}oStack;

typedef struct {
	double data[MaxSize];
	int top;
}fStack;

void initoStack(oStack *S) {
	S->top = -1;
}

void initfStack(fStack *S) {
	S->top = -1;
}

bool stackEmpty(oStack S) {
	return S.top == -1;
} 

bool StackEmpty(fStack S) {
	return S.top == -1;
}

bool pushOperator(oStack *S, char element) {
	if (S->top > MaxSize - 1)
		return false;
	S->top++;
	S->data[S->top] = element;
}

bool pushFigure(fStack *S, double x) {
	if (S->top == MaxSize - 1) return false;
	S->top++;
	S->data[S->top] = x;
	return true;
}

char popOperator(oStack *S) {
	if (S->top >= 0) {
		return S->data[S->top--];
	}
	return '\0';
}

bool popFigure(fStack *S, double *x) {
	if (S->top == -1) return false;
	*x = S->data[S->top];
	S->top--;
	return true;
}

//	判断是否为数字 (isdigit函数) 
bool isNumberChar(char c) {
	return c >= '0' && c <= '9';
}

//	判断是否为运算符 
bool is_operator(char c) {
	return (c == '+' || c == '-' || c == '*' || c == '/');
}

//	判断运算符优先级 
int precedence(char c) {
	switch(c) {
		case '+':
		case '-':
			return 1;
		case '*':
		case '/':
			return 2;
		default:
			return 0;
	}
}

//	解析数字(atof函数) 
double parseNumber(const char *expression, int *index) {
	double num = 0.0;
	int i = *index;
	
//	// 解析整数部分
	while(isNumberChar(expression[i])) {
		num = num * 10 + (expression[i] - '0');
		i++;
	}
//	// 解析小数部分
	if (expression[i] == '.') {
		i++;   // 跳过小数点
		double decimalValue = 0.0;
		double decimalPlaceValue = 0.1;
		while(isNumberChar(expression[i])) {
			decimalValue = decimalValue + (expression[i] - '0') * decimalPlaceValue;
			decimalPlaceValue *= 0.1;
			i++;
		}
		num = num + decimalValue;
	}
	*index = i;
	return num;
}

void tool(fStack *S, char c) {
	if ((c == '+' || c == '-' || c == '*' || c == '/')) {
		double num1;
		double num2;
		popFigure(S, &num2);
		popFigure(S, &num1);
		switch (c) {
			case '+':
				pushFigure(S, num1 + num2);
				break;
			case '-':
				pushFigure(S, num1 - num2);
				break;
			case '*':
				pushFigure(S, num1 * num2);
				break;
			case '/':
				pushFigure(S, num1 / num2);
				break;
		}
	}
}


double calculate(const char *expression) {
	oStack operatorStack;
    initoStack(&operatorStack);
    
    fStack figureStack;
	initfStack(&figureStack);
	 
	int i = 0;
	int bracketsQuantity = 0; 
	while (expression[i] != '\0') {
		if (expression[i] == ' ') {
			i++;
			continue;
		} else if (isNumberChar(expression[i])) {
			double num = parseNumber(expression, &i);
			pushFigure(&figureStack, num);
			while (isNumberChar(expression[i])) {
				//  跳过当前数字字符(或操作符)的部分,直到遇到空格或字符串结束符 '\0'
				i++;
				printf("1223122342434\n"); 
			}
			i--;
		} else if (expression[i] == '(') {
			bracketsQuantity++;
			pushOperator(&operatorStack, expression[i]);
		} else if (expression[i] == ')') {
			bracketsQuantity--;
			if (bracketsQuantity < 0 || operatorStack.top == -1) {
                return 0.0;
            }
			while (operatorStack.top >= 0 && operatorStack.data[operatorStack.top] != '(') {
				tool(&figureStack, popOperator(&operatorStack));
			}
			popOperator(&operatorStack);
		} else if (is_operator(expression[i])) {
			while (operatorStack.top >= 0 && precedence(operatorStack.data[operatorStack.top]) >= precedence(expression[i])) {
				tool(&figureStack, popOperator(&operatorStack));
			}
			pushOperator(&operatorStack, expression[i]);
		}
		
		i++;
	}
	if (bracketsQuantity != 0) {
        return 0.0;
    }
    
	while (!stackEmpty(operatorStack)) {
        tool(&figureStack, popOperator(&operatorStack));
    }
    
	double result;
	popFigure(&figureStack, &result);
	return result;
}

int main() {
	const char *infixExpression = "((15/(7-(1+1)))*3)-(2+(1+1))";

	double result = calculate(infixExpression);
	
	if (result == 0.0) {
		printf("表达式错误\n");
	} else {
		printf("Result: %.2f\n", result);
	}
	
    return 0;
}
相关推荐
武昌库里写JAVA21 分钟前
SpringCloud+SpringCloudAlibaba学习笔记
java·开发语言·算法·spring·log4j
小咖拉眯32 分钟前
第十六届蓝桥杯模拟赛第二期题解—Java
java·数据结构·算法·蓝桥杯·图搜索算法
Sunyanhui135 分钟前
力扣 最长回文字串-5
算法·leetcode·职场和发展
csdn_aspnet38 分钟前
C# 程序来计算三角形的面积(Program to find area of a triangle)
算法·c#
xiangxiang-39 分钟前
目标检测,图像分割,超分辨率重建
算法·机器学习·支持向量机
一直学习永不止步40 分钟前
LeetCode题练习与总结:数组中两个数的最大异或值--421
java·算法·leetcode·字典树·数组·位运算·哈希表
机器学习之心1 小时前
异常检测 | 高斯分布拟合算法异常数据检测(Matlab)
算法·数学建模·matlab·异常数据检测
Ning_.1 小时前
力扣第 66 题 “加一”
算法·leetcode
kitesxian1 小时前
Leetcode437. 路径总和 III(HOT100)
算法·深度优先
YSRM1 小时前
异或-java-leetcode
java·算法·leetcode