链栈的应用案例:表达式求值

 文末有完整代码,有疑问的读者可以在评论区一起讨论,也可以私信一起交流学习

一:题目要求

<1>案例分析

  任何一个表达式都是由操作数(operand)、运算符(operator)和界限符(delimiter)组成的,统称他们为单词。一般地,操作数即可以是常数也可以是被定义为变量或常亮的标识符;运算符可以分为算数运算符、关系运算符和逻辑运算符3类;基本界限符有左右括号和表达式结束符等。为了叙述的简洁,在此仅讨论简单算术表达式的求值问题,这种表达式只含加、减、乘、除4种运算符。读者不难将它推广到更一般的表达式上。

下面把运算符和界限符统称为算符;我们知道算术四则运算遵循以下三条规则:

1>先乘除,后加减

2>从左算到右

3>先括号内后括号外

根据上述3条运算规则,在运算的每一步中,任意两个相继出现的算符theta1和theta2之间的优先关系,至多是下面3种关系之一:

       theta1<theta2,即theta1的优先权低于theta2

       theta1=theta2,即theta1的优先权等于theta2

       theta1>theta2,即theta1的优先权高于theta2

1:算符间的优先关系表:

由规则(1),先进行乘除运算,后进行加减运算,所以有"+" < "*" 、"+" <" /" 、 "*" > "+" 、"/" > "+"等。
由规则(2),运算遵循左结合性,当两个运算符相同时,先出现的运算符优先级高,所以有"+" > "+"、"-" > "-" 、"*" > "*"、"/" > " I "。
由规则(3),括号内的优先级高,tahta1为"+" "_" "*" 和 "/" 时的优先级均低于tahta2为"("时的优先级,但高于tahta2为")"时的优先级。
表中的"("=")"表示当左右括号相遇时,括号内的运算已经完成。为了便于实现,假设每个表达式均以"#"开始,以 "#" 结束。所以 "#" = "#" 表示整个表达式求值完毕。")" 与 "(" 、"#" 与 ")" 以及"(" 与 "#" 之间无优先关系,这是因为表达式中不允许它们相继出现,一旦遇到这种情况,则可以认为出现了语法错误。在下面的讨论中,我们暂假定所输入的表达式不会出现语法错误。
【案例实现】
可以使用两个工作栈实现表达式求值算法,一个称作 OPTR ,用以寄存运算符;另一个称作 OPND ,用以寄存操作数或运算结果。

 <2>【算法步骤】

①初始化 OPTR 栈和 OPND 栈,将表达式起始符"#"压入 OPTR 栈。
②读取表达式,读入第一个字符 ch ,如果表达式没有读取完毕至"#"或 OPTR 的栈顶元素不为"#"时,则循环执行以下操作。
●若 ch 不是运算符,则压入 OPND 栈,读入下一字符 ch 。

●若 ch 是运算符,则根据 OPTR 的栈顶元素和 ch 的优先级比较结果,做不同的处理:
>若小于,则将 ch 压入 OPTR 栈,读入下一字符 ch ;
>若大于,则弹出 OPTR 栈顶的运算符,从 OPND 栈弹出两个数,进行相应运算,将结果压人 OPND 栈;
>若等于,则 OPTR 的栈顶元素是"("且 ch 是")",这时弹出 OPTR 栈顶的"(",相当于括号匹配成功,然后读入下一字符 ch 。
 OPND 栈顶元素即表达式求值结果,返回此元素。

                                理论指导《数据结构 C语言第二版》 严蔚敏....编著

2:ASCII码参考表:

 3:输入历程思路表:

 二、代码实现

<1>.cpp文件

#include"Module.h"
int main()
{
	char ch,x;//用以存储压入栈的运算符
	char theta;//用以存储弹出栈的运算符
	char a,b;//用以存储运算数数值
	LinkStack OPTR ;//运算符栈
	LinkStack OPND;//操作数栈
	InitStack(OPTR);//初始化栈
	InitStack(OPND);
	Push(OPTR, '#');//将#压入运算符栈OPTR中
	scanf_s("%c", &ch);//不管是数字还是字符在本例中都以字符char的形式输入以及存储
	getchar();
	while (ch != '#' || GetTop(OPTR) != '#')
	{
		if (!In(ch))//如果不是算符则:
		{
			Push(OPND, ch);//把数字字符压入OPND栈中
			scanf_s("%c", &ch);//继续读入下一个元素
			getchar();//解决输入函数,输入字符时因为缓冲区而不能正常输入的问题
		}
		else
			switch (Precede(GetTop(OPTR), ch))//比较OPTR的栈顶元素和ch的优先级
			{
			case '<':
				Push(OPTR, ch);//优先权大于栈顶算符则压入OPTR栈
				scanf_s("%c", &ch);//继续读取下一字符
				getchar();
				break;
			case '>':
				Pop(OPTR, theta);//如果小于栈顶算符则弹出栈顶算符
				Pop(OPND, b);//同时弹出OPND数栈中两个元素进行运算
				Pop(OPND, a);//例:7-2,7先输入2后输入,先弹出b2再弹出a7
				Push(OPND, Operate(a, theta, b));//做运算7-2,再把运算结果压入数栈OPND中
				break;
			case '=':
				Pop(OPTR, x);
				scanf_s("%c", &ch);
				getchar();
				break;
			}
	}
	printf("表达式计算结果为:%d\n",GetTop(OPND) - 48);//GetTop返回的数据类型为char型,减48得到我们需要的int类型
	return true;
}

