Вычисление выражения в обратной польской записи
Условие
Напишите функцию для вычисления значения арифметического выражения в обратной польской записи (Reverse Polish Notation, RPN).
Входные данные — вектор строк, где каждый элемент является либо целым числом, либо оператором (+, -, *, /).
Требования
- Поддерживаемые операторы: +, -, *, /
- Деление — целочисленное (результат округляется к нулю)
- Гарантируется, что выражение корректно и не приводит к делению на ноль
- Операнды и промежуточные результаты помещаются в int32_t
Пример
std::vector<std::string> tokens = {"2", "1", "+", "3", "*"};
int result = evalRPN(tokens); // (2 + 1) * 3 = 9
tokens = {"4", "13", "5", "/", "+"};
result = evalRPN(tokens); // 4 + (13 / 5) = 4 + 2 = 6
tokens = {"10", "6", "9", "3", "+", "-11", "*", "/", "*", "17", "+", "5", "+"};
result = evalRPN(tokens); // 22
Подсказка
Используйте стек для хранения операндов.
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Решение: Вычисление Reverse Polish Notation (RPN)
Принцип работы
Обратная польская нотация (RPN) — это постфиксная запись, где операторы идут ПО СЛЕ операндов. Её главное преимущество — вычисление слева направо без скобок и приоритетов.
Алгоритм:
- Проходим по массиву слева направо
- Если элемент — число, кладём его в стек
- Если элемент — оператор, извлекаем два верхних элемента, выполняем операцию, кладём результат обратно
- В конце стека остаётся результат
Реализация
#include <vector>
#include <string>
#include <stack>
#include <cctype>
#include <stdexcept>
int evalRPN(std::vector<std::string>& tokens) {
std::stack<long long> st; // long long для промежуточных результатов
for (const auto& token : tokens) {
if (token == "+" || token == "-" || token == "*" || token == "/") {
// Оператор — извлекаем два операнда
if (st.size() < 2) {
throw std::invalid_argument("Invalid RPN expression");
}
long long b = st.top(); st.pop(); // второй операнд
long long a = st.top(); st.pop(); // первый операнд
long long result = 0;
if (token == "+") {
result = a + b;
} else if (token == "-") {
result = a - b;
} else if (token == "*") {
result = a * b;
} else if (token == "/") {
// Целочисленное деление, округление к нулю
if (b == 0) {
throw std::invalid_argument("Division by zero");
}
result = a / b;
}
st.push(result);
} else {
// Число — парсим и кладём в стек
long long num = std::stoll(token);
st.push(num);
}
}
if (st.size() != 1) {
throw std::invalid_argument("Invalid RPN expression");
}
return (int)st.top();
}
Трассировка примера 1
["2", "1", "+", "3", "*"]
| Шаг | Токен | Стек до | Стек после | Описание |
|---|---|---|---|---|
| 1 | "2" | [] | [2] | Число → в стек |
| 2 | "1" | [2] | [2, 1] | Число → в стек |
| 3 | "+" | [2, 1] | [3] | Pop 1, 2 → 2+1=3 |
| 4 | "3" | [3] | [3, 3] | Число → в стек |
| 5 | "*" | [3, 3] | [9] | Pop 3, 3 → 3*3=9 |
| Итог | - | [9] | [9] | Результат: 9 |
Трассировка примера 2
["4", "13", "5", "/", "+"]
| Шаг | Токен | Действие | Стек |
|---|---|---|---|
| 1 | "4" | Push 4 | [4] |
| 2 | "13" | Push 13 | [4, 13] |
| 3 | "5" | Push 5 | [4, 13, 5] |
| 4 | "/" | Pop 5,13 → 13/5=2 | [4, 2] |
| 5 | "+" | Pop 2,4 → 4+2=6 | [6] |
| Итог | - | - | Результат: 6 |
Важные детали
1. Порядок операндов при вычитании и делении:
Стек: [10, 3]
Оператор: "-"
b = 3 (верхний элемент)
a = 10 (нижний элемент)
Результат: a - b = 10 - 3 = 7
2. Целочисленное деление:
В C++ оператор / с целыми числами округляет к нулю:
13 / 5 = 2 // не 2.6
-13 / 5 = -2 // не -2.6
3. Использование long long: Применяем long long для стека, чтобы избежать переполнения при промежуточных вычислениях:
int a = INT_MAX;
int b = INT_MAX;
int result = a * b; // ❌ переполнение!
long long a_ll = a;
long long b_ll = b;
long long result_ll = a_ll * b_ll; // ✅ корректно
Анализ сложности
- Временная сложность: O(N) — один проход по массиву
- Пространственная сложность: O(N) — в худшем случае стек содержит N/2 элементов (все числа)
Альтернативные подходы
- Рекурсия — более сложна в реализации, требует явного стека
- Парсинг с regexp — медленнее, усложняет код
- Инфиксная к RPN конвертация (алгоритм Шунтинг-ярд) — нужна, если входные данные в инфиксной нотации
Тестирование граничных случаев
// Отрицательные числа
std::vector<std::string> tokens = {"-2", "3", "*"};
assert(evalRPN(tokens) == -6);
// Большие числа
tokens = {"1000000", "1000000", "*"};
assert(evalRPN(tokens) == 1000000000000LL); // переполнение int!
// Деление с отрицательными
tokens = {"10", "-3", "/"};
assert(evalRPN(tokens) == -3); // 10 / (-3) = -3 (к нулю)