
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.