前、中、后缀表达式概念介绍
中缀转前、后缀方法
用栈实现表达式转换与计算原理
转换
计算
c代码实现
九层之台,起于累土,需要先搭建好底层结构,再实现逻辑业务。
(涉及:联合体,结构体,链栈,链表,指针等)
对于栈结构实现如下。该栈能同时入栈两种不同类型的数据,每个栈节点可以是运算数或者运算符
1.1. 头文件LStack.h
本BLOG上原创文章未经本人许可,不得用于商业用途。转载请注明出处,否则保留追究法律责任的权利。
https://blog.csdn.net/Ijerome/article/details/111773298
#ifndef _LSTACK_H_
#define _LSTACK_H_
/************************************************************
Copyright (C), 1988-1999
FileName: LStack.h 2021.01.02
Author:hzp Version :1.0
Description: //链表存用于存放操作符或者操作数
Version: // v1.0
History: // 历史修改记录
<author> <time> <version > <desc>
***********************************************************/
#include "stdio.h"
#include "stdlib.h"
//定义bool类型
#ifndef BOOL
#define BOOL int
#define TRUE 1
#define FALSE 0
#endif
//定义运算符优先级,同时也起到标注作用
#define ADD_PRIORITY (0)//加
#define SUB_PRIORITY (1)//减
#define MUL_PRIORITY (3)//乘
#define DIV_PRIORITY (4)//除
#define L_BRACKET_PRIORITY (6)//左括号
#define R_BRACKET_PRIORITY (7)//右括号
#define OPREATOR (1) //操作符类型
#define NUMBER (2) //操作数类型
typedef int operatorKind; //运算符类型数据
typedef double numberData; //操作数类型数据
typedef char elementKind;//标志元素类型
//联合体类型数据,存放操作符或者操作数
typedef union {
numberData num;
operatorKind kind;
}elementData;
//栈节点
typedef struct lNode *pNode;
typedef struct lNode {
elementData data;
elementKind elementKind;
pNode next;
}lNode;
typedef struct {
pNode top; //栈顶
int count; //元素数量
}linkStack;
//初始化链栈
void initStack(linkStack *lStack);
//入栈
BOOL pushStack(linkStack *lStack , pNode pushNode);
//出栈
void popStack(linkStack *lStack, pNode popNode);
//查看栈顶元素(不出栈)
void readStack(linkStack *lStack, pNode popNode);
//释放栈
void freeStack(linkStack *lStack);
//打印节点数据
void printfStackNode(pNode node);
#endif
1.2. 源文件LStack.c
本BLOG上原创文章未经本人许可,不得用于商业用途。转载请注明出处,否则保留追究法律责任的权利。
https://blog.csdn.net/Ijerome/article/details/111773298
#include"LStack.h"
#include <stdlib.h>
/************************************************************
Copyright (C), 1988-1999
FileName: LStack.c 2021.01.02
Author:hzp Version :1.0
Description: //链表实现栈
Version: // v1.0
History: // 历史修改记录
<author> <time> <version > <desc>
***********************************************************/
//函数定义
//初始化链栈
void initStack(linkStack *lStack)
{
lStack->top = NULL;
lStack->count = 0;
}
/*************************************************
@ Function: pushStack
@ Description:
入栈操作,头插法
@ Input:
lStack //栈指针
elemt //入栈元素指针
size //入栈元素大小
@ Output:
@ Return:
入栈成功/失败
@ Others:
*************************************************/
BOOL pushStack(linkStack *lStack, pNode pushNode)
{
pNode Node = NULL;//栈节点
pNode temp = NULL;//临时节点
if (!pushNode||!lStack)return FALSE;//空指针
Node = (pNode)malloc(sizeof(lNode));
if (!Node) //申请空间失败
{
return FALSE;
}
int size = sizeof(elementData);
memcpy(&Node->data,&pushNode->data,size);//数据拷贝到自己节点空间,如果不做数据类型大小修改,这里可以直接赋值
Node->elementKind = pushNode->elementKind;
Node->next = NULL;
//头插法,插入节点
temp= lStack->top;
lStack->top = Node;
Node->next = temp;
lStack->count += 1;
return TRUE;
}
/*************************************************
@ Function: popStack
@ Description:
出栈操作
@ Input:
lStack //栈指针
@ Output:
popNode //将出栈的元素赋值到该指针上
@ Return:
@ Others:
popNode指针空间需要开辟,以保证达到空间需求
*************************************************/
void popStack(linkStack *lStack, pNode popNode)
{
pNode tempNode = NULL;//栈节点
if (lStack->count <= 0||!lStack||!popNode)return;//栈空
//赋值节点元素数据
memcpy(&popNode->data, &lStack->top->data, sizeof(elementData));
popNode->elementKind = lStack->top->elementKind;
//释放节点空间
tempNode = lStack->top;
lStack->top = lStack->top->next;
free(tempNode);//
tempNode = NULL;
lStack->count -= 1;//计数
return ;
}
/*************************************************
@ Function: readStack
@ Description:
查看栈顶元素(不出栈)
@ Input:
lStack //栈指针
@ Output:
popNode //将栈顶的元素赋值到该指针上
@ Return:
@ Others:
popNode指针空间需要开辟,以保证达到空间需求
*************************************************/
void readStack(linkStack *lStack,pNode popNode)
{
pNode tempNode = NULL;//栈节点
if (lStack->count <= 0 || !lStack || !popNode)return;//栈空
//赋值节点元素数据
memcpy(&popNode->data, &lStack->top->data, sizeof(elementData));
popNode->elementKind = lStack->top->elementKind;
return;
}
//销毁栈
void freeStack(linkStack *lStack)
{
pNode temp = NULL;
temp = lStack->top;
while (temp)//栈内还有元素
{
lStack->top = lStack->top->next;
free(temp);
lStack->count--; //测试用
temp = lStack->top;
}
lStack->count = 0;
}
//打印节点数据
void printfStackNode(pNode node )
{
if (!node)return;
if (node->elementKind == NUMBER)//数字
{
printf("%0.2lf ", node->data.num);
}
else
{
switch (node->data.kind)
{
case ADD_PRIORITY:printf("+ "); break;
case SUB_PRIORITY:printf("- "); break;
case MUL_PRIORITY:printf("* "); break;
case DIV_PRIORITY:printf("/ "); break;
case L_BRACKET_PRIORITY:printf("( "); break;
case R_BRACKET_PRIORITY:printf(") "); break;
default:printf("? ");
}
}
}
入栈顺序:1+2+3+4+5+ 出栈结果如下,测试通过)
2. 前面已经实现了栈的结构,用来存放运算符以及运算数,但是对于用户输入的字符串,并不知道哪些是运算符,那些是运算数,哪些字符是整数以及小数部分,因此,需要一种方法来将用户输入转化为能识别的数据存放,目的是将运算数以及运算符分开。
在此,因为前面的链表节点已经能够存放操作数和操作符,所以利用前面的节点类型生成链表,单纯用来存放用户输入的中缀表达式,这样在计算阶段,就可以根据节点类型方便地转换和计算结果。链表文件如下
2.1 头文件 turnRPN.h
/************************************************************
Copyright (C), 1988-1999
FileName: turnRPN.h 2021.01.02
Author:hzp Version :1.0
Description: //获取用户输入,并且转换、计算后缀表达式的模块
Version: // v1.0
History: // 历史修改记录
<author> <time> <version > <desc>
本BLOG上原创文章未经本人许可,不得用于商业用途。转载请注明出处,否则保留追究法律责任的权利。
https://blog.csdn.net/Ijerome/article/details/111773298
***********************************************************/
#ifndef _TURN_RPN_H_
#define _TURN_RPN_H_
#include "LStack.h"
#include<stdio.h>
//定义扫描标记,用于扫描中缀表达式字符串并且转换成中缀表达式链
#define PRE_NULL (0)//前面没有字符
#define PRE_SYMBOL (1)//前面字符为运算符类型
#define PRE_INT (2)//前面字符为整形
#define PRE_DOUBLE (3) //前面字符为浮点类型
//单链表结构
typedef struct calLink {
lNode* front;//链头
lNode* rear;//链尾
int count;
}calLink;
//函数
//初始化并且获取中缀表达式链表
BOOL initCalLink(calLink* link);
//释放中缀表达式链表
void freeCalLink(calLink* link);
//尾插法添加链表
void addCalLink(calLink* link, pNode addNode);
//根据中缀表达式链转换并计算结果
BOOL calMiddle(calLink* link, numberData *sumData);
//右括号处理
BOOL popBracket(calLink* link, linkStack* lStack);
//栈内的四则运算(括号不参与)
BOOL calFun(linkStack* opStack, linkStack* numStack, operatorKind kind);
//单个操作符计算(括号不参与)
BOOL singleFun(linkStack* numStack, operatorKind kind);
#endif // !_TURN_RPN_H_
2.2 源文件turnRPN.c
/************************************************************
Copyright (C), 1988-1999
FileName: turnRPN.c 2021.01.02
Author:hzp Version :1.0
Description: //获取用户输入,并且转换、计算后缀表达式的模块
Version: // v1.0
History: // 历史修改记录
<author> <time> <version > <desc>
本BLOG上原创文章未经本人许可,不得用于商业用途。转载请注明出处,否则保留追究法律责任的权利。
https://blog.csdn.net/Ijerome/article/details/111773298
***********************************************************/
#include "turnRPN.h"
//函数
//初始化并且获取中缀表达式链表
BOOL initCalLink(calLink* link)
{
char getc=1; //用于接收输入
int preKind = PRE_NULL; //用于标记当前字符前的数据类型,以便当前字符的转换
int doubleLevel = 0;//小数点位数
//初始化链表
lNode node;
node.next = NULL;
link->count = 0;
link->front = NULL;
link->rear = NULL;
//接收输入的表达式并转换链表
while ((getc = getchar()) != '\n')
{
//分析每个字符
switch (getc)
{
case '+':
{
node.elementKind = OPREATOR;//链表节点为运算符
node.data.kind = ADD_PRIORITY;
addCalLink(link, &node);//添加链表
preKind = PRE_SYMBOL;//链表前节点为运算符类型
}break;
case '-':
{
if (preKind == PRE_SYMBOL)//-1=0-1,为后续后缀计算实现负数计算,在纯负号前添加0
{
node.elementKind = NUMBER;//链表节点为运算符
node.data.num = 0;
addCalLink(link, &node);//添加链表
}
node.elementKind = OPREATOR;//链表节点为运算符
node.data.kind = SUB_PRIORITY;
addCalLink(link, &node);//添加链表
preKind = PRE_SYMBOL;//链表前节点为运算符类型
}break;
case '*':
{
node.elementKind = OPREATOR;//链表节点为运算符
node.data.kind = MUL_PRIORITY;
addCalLink(link, &node);//添加链表
preKind = PRE_SYMBOL;//链表前节点为运算符类型
}break;
case '/':
{
node.elementKind = OPREATOR;//链表节点为运算符
node.data.kind = DIV_PRIORITY;
addCalLink(link, &node);//添加链表
preKind = PRE_SYMBOL;//链表前节点为运算符类型
}break;
case '(':
{
node.elementKind = OPREATOR;//链表节点为运算符
node.data.kind =L_BRACKET_PRIORITY;
addCalLink(link, &node);//添加链表
preKind = PRE_SYMBOL;//链表前节点为运算符类型
}break;
case ')':
{
node.elementKind = OPREATOR;//链表节点为运算符
node.data.kind = R_BRACKET_PRIORITY;
addCalLink(link, &node);//添加链表
preKind = PRE_SYMBOL;//链表前节点为运算符类型
}break;
case '.': //小数点情况需要做标记,说明后面字符为小数
{
if (preKind != PRE_INT) //小数点前必须为整数
{
freeCalLink(link);//释放已经建立的链表
return FALSE;
}
else
{
preKind = PRE_DOUBLE;//链表前节点为浮点类型
}
}break;
case ' ':break; //空格不管
default: //数字情况
{
if (getc >= 48 && getc <= 57) //数字
{
numberData numChar = (numberData)getc - 48;//取当前字符对应的数字
if (preKind == PRE_SYMBOL|| preKind == PRE_NULL)//如果当前表达式字符前方是空或运算符,那么当前需要新建一个数字节点
{
node.elementKind = NUMBER;//链表节点为运算符
node.data.num = 0;//数值初始化0
node.data.num += numChar;
addCalLink(link, &node);//添加链表
preKind = PRE_INT;//链表前节点为整形类型
doubleLevel = 0;//目前小数点保留0位
}
else if (preKind == PRE_DOUBLE) //如果当前表达式字符前方是浮点,就取链尾浮点运算
{
doubleLevel++;//小数点保留加1位
for (int i = 0; i < doubleLevel; i++)numChar /= 10;
link->rear->data.num += numChar;
}
else //如果当前表达式前方是整数,就取链尾进行整数运算
{
link->rear->data.num = link->rear->data.num * 10 + numChar;
}
}
else //非法的符号
{
freeCalLink(link);//释放已经建立的链表
return FALSE;
}
}
}
}
return TRUE;
}
//释放中缀表达式链表
void freeCalLink(calLink* link)
{
lNode* temp=NULL;
if (!link)return;
while (link->count > 0)
{
temp = link->front;
link->front = temp->next;
free(temp);
link->count--;
}
link->rear = NULL;
}
//尾插法添加链表
void addCalLink(calLink* link, pNode addNode)
{
if (!link || !addNode)return;
lNode* node = NULL;//链表节点
node = (pNode)malloc(sizeof(lNode));
memcpy(node, addNode, sizeof(lNode)); //复制数据
if (link->count > 0)
{
link->rear->next = node;
link->rear = link->rear->next;
link->rear->next = NULL;
}
else
{
link->front = node;
link->rear = link->front;
link->rear->next = NULL;
}
link->count++;
}
//右括号处理
BOOL popBracket( linkStack* opStack, linkStack* numStack)
{
int isFind = 0;//是否弹出了当前对应的左括号
lNode tempNode;
while (!isFind&&opStack->count>0)
{
readStack(opStack, &tempNode); // 查看栈顶元素是否为左括号
if (tempNode.data.kind == L_BRACKET_PRIORITY)
{
popStack(opStack, &tempNode);
isFind = 1; break; //结束循环
}
else
{
if((calFun(opStack, numStack, tempNode.data.kind)==FALSE))return FALSE; //表达式有误
}
}
if (!isFind)return FALSE; //没找到最近匹配的左括号
return TRUE;
}
//根据当前操作符进行出栈的四则运算(括号不参与)操作符栈没有入栈操作
BOOL calFun(linkStack* opStack, linkStack* numStack, operatorKind kind)
{
lNode opNode; //运算符栈节点
if (opStack->count <= 0)return TRUE;//操作数栈空就返回,上层外部函数入栈
readStack(opStack, &opNode);//查看栈顶元素,不弹出
while ((opNode.data.kind+1) >= kind&& (opNode.data.kind + 1)<L_BRACKET_PRIORITY) //弹出优先级不低于当前的运算符,计算
{
popStack(opStack, &opNode);//弹出栈
if (singleFun(numStack, opNode.data.kind) == FALSE)
{
return FALSE; //当前符号计算失败(没有操作数或者分母0)
}
if (opStack->count <= 0)return TRUE;//操作数栈空就返回,上层外部函数入栈
readStack(opStack, &opNode);//查看栈顶元素,不弹出
}
return TRUE;
}
//单个操作符计算(括号不参与),*a=0,/a=0(单乘或单除都为0)
BOOL singleFun(linkStack* numStack, operatorKind kind)
{
lNode opNode; //运算符栈节点
lNode numRNode; //运算数栈节点,右操作数,用于存放第一个弹出的数
lNode numLNode; //左操作数,用于存放第二个弹出的数
numLNode.data.num = 0;//初始化符号
if (numStack->count <= 0)return FALSE; //没有操作数
else if (numStack->count >=1) //弹出两个操作数
{
popStack(numStack, &numRNode);
if(numStack->count >= 1)popStack(numStack, &numLNode);
}
switch (kind)
{
case ADD_PRIORITY://加
{
numRNode.data.num = numLNode.data.num + numRNode.data.num;
pushStack(numStack, &numRNode);
}break;
case SUB_PRIORITY://减
{
numRNode.data.num = numLNode.data.num - numRNode.data.num;
pushStack(numStack, &numRNode);
}break;
case MUL_PRIORITY://乘
{
numRNode.data.num = numLNode.data.num * numRNode.data.num;
pushStack(numStack, &numRNode);
}break;
case DIV_PRIORITY://除
{
if (numRNode.data.num == 0)return FALSE;
numRNode.data.num = numLNode.data.num / numRNode.data.num;
pushStack(numStack, &numRNode);
}break;
default:return FALSE;//存在不能运算的操作符
}
return TRUE;
}
//根据中缀表达式链转换并计算结果
BOOL calMiddle(calLink* link ,numberData *sumData)
{
//准备两个栈,一个操作数栈,一个运算符栈,中缀表达式存放在链表里
linkStack opStack; //运算符栈
linkStack numStack; //运算数栈
lNode tempNode; //临时栈节点
pNode pLinkNode; //链表节点
int count = 0;//链表节点数
if (!link || link->count <= 0)return FALSE;
count = link->count;
pLinkNode = link->front; //获取中缀链表头节点
initStack(&opStack);//初始化栈
initStack(&numStack);
//根据中缀表达式链表转换和计算
while (count-- && pLinkNode)//根据count数量从左到右遍历链表
{
if (pLinkNode->elementKind == OPREATOR)//运算符
{
if (pLinkNode->data.kind == R_BRACKET_PRIORITY) //处理右括号
{
if (popBracket(&opStack, &numStack) == FALSE)
{
freeStack(&opStack); freeStack(&numStack);
return FALSE;//表达式有误
}
}
else if (pLinkNode->data.kind < L_BRACKET_PRIORITY)// 处理栈内非括号运算符
{
if (calFun(&opStack, &numStack, pLinkNode->data.kind) == FALSE)
{
freeStack(&opStack); freeStack(&numStack);
return FALSE;//表达式有误
}
pushStack(&opStack, pLinkNode); //当前运算符入栈
}
else pushStack(&opStack, pLinkNode);//左括号直接入栈
}
else if (pLinkNode->elementKind == NUMBER)//运算数
{
pushStack(&numStack, pLinkNode); //入操作数栈
}
pLinkNode = pLinkNode->next;
}
while (opStack.count > 0)//遍历链表结束后,处理剩下的栈内操作符
{
popStack(&opStack, &tempNode);//出栈
if (singleFun(&numStack, tempNode.data.kind) == FALSE)
{
freeStack(&opStack); freeStack(&numStack);
return FALSE;//表达式有误
}
}
if (numStack.count != 1)//最终运算符栈内自身一个元素才是正确的
{
freeStack(&opStack); freeStack(&numStack);
return FALSE;//栈内结果有误
}
else
{
popStack(&numStack, &tempNode);
*sumData = tempNode.data.num; // 返回结果
freeStack(&opStack); freeStack(&numStack);
}
return TRUE;
}
- 最后,只需要根据最初的出栈、入栈规则,便可以转换中缀表达式链并且计算出相应的四则运算结果。
#include <stdio.h>
#include <stdlib.h>
//#include"test.h"
#include "LStack.h" //栈
#include"turnRPN.h" //链表
int main()
{
while (1)
{
numberData sumData = 0;
calLink link;
printf("输入计算的表达式:");
if (initCalLink(&link) == FALSE)printf("输入有误!\n");
else if (calMiddle(&link, &sumData) == FALSE)printf("表达式有误!\n");
else printf("\n结果:%0.3f\n", sumData);
freeCalLink(&link);
}
return 0;
}
实现的功能如下:
- 四则运算支持
- 浮点有符号运算
- 嵌套括号以及空格的忽略
- 可以方便地添加其他运算符如%、^ 等
测试结果