Перейти к содержимому
Обложка сообщества Разное

2008, oбходы деревьев и Microsoft Denmark

В далеком 2008 году произошло мое первое интервью с крупной софтверной компанией, которое я (неудивительно) бездарно провалил еще на стадии телефонного интервью, т.к. интервьюировался до того только в Казахстане, да и вообще, имел довольно слабое представление о том, как интервьюируют серьезные компании, и к чему следует быть готовым.

И вот, когда меня попросили открыть систему для совместного редактирования кода и писать там решение задачи в процессе интервью, это для меня стало _очень_ большим сюрпризом. Тем не менее, это был, пожалуй, самый полезный фейл в моей жизни, после которого я сделал ряд важных выводов.

Тем не менее, возвращаясь к тому событию, хотелось бы рассмотреть задачу, которую я зафейлил, а также сходные задачи и их решения.

Задача довольно стандартная, реализовать обход дерева в порядке postorder без использования рекурсии. Postorder это один из трех стандартных вариантов обхода дерева, два других это preorder и inorder. В данном посте я рассмотрю решения для всех трех обходов без помощи рекурсии.

Wiki дает следующие русские термины для этих обходов:

preorder - предупорядоченный обход; postorder - поступорядоченный обход; inorder - симметричный обход.

Для решения всех трех обходов без рекурсии необходимо будет использовать стек, который, в определенном смысле, заменит стек выполнения, на котором основана работа рекурсивной программы.

1) Preorder - порядок обхода такой: нода, левое поддерево, правое поддерево.

В рамках данной задачи preorder - это самый простой случай, когда мы инициализируем стек корнем дерева, и затем в цикле будем брать элемент из стека, выводить его значение, и после добавлять в стек его правого, а затем левого потомка (для того, чтобы на следующей итерации мы бы вытащили левого потомка, и пошли по левому поддереву, а затем по правому).

Иллюстрировать тут, ИМХО, нечего, достаточно просто посмотреть на имплементацию.
https://gist.github.com/codekrolik/446b67b970d9c038d649

А вот здесь пост на leetcode на эту тему.

 

2) Postorder - порядок обхода такой: левое поддерево, правое поддерево, нода.

Реализовать такой обход сложнее, т.к. необходимо различать для нод с потомками, в какой момент доставаемую из стека ноду надо передобавить в стек с ее потомками, а в какой момент ее следует процессить в порядке обхода (в нашем случае - вывести ее значение на экран).

Логика для этого будет следующая: если мы достаем из стека ноду и предыдущая "обойденная" нода не является ее ребенком, то мы передобавляем ноду и ее детей в стек в следующем порядке: нода, правый ребенок, левый ребенок.

Если же предыдущая нода - ее ребенок, то мы процессим ее и уходим на следующую итерацию (к ее родителю, либо правому сиблингу). Даный прием гарантирует порядок процессинга, при котором сама нода будет отпроцессена лишь после того, как ее левый и правый дети будут отпроцессены.

Моя имплементация алгоритма:
https://gist.github.com/codekrolik/916286dac0524caee4f6

Это специальный пост из моего блога, где показано на примере, как работает стек в этом алгоритме.

А здесь пост на leetcode по этой теме.

 

3) Inorder - порядок обхода такой: левое поддерево, нода, правое поддерево.

С моей точки зрения, это самый хитрый обход в контексте реализации без рекурсии.

Решение можно представить следующим образом: для начала пройдем от корня по всем левым потомкам, добавляя их в стек. Когда мы дошли таким образом до листа дерева, необходимо вывести его, затем его родителя, и перейти на правое поддерево родителя, если таковое имеется.

Реализовать это можно это путем использования стека и дополнительной ноды (переменной). Инициализируем доп.ноду корнем дерева, и начнем действия в цикле по следующему алгоритму:

1) если доп.нода не NULL, добавим доп.ноду в стек, и изменим ее значение на левого ребенка.

2) если доп.нода NULL, вытащим ноду из стека, отпроцессим ее, и изменим значение доп. ноды на правого ребенка ноды из стека.

Таким образом, все ноды будут проходить следующий цикл:

1) запись ноды в доп.ноду;

2) добавление ноды в стек из доп.ноды, и запись левого ребенка в доп.ноду (т.е. левое поддерево будет отпроцессенно раньше, чем сама нода);

3) забирание (pop) ноды из стека, ее процессинг, и запись ее правого ребенка в доп.ноду. (т.е. правое поддерево будет отпроцессено сразу после того, как будет отпроцессена сама нода).

В случае если левый либо правый ребенок ноды отсутствуют, соответственно, левое/правое дерево будет проигнорировано, и цикл начнет получать ноды с предыдущего уровня из стека. Индикатором такой ситуации будет значение NULL в доп.ноде.

Моя имплементация:
https://gist.github.com/codekrolik/6f8fef669154f75b6316

Пост из моего блога с примером работы алгоритма.

Пост на leetcode.

А вот здесь тесты для всех реализаций обхода.
https://gist.github.com/codekrolik/ab806962e7e381c41cbf

0
0
298

Еще по теме

2008, oбходы деревьев и Microsoft Denmark - Yvision.kz