Допустим, вы хотели рекурсивно реализовать поиск в двоичном дереве в ширину . Как бы вы пошли об этом?
Возможно ли использовать только стек вызовов в качестве вспомогательного хранилища?
Допустим, вы хотели рекурсивно реализовать поиск в двоичном дереве в ширину . Как бы вы пошли об этом?
Возможно ли использовать только стек вызовов в качестве вспомогательного хранилища?
Ответы:
(Я предполагаю, что это просто какое-то упражнение на размышление или даже домашнее задание / вопрос с подвохом, но я полагаю, я мог бы представить какой-то причудливый сценарий, когда вам по какой-то причине не хватает места в куче [какой-то действительно плохой обычай диспетчер памяти - какие-то странные проблемы со временем выполнения / ОС?], пока у вас все еще есть доступ к стеку ...)
Обход в ширину традиционно использует очередь, а не стек. Природа очереди и стека в значительной степени противоположна, поэтому попытка использовать стек вызовов (который является стеком, отсюда и название) в качестве вспомогательного хранилища (очереди) в значительной степени обречен на неудачу, если вы не делаете что-то глупо смешное со стеком вызовов, которым ты не должен быть.
С другой стороны, природа любой нехвостовой рекурсии, которую вы пытаетесь реализовать, заключается в добавлении стека к алгоритму. Это больше не позволяет выполнять поиск в двоичном дереве в ширину, и, таким образом, время выполнения и все такое для традиционной BFS больше не применяется полностью. Конечно, вы всегда можете легко превратить любой цикл в рекурсивный вызов, но это не какая-либо значимая рекурсия.
Однако, как продемонстрировали другие, есть способы реализовать что-то, что следует семантике BFS, за определенную плату. Если стоимость сравнения стоит дорого, но обход узла обходится дешево, то как @Simon Buchan сделал , вы можете просто запустить итеративный поиск в глубину, только обработав листья. Это будет означать отсутствие растущей очереди, хранящейся в куче, только локальную переменную глубины, и стеки, накапливающиеся снова и снова в стеке вызовов, когда дерево перебирается снова и снова. И, как заметил @Patrick , двоичное дерево, поддерживаемое массивом, в любом случае обычно хранится в порядке обхода в ширину, поэтому поиск в ширину в этом случае будет тривиальным, также без необходимости во вспомогательной очереди.
источник
Если вы используете массив для поддержки двоичного дерева, вы можете определить следующий узел алгебраически. Если
i
это узел, то его дочерние элементы могут быть найдены в2i + 1
(для левого узла) и2i + 2
(для правого узла). Следующий сосед узла задается какi + 1
, если неi
является степенью2
Вот псевдокод для очень наивной реализации поиска в ширину в двоичном дереве поиска на основе массива. Это предполагает массив фиксированного размера и, следовательно, дерево фиксированной глубины. Он будет смотреть на узлы без родителей и может создать неуправляемо большой стек.
источник
Я не мог найти способ сделать это полностью рекурсивным (без какой-либо вспомогательной структуры данных). Но если очередь Q передается по ссылке, то у вас может быть следующая рекурсивная функция глупого хвоста:
источник
Следующий метод использовал алгоритм DFS, чтобы получить все узлы на определенной глубине - что аналогично выполнению BFS для этого уровня. Если вы выясните глубину дерева и сделаете это для всех уровней, результаты будут такими же, как у BFS.
Найти глубину дерева - это кусок пирога:
источник
level
станет ноль.Простая рекурсия BFS и DFS в Java:
просто нажмите / предложите корневой узел дерева в стеке / очереди и вызовите эти функции.
источник
Я нашел очень красивый рекурсивный (даже функциональный) алгоритм обхода Breadth-First. Не моя идея, но я думаю, что это должно быть упомянуто в этой теме.
Крис Окасаки объясняет свой алгоритм нумерации в ширину из ICFP 2000 по адресу http://okasaki.blogspot.de/2008/07/breadth-first-numbering-algorithm-in.html, используя только 3 изображения.
Scala-реализация Debasish Ghosh, которую я нашел по адресу http://debasishg.blogspot.de/2008/09/breadth-first-numbering-okasakis.html :
источник
Тупой путь:
источник
Вот краткое решение Scala :
Идея использования возвращаемого значения в качестве аккумулятора хорошо подходит. Может быть реализован на других языках аналогичным образом, просто убедитесь, что ваша рекурсивная функция обрабатывает список узлов .
Список тестовых кодов (с использованием тестового дерева @marco):
Вывод:
источник
Вот реализация Python:
источник
Вот Scala 2.11.4 реализация рекурсивной BFS. Я пожертвовал оптимизацией хвостового вызова для краткости, но версия TCOd очень похожа. Смотрите также сообщение @snv .
источник
Следующее мне кажется довольно естественным, используя Haskell. Выполните рекурсивную итерацию по уровням дерева (здесь я собираю имена в большую упорядоченную строку, чтобы показать путь через дерево):
источник
Вот реализация Python для рекурсивного обхода BFS, работающая для графа без цикла.
источник
Я хотел бы добавить свои центы к верхнему ответу в том, что если язык поддерживает что-то вроде генератора, bfs можно делать ко-рекурсивно.
Для начала ответ @ Tanzelax гласит:
Действительно, стек обычных вызовов функций не будет вести себя как обычный стек. Но функция генератора приостановит выполнение функции, поэтому она дает нам возможность получить следующий уровень дочерних узлов, не углубляясь в более глубоких потомков узла.
Следующий код является рекурсивным BFS в Python.
Интуиция здесь такова:
источник
Мне пришлось реализовать обход кучи, который выводит в порядке BFS. На самом деле это не BFS, но выполняет ту же задачу.
источник
Пусть v будет начальной вершиной
Пусть G - рассматриваемый граф
Ниже приведен псевдокод без использования очереди.
источник
BFS для двоичного (или n-арного) дерева может быть выполнена рекурсивно без очередей следующим образом (здесь, в Java):
Пример обхода печати номерами 1-12 в порядке возрастания:
источник
источник
Вот реализация JavaScript, которая имитирует обход в ширину и рекурсию в глубину. Я храню значения узлов на каждой глубине внутри массива, внутри хеша. Если уровень уже существует (у нас есть коллизия), поэтому мы просто перемещаемся к массиву на этом уровне. Вы также можете использовать массив вместо объекта JavaScript, поскольку наши уровни являются числовыми и могут служить индексами массива. Вы можете вернуть узлы, значения, преобразовать в связанный список или что угодно. Я просто возвращаю значения ради простоты.
Вот пример фактического обхода ширины с использованием итеративного подхода.
источник
Ниже приведен мой код для полностью рекурсивной реализации поиска в ширину двунаправленного графа без использования цикла и очереди.
источник
C # реализация рекурсивного алгоритма поиска в ширину для двоичного дерева.
Визуализация данных двоичного дерева
Если вы хотите, чтобы алгоритм работал не только с двоичным деревом, но и с графиками, которые могут иметь два и более узлов, которые указывают на один и тот же другой узел, вы должны избегать самоциклирования, храня список уже посещенных узлов. Реализация может выглядеть следующим образом.
Визуализация графических данных
источник
Я сделал программу, использующую c ++, которая также работает в объединенном и непересекающемся графе.
источник