- 入力中置式の正当性を判断するコードを追加しました。
「データ構造」: 中置式の正当性の判断 - Amentos ブログ - CSDN ブログ
目次
例 中置式 2*(3+5)+7/1-4 は後置式に変換されます
例 後置式の計算 2 3 5 + * 7 1 / + 4 -
1. 基本的な考え方
1. 中置式:
演算子は、中置記号 (3+2 など) の形式でオペランドの中央に配置されます。これは、日常生活における算術論理式の一般的な表現方法です。
2. 接尾語表現:
逆ポーランド記法( RPN ) としても知られるこの演算子は、接尾辞形式で 2 つのオペランドの後に配置されます (たとえば、3+2 の接尾辞式は 3 2 + です)。
3. プレフィックス式:
ポーランド語(ポーランド語表記) とも呼ばれるこの演算子は、接頭辞の形式で 2 つのオペランドの前に配置されます (たとえば、3+2 の接頭辞式は + 3 2 です)。
中置式では、演算の順序を示すために演算子と対応するオペランドを括弧で囲む必要があることがよくあります。
例: 5*(2+1) *の優先順位は+の優先順位よりも高くなりますが、括弧の存在は、括弧内の+演算が最初に実行される必要があることを意味します。
中置式は人間の思考構造やコンピューティングの習慣には適していますが、コンピュータには適していません。
特に括弧を含む中置式は、コンピュータにとって非常に複雑な構造です。
コンピュータ用の後置式
中置式とは異なり、後置式では演算子の優先順位を示すための括弧は必要ありません。
後置式の計算は、(演算子の優先順位に関係なく)演算子が 現れる順序で左から右に順番に実行されます。これは、コンピュータにとっては比較的単純な構造です。
2. 中置式から後置式へ
中置式の各文字を左から右にトラバースします (演算子と括弧を格納するための文字スタックを準備する必要があります)
1. 文字はオペランドです。
後置式に直接送信します (注:最初に完全なオペランドを分析する必要があります)。
2. 文字は左括弧です:
それをスタックに直接プッシュします (注:左括弧がスタックにプッシュされた後、優先度は最低に下がります)。
3. 文字は閉じ括弧です。
スタックを直接ポップし、スタックの先頭の文字が左括弧になるまで、ポップされた文字を 1 つずつ接尾辞式に送信します (左括弧もスタックからポップされますが、接尾辞式には送信されません)。
概要:スタックの先頭が左括弧である限り、スタックを最後にポップすることができます。
4. キャラクターは演算子です:
スタックが空の場合は、スタックに直接プッシュされます。
スタックが空でない場合は、スタックの先頭の演算子を判定し、スタックの先頭の演算子の優先順位がその演算子の優先順位よりも低い場合は、その演算子をスタックにプッシュし、そうでない場合はスタックをプッシュします。常にポップされ、スタックが空になるまで、ポップされた文字が順に接尾辞式に送信されます。または、スタックの先頭にある演算子の優先順位がこの演算子よりも低く、この演算子がスタックにプッシュされます。
概要:スタックが空であるか、優先順位がスタックの最上位の演算子よりも高い限り、ポップを停止して演算子をスタックにプッシュできます。
5. 上記の手順を中置式の走査が完了するまで繰り返し、文字スタックが空かどうかを判断し、空でない場合は直接ポップし、ポップした文字を順に接尾辞式に送ります。
注:中置式の走査が完了した後、スタック内に出力されていない文字が残っている可能性があるため、スタックが空であると判断する必要があります。
後置式に変換された 中置式2*(3+5)+7/1-4の例
中置式の文字を左から右に順番にたどります。
最初の文字はオペランドで、直接出力されます。
2 番目の文字は演算子で、スタックが空である/ 優先順位がスタックの一番上の演算子より高い という条件を満たし、演算子はスタックにプッシュされます。
3 番目の文字は左括弧で、スタックに直接プッシュされます (スタックがプッシュされた後、優先順位は最低に下がります)。
4 番目の文字はオペランドで、直接出力されます。
5 番目の文字は演算子で、スタックが空である/スタックの先頭にある演算子よりも優先順位が高い という条件を満たし、演算子はスタックにプッシュされます。
6 番目の文字はオペランドで、直接出力されます。
7 番目の文字は右括弧であり、これはスタックから直接ポップされて、スタックの先頭が最後のポップの左括弧になるまで出力されます (出力されません)。
8 番目の文字は演算子で、スタックが空である/スタックの先頭の演算子よりも優先度が高いという 条件を満たさず、条件が満たされるまでスタックをポップします。
9 番目の文字はオペランドで、次のように直接出力されます。
10 番目の文字は演算子で、スタックが空である/スタックの先頭にある演算子よりも優先順位が高い という条件を満たし、演算子がスタックにプッシュされます。
11 番目の文字はオペランドで、次のように直接出力されます。
12 番目の文字は演算子で、スタックが空である/スタックの先頭の演算子より優先度が高いという 条件を満たさず、条件が満たされるまでスタックをポップします。
13 番目の文字はオペランドで、次のように直接出力されます。
中置式の走査が完了した後、文字スタックに演算子があるかどうかが判断され、ある場合はポップされて出力されます。
変換が完了しました:
3. 接尾辞表現の計算
接尾辞式の各文字を左から右にトラバースします (オペランドと演算結果を保存するオペランド スタックを準備する必要があります)
1. 文字はオペランドです。
スタックに直接プッシュします (注:完全なオペランドを分析し、対応するデータ型に変換する必要があります)
2. キャラクターは演算子です:
スタックを 2 回続けてポップし、ポップされた 2 つのデータを使用して対応する計算を実行し、計算結果をスタックにプッシュします。
例: スタックから飛び出した最初のオペランドはa、スタックから飛び出した 2 番目のオペランドはb、このときの演算子は-で、 ba を計算します(注: a と b の順序は逆にすることはできません)。そして結果をスタックに入れます。
3. 後置式が検索され、スタック内のデータが中置式の計算結果になるまで、上記の手順を繰り返します。
例の 接尾辞式の計算2 3 5 + * 7 1 / + 4 -
接尾辞式の各文字を左から右にたどります。
最初の文字はオペランドで、スタックに直接プッシュされます。
2 番目の文字はオペランドで、スタックに直接プッシュされます。
3 番目の文字はオペランドで、スタックに直接プッシュされます。
4 番目の文字は演算子で、スタックから直接 2 回ポップアウトされます。
ポップを続けます:
実行: 2 番目のポップ オペランド演算子が 初めてオペランド をポップします 。
つまり、3 + 5
結果: 8
計算結果をスタックにプッシュします。
5 番目の文字は演算子で、スタックから直接 2 回ポップアウトされます。
実行: 2 番目のポップ オペランド演算子が 初めてオペランド をポップします 。
つまり、2 * 8
結果: 16
計算結果をスタックにプッシュします。
6 番目の文字はオペランドで、スタックに直接プッシュされます。
7 番目の文字はオペランドで、スタックに直接プッシュされます。
8 番目の文字は演算子で、スタックから直接 2 回ポップアウトされます。
実行: 2 番目のポップ オペランド演算子が 初めてオペランド をポップします 。
つまり、7 / 1
結果: 7
計算結果をスタックにプッシュします。
9 番目の文字は演算子で、スタックから直接 2 回ポップアウトされます。
実行: 2 番目のポップ オペランド演算子が 初めてオペランド をポップします 。
つまり、16 + 7
結果: 23
計算結果をスタックにプッシュします。
10 番目の文字はオペランドで、スタックに直接プッシュされます。
11 番目の文字は演算子で、スタックから直接 2 回ポップアウトされます。
実行: 2 番目のポップ オペランド演算子が 初めてオペランド をポップします 。
つまり: 23 - 4
結果: 19
計算結果をスタックにプッシュします。
接尾辞式の走査が完了し、スタック内のデータが最終的な計算結果になります。
4. アルゴリズムの実装
コード:
#include<stdio.h>
#include<stdlib.h>
#include<malloc.h>
#include<string.h>
#include<ctype.h>
#define ERROR 0
#define OK 1
#define STACK_INT_SIZE 10 /*存储空间初始分配量*/
#define STACKINCREMENT 5 /*存储空间分配增量*/
#define M 50
typedef char ElemType; /*定义字符数据类型*/
typedef double ElemType2; /*定义运算数数据类型*/
/*字符栈*/
typedef struct{
ElemType *base;
ElemType *top;
int stacksize;
}SqStack;
/*运算数栈*/
typedef struct{
ElemType2 *base;
ElemType2 *top;
int stacksize;
}NStack;
int InitStack(SqStack *S); /*构造空栈*/
int push(SqStack *S,ElemType e); /*入栈*/
int Pop(SqStack *S,ElemType *e); /*出栈*/
int StackEmpty(SqStack *s); /*栈空判断*/
void in2post(ElemType *str,ElemType *p); /*中缀表达式转后缀表达式*/
double cal_post(char *str); /*计算后缀表达式*/
/*字符栈初始化*/
int InitStack(SqStack *S){
S->base=(ElemType *)malloc(STACK_INT_SIZE * sizeof(ElemType));
if(!S->base)
return ERROR; //分配失败
S->top = S->base;
S->stacksize = STACK_INT_SIZE;
return OK;
}/*InitStack*/
/*运算数栈初始化*/
int InitStack_N(NStack *S){
S->base=(ElemType2 *)malloc(STACK_INT_SIZE * sizeof(ElemType2));
if(!S->base)
return ERROR;
S->top = S->base;
S->stacksize = STACK_INT_SIZE;
return OK;
}
/*字符栈入栈*/
int Push(SqStack *S,ElemType e){
//判断栈满
if(S->top - S->base >= S->stacksize){
S->base = (ElemType *)realloc(S->base,(S->stacksize + STACKINCREMENT)*sizeof(ElemType));
if(NULL == S->base) //分配失败
return ERROR;
S->top = S->base + S->stacksize;
S->stacksize = S->stacksize+STACKINCREMENT;
}
*S->top = e;
S->top++;
return OK;
}
/*运算数栈入栈*/
int Push_N(NStack *S,ElemType2 e){
if(S->top - S->base >= S->stacksize){
S->base = (ElemType2 *)realloc(S->base,(S->stacksize + STACKINCREMENT)*sizeof(ElemType2));
if(NULL == S->base)
return ERROR;
S->top = S->base + S->stacksize;
S->stacksize = S->stacksize+STACKINCREMENT;
}
*S->top = e;
S->top++;
return OK;
}
/*字符栈出栈*/
int Pop(SqStack *S,ElemType *e){
//判断栈空
if(S->top == S->base)
return ERROR;
S->top--;
*e=*S->top;
return OK;
}/*Pop*/
/*运算数栈出栈*/
int Pop_N(NStack *S,ElemType2 *e){
if(S->top == S->base)
return ERROR;
S->top--;
*e=*S->top;
return OK;
}
/*判断栈空*/
int StackEmpty(SqStack *s){
if(s->top == s->base)
return OK;
return ERROR;
}/*StackEmpty*/
//str为待转换的中缀表达式字符串,p为转换后的后缀表达式字符串
void in2post(ElemType *str,ElemType *p){ /*infix to postfix*/
SqStack s;
InitStack(&s); //初始化一个空字符栈
ElemType e;
int i;
int j=0;
for(i=0 ; i<strlen(str) ; i++) //遍历中缀表达式
{
//遇到数字和小数点直接输出
//使用循环完整接收一个运算数
while(isdigit(str[i]) || '.'==str[i])
{
p[j++]=str[i++];
if(!isdigit(str[i]) && '.'!=str[i])
p[j++]=' '; //一个数字完整输出后使用空格与其它运算符或数字分隔开
}
//遇到左括号直接入栈
if('('==str[i])
Push(&s,str[i]);
//遇到右括号直接出栈,直到栈顶为左括号
if(')'==str[i])
{
while('(' != *(s.top-1))
{
Pop(&s,&e);
p[j++]=e;
p[j++]=' ';
}
Pop(&s,&e); //左括号出栈但不输出
}
//遇到+或—
//1.栈空/栈顶为左括号:直接入栈
//2.否则一直出栈,直到栈空/栈顶为左括号,再入栈
if('+'==str[i] || '-'==str[i])
{
while(!StackEmpty(&s) && '('!=*(s.top-1))
{
Pop(&s,&e);
p[j++]=e;
p[j++]=' ';
}
Push(&s,str[i]);
}
//遇到*或/
//1.栈空/栈顶为左括号/栈顶操作符为+ or -:直接入栈
//2.否则一直出栈,直到满足1,再入栈
if('*'==str[i] || '/'==str[i] || '%'==str[i])
{
while(!StackEmpty(&s) && '('!=*(s.top-1) && '+'!=*(s.top-1) && '-'!=*(s.top-1))
{
Pop(&s,&e);
p[j++]=e;
p[j++]=' ';
}
Push(&s,str[i]);
}
}
//中缀表达式遍历完成,还需检查栈中是否有未输出字符
//判断栈空,非空则直接出栈并输出(左括号不用输出)
while(!StackEmpty(&s)){
Pop(&s,&e);
if('('!=e)
{
p[j++]=e;
p[j++]=' ';
}
}
p[--j]='\0';
}/*infix2postfix*/
//str为待计算的后缀表达式,返回值为计算结果
double cal_post(char *str){ /*计算后缀表达式*/
int i;
ElemType2 e,a,b;
char d[M];
NStack n;
InitStack_N(&n); //初始化一个运算数栈保存运算数
for(i=0;i<strlen(str);i++)
{
int j=0;
while(isdigit(str[i]) || '.'==str[i])
{
d[j++]=str[i++];
d[j]='\0';
if(!isdigit(str[i]) && '.'!=str[i])
{
e=atof(d); //使用atof()将字符串形式的运算数转换为double型数据
Push_N(&n,e); //运算数入栈
}
}
switch(str[i])
{
case '+':
Pop_N(&n,&b);
Pop_N(&n,&a);
Push_N(&n,a+b);
break;
case '-':
Pop_N(&n,&b);
Pop_N(&n,&a);
Push_N(&n,a-b);
break;
case '*':
Pop_N(&n,&b);
Pop_N(&n,&a);
Push_N(&n,a*b);
break;
case '/':
Pop_N(&n,&b);
Pop_N(&n,&a);
Push_N(&n,a/b);
break;
}
}
Pop_N(&n,&e);
return e;
}/*calculate_postfix*/
int main()
{
char str[M];
char post[M];
int i;
printf("\n输入一串中缀表达式:\n");
gets(str);
printf("\n对应的后缀表达式:\n");
in2post(str,post);
printf("%s",post);
printf("\n\n计算后缀表达式:\n");
printf("%f",cal_post(post));
return 0;
}
操作結果:
5. アルゴリズムの改善
上記のコードが正しく動作するための前提条件は次のとおりです。
① 入力された中置式は正当です
②オペランドが非負である
たとえば 、3*( -6 +5) と入力して次の結果が得られますが、接尾辞の式と演算結果は明らかに間違っています。
ここでの-6 は完全なオペランドである必要がありますが、 「-」記号と数値「6」が接尾辞式で分離されており、取得される結果は正しくありません。コード内の「-」記号は常に演算子として扱われるため、-6 のような負の数に直面した場合、完全で正しいオペランドを分析できません。
- これを変更する場合は、オペランド分析を実行するときに負の数の場合を考慮する必要があります。記号「-」が出現する場合、それがマイナス記号として使用されているのか、オペランドの一部として使用されているのか、演算子として使用されているのかを判断する必要があります。
そのため、オペランド解析コードに負数処理を追加する必要があり、修正コードは以下の通りです。
In2post関数コードの変更:
void in2post(ElemType *str,ElemType *p){ /*infix to postfix*/
//初始化一个空栈
SqStack s;
InitStack(&s);
ElemType e;
int i;
int j=0;
for(i=0 ; i<strlen(str) ; i++) //遍历中缀表达式
{
if('-' == str[i]) //负数情况判断
{
//表达式首位是'-',则一定是作为负数符号
if(0 == i)
p[j++]=str[i++];
//'-'前面是'(',则一定是作为负数符号
else if('(' == str[i-1])
p[j++]=str[i++];
}
//遇到数字和小数点直接输出
while(isdigit(str[i]) || '.'==str[i])
{
p[j++]=str[i++];
if(!isdigit(str[i]) && '.'!=str[i])
p[j++]=' '; //一个数字完整输出后使用空格与其它运算符或数字分隔开
}
//遇到左括号直接入栈
if('('==str[i])
Push(&s,str[i]);
//遇到右括号直接出栈,直到左括号出栈(左括号不输出)
if(')'==str[i])
{
while('(' != *(s.top-1))
{
Pop(&s,&e);
p[j++]=e;
p[j++]=' ';
}
Pop(&s,&e); //左括号出栈但不输出
}
//遇到+或—
//1.栈空/栈顶为左括号:直接入栈
//2.否则一直出栈,直到栈空/栈顶为左括号,再入栈
if('+'==str[i] || '-'==str[i])
{
while(!StackEmpty(&s) && '('!=*(s.top-1)) //栈非空 且 栈顶非左括号
{
Pop(&s,&e);
p[j++]=e;
p[j++]=' ';
}
Push(&s,str[i]);
}
//遇到*或/
//1.栈空/栈顶为左括号/栈顶操作符为+ or -:直接入栈
//2.否则一直出栈,直到满足1,再入栈
if('*'==str[i] || '/'==str[i] || '%'==str[i])
{
while(!StackEmpty(&s) && '('!=*(s.top-1) && '+'!=*(s.top-1) && '-'!=*(s.top-1))
{
Pop(&s,&e);
p[j++]=e;
p[j++]=' ';
}
Push(&s,str[i]);
}
}
//中缀表达式遍历完成,还需检查栈中是否有未输出字符
//判断栈空,非空则直接出栈并输出(左括号不用输出)
while(!StackEmpty(&s)){
Pop(&s,&e);
if('('!=e)
{
p[j++]=e;
p[j++]=' ';
}
}
p[--j]='\0';
}
Cal_post関数コードの変更:
double cal_post(char *str){
ElemType2 e,a,b;
char d[M];
//初始化一个运算数栈保存运算数
NStack n;
InitStack_N(&n);
int i=0;
int j=0;
while(str[i]) //遍历后缀表达式
{
switch(str[i])
{
case '-':
if( isdigit(str[i+1]) ) //判断'-'是作为负数符号or运算符
{
d[j++]=str[i++]; //将负号加入运算数字符串
d[j]='\0';
break; //注:这里的break只是跳出switch循环
}
else
{
Pop_N(&n,&b);
Pop_N(&n,&a);
Push_N(&n,a-b);
i++;
break;
}
case '+':
Pop_N(&n,&b);
Pop_N(&n,&a);
Push_N(&n,a+b);
i++;
break;
case '*':
Pop_N(&n,&b);
Pop_N(&n,&a);
Push_N(&n,a*b);
i++;
break;
case '/':
Pop_N(&n,&b);
Pop_N(&n,&a);
Push_N(&n,a/b);
i++;
break;
case ' ':i++;
}
//遇到运算数直接入栈(先转换double类型)
//d保存后缀表达式中的字符串形式的运算数
//使用atof将字符串转换为double类型
while(isdigit(str[i]) || '.'==str[i])
{
d[j++]=str[i++];
d[j]='\0';
if( ' ' == str[i] )
{
e = atof(d); //此时分析出的就是完整的运算数
Push_N(&n,e);
i++;
j = 0;
}
}
}
Pop_N(&n,&e);
return e;
}
- 操作結果: