5.4. Представление арифметического выражения в виде бд.
Одной из главных причин, лежащих в основе появления языков программирования высокого уровня, явились вычислительные задачи, требующие больших объёмов рутинных вычислений. Поэтому к языкам программирования предъявлялись требования максимального приближения формы записи вычислений к естественному языку математики. В этой связи одной из первых областей системного программирования сформировалось исследование способов трансляции выражений. Здесь получены многочисленные результаты, однако наибольшее распространение получил метод трансляции с помощью обратной польской записи, которую предложил польский математик Я.Лукашевич.
Пример.
Пусть задано простое арифметическое
выражение вида:
(A+B)*(C+D) - E (1)
Представим это выражение в виде дерева, в котором узлам соответствуют операции, а ветвям - операнды. Построение начнем с корня, в качестве которого выбирается операция, выполняющаяся последней. Левой ветви соответствует левый операнд операции, а правой ветви - правый. Дерево выражения (1) показано на рис. 5.2. Совершим обход дерева, которым будем понимать формирование строки символов из символов узлов и ветвей дерева. Обход будем совершать от самой левой ветви вправо и узел переписывать в выходную строку только после рассмотрения всех его ветвей (обход LRT). Результат обхода дерева имеет вид:
AB + CD +*E - (2)
Характерные особенности выражения (2) состоят в следовании символов операций за символами операндов и в отсутствии скобок. Такая запись называется обратной польской или постфиксной записью (ОПЗ). Обратная польская запись обладает рядом замечательных свойств, которые превращают ее в идеальный промежуточный язык при трансляции.
Во-первых, вычисление выражения, записанного в обратной польской записи, может проводиться путем однократного просмотра, что является весьма удобным при генерации объектного кода программ. Например, вычисление выражения
7 5 2 - 4 * + { строка ОПЗ для выражения 7+(5-2)*4= } (3)
может быть проведено следующим образом.
Алгоритм вычисления выражения, записанного в ОПЗ. Для реализации этого алгоритма используется стек для чисел (или для переменных, если они встречаются в исходном выражении). Алгоритм очень прост. В качестве входной строки мы теперь рассматриваем выражение, записанное в ОПЗ:
-
Если очередной символ входной строки - число, то кладем его в стек.
-
Если очередной символ - знак операции, то извлекаем из стека два верхних числа, используем их в качестве операндов для этой операции, затем кладем результат обратно в стек.
Когда вся входная строка будет разобрана в стеке должно остаться одно число, которое и будет результатом данного выражения. Рассмотрим этот алгоритм на примере выражения (3):
Рассмотрим поочередно все символы:
|
Символ |
7+(5-2)*4= → 7 5 2 - 4 * + Действие |
Состояние стека после совершенного действия |
|
7 |
'7' - число. Помещаем его в стек. |
7 |
|
5 |
'5' - число. Помещаем его в стек. |
7 5 |
|
2 |
'2' - число. Помещаем его в стек. |
7 5 2 |
|
- |
'-' - знак операции. Извлекаем из стека 2 верхних числа ( 5 и 2 ) и совершаем операцию 5 - 2 = 3, результат которой помещаем в стек |
7 3 |
|
4 |
'4' - число. Помещаем его в стек. |
7 3 4 |
|
* |
'*' - знак операции. Извлекаем из стека 2 верхних числа ( 3 и 4 ) и совершаем операцию 3 * 4 = 12, результат которой помещаем в стек |
7 12 |
|
+ |
'+' - знак операции. Извлекаем из стека 2 верхних числа ( 7 и 12 ) и совершаем операцию 7 + 12 = 19, результат которой помещаем в стек |
19 |
Теперь строка разобрана и в стеке находится одно число 19, которое является результатом исходного выражения.
Пример программы вычисления арифметического выражения в ОПЗ:
Program OPZ;
Var z :String; {строка ОПЗ}
S: Array[1..100]Of Real; {стек}
h, i :Integer;
Procedure Op (Var h :Integer; ch :Char);
Begin
Case ch Of
'+' : S[h-1]:=S[h-1]+S[h];
'-' : S[h-1]:=S[h-1]-S[h];
'*' : S[h-1]:=S[h-1]*S[h];
'/' : S[h-1]:=S[h-1]/S[h];
End;
Dec(h);
End;
{ Головная программа }
Begin
z:='752-4*+'; { строка ОПЗ для выражения 7+(5-2)*4= }
Write(z);
h:=0;
For i:=1 To Length(z) Do
If z[i] In ['0'..'9']
Then Begin Inc(h); S[h]:=Ord(z[i])-Ord('0'); End
Else Op(h,z[i]);
Write(S[1]);
End.
Во-вторых, получение обратной польской записи из исходного выражения (инфиксной формы записи) может осуществляться весьма просто на основе простого алгоритма, предложенного Дейкстрой. Для этого вводится понятие стекового приоритета операций (табл.5.1).
|
Таблица 5.1. Приоритеты операций |
|
|
Операция |
Приоритет |
|
( |
0 |
|
) |
1 |
|
+ - |
2 |
|
* / |
3 |
|
** |
4 |
-
если стек пуст, то операция из входной строки переписывается в стек;
-
операция выталкивает из стека все операции с большим или равным приоритетом в выходную строку;
-
если очередной символ из исходной строки есть открывающая скобка, то он проталкивается в стек;
-
закрывающая круглая скобка выталкивает все операции из стека до ближайшей открывающей скобки, сами скобки в выходную строку не переписываются, а уничтожают друг друга.
-
Процесс получения обратной польской записи выражения (1) схематично представлен ниже:
|
Просматриваемый символ |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
|
Входная строка |
( |
A |
+ |
B |
) |
* |
( |
C |
+ |
D |
) |
- |
E |
|
|
Состояние стека |
( |
( |
+ ( |
+ ( |
|
* |
( * |
( * |
+ ( * |
+ ( * |
* |
- |
- |
|
|
Выходная строка |
|
A |
|
B |
+ |
|
|
C |
|
D |
+ |
* |
E |
- |
Программа преобразования выражения из инфиксной в ОПЗ:
Рrogram InfixToРostfix;
Uses Stack; {модуль стека}
Var h :u; {указатель стека}
s, d :String; {s – входная строка, d – выходная строка }
i :Integer; ch :Char;
Function Р (ch :Char) :Integer; { функция приоритета }
Begin
Case ch Of
'*', '/': Р:=3;
'+', '-': Р:=2;
'(', '#': Р:=1;
End; End;
Begin
s:='(a+b)*(c+d)-e';
d:=''; Рush(h,'#'); {# - барьер, чтобы упростить проверку "пустой" стек}
For i:=1 to Length(s) Do
Case s[i] Of
')' :Begin
While Toр(h) <> '(' Do d := d + Рoр(h);
ch:=Рoр(h);
End;
'a'..'z' :d:=d + s[i];
'(' :Рush(h, s[i] );
'*', '/', '+', '-' :Begin
While Р ( s[i] ) <= Р (Toр(h) ) Do d := d + Рoр(h);
Рush (h, s[i]);
End;
End;
While Toр(h) <> '#' Do d := d + Рoр(h);
Writeln(d);
End.
