数据结构与算法——第3章-后缀表达式的转换(3.6.2)

一 概述

1
2
3
4
1.调用场算法
2.调用场算法实现
3.调用场算法演示
4.示例代码

二 调用场算法

1
2
幸运的是,针对这个问题,伟人迪杰斯特拉给出了一个完美的解决方案,称为调用场算法,
该算法可以实现将一个普通表达式转换成后缀表达式

三 调用场算法实现

3.1 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
调用场算法的实现,需要借助一个空栈(假设栈名为 Optr)和一个空数组(假设数组名为 postexp)。
对于给定的一个普通表达式,调用场算法的转换过程是:逐个遍历表达式中的每个字符:
1.如果为 '0'~'9' 的字符,将其添加到 postexp 数组的末尾;
2.如果该字符为除 ‘(’ 和 ')' 以外的运算符,将其与 Optr 栈顶的运算符进行大小比较,
如果该运算符大于栈顶运算符,则将其入栈;
反之,如果该运算符小于栈顶运算符,
则将栈顶运算符出栈并添加到 postexp 数组的尾部,然后继续拿当前运算符同新的栈顶运算符做大小比较,以此类推。

3.如果该字符为 '(' 运算符,直接入栈;如果为 ')' 运算符,
依次取 Optr 栈顶运算符并将其添加到 postexp 数组末尾,
直到遇到 '(' 字符为止(注意,'(' 字符也从栈顶取出,但不将其添加 postexp 数组中)。

依照以上处理过程,直到将普通表达式遍历完毕,
如果 Optr 栈中仍有运算符,依次将它们出栈并添加到 postexp数组尾部。
最终,postexp 数组内存储的表达式就是转换后的后缀表达式

3.2 迪杰斯塔表格

值得一提的是,第 2 步中关于运算符的大小比较,迪杰斯塔拉给出了如下所示的表格:

1
2
3
4
如表 1 所示,假设栈顶运算符为 *,当前遍历到的运算符为 +,则根据表 1 中第 3 行第 1 列可知,
* > +(注意不是 + > * ),即当前运算符小于栈顶运算符。
根据调用场算法的处理规则,需将 * 出栈并添加到 postexp 数组的尾部,
继续用 + 运算符同新的栈顶运算符做比较,以此类推。

四 调用场算法演示

以 3!+4*2/(1-5)^2 为例,接下来为大家演示调用场算法的整个转换过程。遍历整个表达式:

  1. 对于字符 3,直接将其添加 postexp 数组的尾部:

  1. 遍历至 !,将其与 Optr 栈顶字符进行比较,由于此时 Optr 为空栈,因此直接将 ! 入栈:

  1. 遍历至 +,Optr 栈顶运算符! > +,将 ! 从 Optr 栈中取出并添加到 postexp 数组末尾。此时,Optr 栈为空,
    将 + 入栈:

  1. 遍历至 4,直接添加到 postexp 数组末尾:

  1. 遍历至 *,Optr 栈顶运算符+ < *,所以将 * 入栈:

  1. 遍历至 2,将其添加至 postexp 数组的末尾:

  1. 遍历至 /,Optr 栈顶运算符* > /,将 * 取出并添加到 postexp 数组末尾:

继续用 / 同 Optr 栈顶的 + 运算符比较,+ < /,将 / 入栈:

  1. 遍历至 (,直接入栈:

  1. 遍历至 1 ,将其添加到 postexp 数组末尾:

  1. 遍历至 -,Optr 栈顶运算符( < -,将 - 入栈:

  1. 遍历至 5,添加到 postexp 数组末尾:

  1. 遍历至 ),对 Optr 栈一直做出栈操作并将出栈元素添加到 postexp 数组末尾,直到将 ( 取出:

  1. 遍历至 ^,Optr 栈顶运算符/ < ^,将 ^ 入栈:

  1. 遍历至 2,将其添加到 postexp 数组末尾:

  1. 将 Optr 栈做出栈操作,并逐个将出栈元素添加到 postexp 数组末尾,直至 Optr 栈为空:

显然,postexp 数组中存储的就是 3!+4*2/(1-5)^2 对应的后缀表达式。

五 示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
// 字符栈
typedef struct {
char data[MAXSIZE];
int top;
} Stack;

void InitStack(Stack **s) {
*s = (Stack*)malloc(sizeof(Stack));
(*s)->top = -1;
}

bool Push(Stack *s, char e) {
if (s->top == MAXSIZE - 1)
return false;
s->top++;
s->data[s->top] = e;
return true;
}

bool Pop(Stack **s, char *e) {
if ((*s)->top == -1)
return false;
*e = (*s)->data[(*s)->top];
(*s)->top--;
return true;
}

bool GetTop(Stack **s, char *e) {
if ((*s)->top == -1)
return false;
*e = (*s)->data[(*s)->top];
return true;
}

bool StackEmpty(Stack **s) {
if ((*s)->top == -1)
return true;
return false;
}

// 将中缀表达式转换成后缀表达式
void trans(char *exp, char postexp[]) {
int i = 0;
char e;
Stack *Optr;
InitStack(&Optr); //初始化操作符栈,为存储后缀表达式做准备

while (*exp != '\0') { // 对每个字符进行判断处理
switch (*exp) {
//单独处理括号
//如果是左括号,直接入栈
case '(':
Push(Optr, '(');
exp++;
break;
//如果为右括号,一直出栈操作,直到将 ( 也出栈
case ')':
Pop(&Optr, &e);
while (e != '(') {
postexp[i++] = e;
Pop(&Optr, &e);
}
exp++;
break;
// + - 优先级相同,当做同一种情况处理
case '+':
case '-':
//由于 + - 的优先级只比 ( 大,所有只要栈顶字符不为 ( 就一直出栈;反之,则将 + - 入栈。
while (!StackEmpty(&Optr)) {
GetTop(&Optr, &e);
if (e == '(')
break;
else {
postexp[i++] = e;
Pop(&Optr, &e);
}
}
//最后将 + - 入栈
Push(Optr, *exp);
exp++;
break;
case '*':
case '/':
// * / 优先级比 * / ^ ! 小,所有如果栈顶运算符是它们,就出栈;反之就将 * / 入栈
while (!StackEmpty(&Optr)) {
GetTop(&Optr, &e);
if (e == '/' || e == '*' || e == '^' || e == '!') // * / 的优先级仅仅低于它前面的 * /,
高于前面的 + -,所以要将前面的 * / 弹出栈;+ - 保留,因为新的 * / 会放在栈低,优先级高。 {
postexp[i++] = e;
Pop(&Optr, &e);
} else
break; // 其他情况( + - 左括号 )退出,
}
//最后将 / * 入栈
Push(Optr, *exp);
exp++;
break;
case '^':
// ^ 优先级仅比 ^ ! 小,如果栈顶运算符是它们,则出栈;反之将 ^ 入栈
while (!StackEmpty(&Optr)) {
GetTop(&Optr, &e);
if (e == '^' || e == '!') {
postexp[i++] = e;
Pop(&Optr, &e);
} else
break; // 其他情况( + - * / ( )退出,
}
Push(Optr, *exp); //最后将 ^ 入栈
exp++;
break;
case '!':
// ! 优先级仅比 ! 小,所有如果栈顶运算符为 !,则将其出栈;反之,将 ! 入栈
while (!StackEmpty(&Optr)) {
GetTop(&Optr, &e);
if (e == '!') {
postexp[i++] = e;
Pop(&Optr, &e);
} else
break; // 其他情况( + - * / ^ ( )退出,
}
//最后将 ! 入栈
Push(Optr, *exp);
exp++;
break;
default:
while (*exp > '0' && *exp < '9') //循环判断是否为数字字符,如果是则保存到postexp,循环判断是
因为可能是多位数字 {
postexp[i++] = *exp;
exp++;
}
//以#分隔各个数字字符
postexp[i++] = '#';
}
}
while (!StackEmpty(&Optr)) { //扫描完exp后,操作符栈可能还有操作符,将其存到postexp
Pop(&Optr, &e);
postexp[i++] = e;
}
postexp[i] = '\0'; //结束字符串
free(Optr); //销毁栈
}

说明:

1
2
3
由此,用栈结构求表达式的值的完整解决方案为:
1.将用户输入的普通表达式,借助调用场算法转换为后缀表达式;
2.由第一步得到的后缀表达式,计算出它的值

七 参考

  • C语言中文网—如何用栈结构求表达式的值