Алгоритм Дейкстры (Сортировочная станция)

Модуль "Выражения" • урок 5 из 7 • ~12 минут

Суть урока. Знаменитый алгоритм Эдсгера Дейкстры изящно и просто переводит обычное (инфиксное) выражение в обратную польскую запись (ОПЗ) всего за один проход, используя один стек. Числа едут сразу на выход, а операторы заходят в тупик (стек), выталкивая оттуда более сильные операции.
Историческая справка: Эдсгер Дейкстра и транслятор ALGOL

Сортировочную станцию Дейкстра придумал около 1960 года, когда в амстердамском Математическом центре писал первый в мире работающий транслятор языка ALGOL 60.

Читать дальше

Транслятор для машины Electrologica X1 Дейкстра и Яап Зонневельд закончили летом 1960 года, опередив все остальные группы мира. Сам алгоритм разбора выражений описан в техническом отчете 1961 года; имя shunting yard (сортировочная станция) родилось из сходства с работой железнодорожной сортировочной горки.

Для Дейкстры это был лишь эпизод большой карьеры: знаменитый алгоритм кратчайшего пути, носящий его имя, семафоры в операционных системах, структурное программирование и легендарное эссе "Go To Statement Considered Harmful" - тоже его работы. В 1972 году Дейкстра получил премию Тьюринга.

Алгоритм Дейкстры (Сортировочная станция): откуда имя

Название алгоритма Shunting-yard algorithm произошло из-за его схожести с железнодорожным узлом:

Правила работы алгоритма

Для работы требуется выходная очередь (массив ответа) и стек для хранения операторов. Читаем строку посимвольно слева направо:

  1. Если токен - число или переменная, он сразу добавляется в выходную очередь.
  2. Если токен - открывающая скобка (, она помещается в стек операторов.
  3. Если токен - закрывающая скобка ), операторы выталкиваются из стека в выход до тех пор, пока на вершине не покажется открывающая скобка. Сами скобки уничтожаются.
  4. Если токен - оператор (+, -, *, /):
    • Пока на вершине стека находится оператор с большим или равным приоритетом, выталкиваем его из стека в выходную очередь.
    • Затем помещаем текущий оператор в стек.
  5. В конце строки все оставшиеся в стеке операторы выталкиваются в выход.
Алгоритм Дейкстры (сортировочная станция) на выражении 2 * 3 + (4 - 1): операнды идут сразу в выход, операции ждут в стеке-тупике
Сортировочная станция на примере тренажера: операнды-вагоны едут прямо, операторы-локомотивы ждут в тупике

Тренажер: алгоритм Дейкстры, сортировочная станция

Переведем 2*3+(4-1) в обратную польскую запись.

Вход
Стек операторов, вершина справа
Выход (ОПЗ)
текущий символ в стеке операторов в выходе
В стеке: 0 В выходе: 0 Шаг: 0 / 0

Как вычислить ОПЗ?

Главная прелесть Обратной польской записи в том, что для ее вычисления больше не нужны ни скобки, ни приоритеты. Машина может вычислить все за один проход, используя простейший стек операндов.

Правила вычисления ОПЗ (чтение слева направо):

Тренажер: вычисление ОПЗ на стеке

Вычислим полученную выше запись 2 3 * 4 1 - +.

ОПЗ
Стек операндов, вершина справа
текущий символ в стеке операндов результат операции
В стеке: 0 Результат: - Шаг: 0 / 0

Алгоритм Дейкстры в коде на языке Си

Ядро алгоритма Дейкстры - это обработчик прихода нового оператора. Цикл `while` реализует логику вытеснения операторов на основе их приоритета.

// Функция приоритета
int priority(char op) {
    if (op == '*' || op == '/') return 2;
    if (op == '+' || op == '-') return 1;
    return 0; // Для скобок и неизвестных символов
}

// Приход нового оператора из входной строки
void process_operator(char current_op, Stack* operator_stack, Queue* output) {
    while (!is_empty(operator_stack)) {
        char top_op = top(operator_stack);
        
        // Если на вершине стека оператор с большим или равным приоритетом
        if (priority(top_op) >= priority(current_op)) {
            enqueue(output, pop(operator_stack)); // Выталкиваем его в ответ
        } else {
            break; // Вершина слабее, останавливаемся
        }
    }
    // Кладем пришедший оператор в стек
    push(operator_stack, current_op);
}

Коротко: главное

  • Алгоритм Дейкстры переводит инфиксную запись (со скобками) в ОПЗ (постфиксную, без скобок).
  • Операнды идут в выход, операторы - в стек.
  • Новый оператор "выталкивает" из стека на выход все операторы, имеющие больший или равный приоритет, после чего сам заходит в стек.
  • Закрывающая скобка выталкивает все операторы вплоть до открывающей.
  • ОПЗ тривиально вычисляется на одном стеке чисел: встретил знак - достал два числа, вычислил, вернул результат.

Проверьте себя

В стеке операторов лежит * (на вершине). В алгоритм поступает знак +. Что произойдет?

Как вам урок? Спасибо за отзыв!