<2>.h文件

#pragma once
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
#define ElemType int
//链栈的存储结构
typedef struct StackNode//使用typedef给struct StackNode起别名SN和*LinkStack
{
	ElemType data;//数据域
	StackNode* next;//指针域
}StackNode, * LinkStack;//定义两个结构体类型其中LinkStack为指向结构体的指针
//初始化函数
//链栈的初始化操作就是构造一个空栈,因为没必要设头结点,所以直接将栈顶指针置空即可
int InitStack(LinkStack& S)
{
	S = NULL;
	return true;
}
//入栈
int Push(LinkStack& S,char Ele)
{
	LinkStack p;//定义p的类型为指向结构体SN的指针
	p = new StackNode;//生成新节点并为p申请空间
	p->data = Ele;//将新结点的数据域赋值为data
	p->next = S;//将新结点插入栈顶
	S = p;//修改栈顶指针为p
	return true;
}
//出栈
int Pop(LinkStack& S,char& Ele)//弹出栈顶元素给变量Ele
{
	LinkStack p;
	if (S == NULL)
	{
		return false;
	}
	else
	{
		Ele = S->data;//用将栈顶元素的值赋给variable
		p = S;//用p临时保存栈顶元素空间,以备释放
		S = S->next;//修改栈顶指针
		delete p;//释放栈顶元素的空间
		return true;
	}
}
//取栈顶元素
int GetTop(LinkStack& S)
{
	if (S == NULL)
	{
		return false;
	}
	else
	{
		return S->data;//返回栈顶元素
	}
}
//遍历链栈元素
int Traversal(LinkStack& S)
{
	LinkStack p;
	p = S;
	if (p == NULL)
	{
		printf("链栈为空!!\n");
		return false;
	}
	else
	{
		printf("链栈存储数据为:");
		for (p = S; p != NULL; p = p->next)//链表循环
		{
			printf("%d\t", p->data);
		}
		printf("\n");
		return true;
	}
}
//In判断ch是否为运算符
int In(char ch)
{
	char arr[7] = { '+','-','*','/','(',')','#' };
	for (int i = 0; i < 7; i++)
	{
		if(ch==arr[i])//遍历char数组,如果在数组中则判定为算符返回true否则返回false
		{
			return true;
		}
	}
	return false;
}
//判断运算符优先级
char Precede(char theta1, char theta2)//根据算符间的优先关系表得到相应的输出
{
	if ((theta1 == '(' && theta2 == ')') || (theta1 == '#' && theta2 == '#'))
	{
		return '=';
	}
	else if (theta1 == '(' || theta1 == '#' || theta2 == '(' || (theta1 == '+'  && (theta2 == '*' || theta2 == '/')) || (theta1 == '-' && (theta2 == '*' || theta2 == '/')))
	{
		return '<';
	}
	else
		return '>';
}
//ASCII码中‘#’35‘*’42 ‘+’43 
char Operate(char a, char theta, char b)
{
	switch (theta)//函数返回类型为char
	{
	case '+'://对于数字来说char类型与int类型对应的十进制数相差48 ‘0’的ASCII码对应的十进制数为48
		return (a - '0') + (b - '0') + 48;//将char类型的字符转化为int类型做运算后,加上48转化为字符型的数字
	case '-':                              //例34-32=2 2+48=50 50为2对应的ASCII值
		return (a - '0') - (b - '0') + 48;
	case '*':
		return (a - '0') * (b - '0') + 48;
	case '/':
		return (a - '0') / (b - '0') + 48;
	}
	return 0;//前面的语句没有成功返回时函数返回0
}

<3>运行结果

注意:输入时中文“()”和 英文 '()' 得到的结果会不一样,原因是二者在ASCII码中对应的十进制数不同,正确输入应使用英文输入法的'()'

  • 12
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值