В чем разница между поиском с возвратом и поиском в глубину?


108

В чем разница между поиском с возвратом и поиском в глубину?

Ответы:


99

Поиск с возвратом - это алгоритм более общего назначения.

Поиск в глубину - это особая форма поиска с возвратом, связанная с поиском в древовидной структуре. Из Википедии:

Один начинается с корня (выбирая какой-либо узел в качестве корня в случае графа) и исследует, насколько это возможно, вдоль каждой ветви перед отслеживанием с возвратом.

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

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


13
Отвечая на древний пост здесь. Хороший ответ, но ... нельзя ли задачу о шахматной доске также представить в виде дерева? :-) Из любой позиции на шахматной доске для данной фигуры разве нет дерева возможных ходов, простирающихся в будущее? Часть меня чувствует, что любой случай, когда можно использовать обратное прослеживание, также можно смоделировать в виде дерева, но я не уверен, прав ли я в этой интуиции.
The111

4
@ The111: Действительно, любую задачу поиска можно представить в виде дерева - у вас есть узел для каждого возможного частичного решения и ребро от каждого узла к одному или нескольким возможным альтернативным вариантам, которые могут быть сделаны в этом состоянии. Я думаю, что ответ lcn, что обратное отслеживание обычно означает DFS в (обычно неявном) дереве поиска, созданном во время рекурсии, наиболее близок к истине.
j_random_hacker

5
@j_random_hacker Итак, DFS - это способ исследовать дерево (или график в более общем смысле), тогда как обратное отслеживание - это способ решения проблемы (которая использует DFS вместе с сокращением). :-)
The111

Что сбивает с толку, Википедия описывает откат как алгоритм поиска в глубину , очевидно, объединяя эти две концепции.
Андерсон Грин

30

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

Для меня разница между отслеживанием с возвратом и DFS заключается в том, что отслеживание с возвратом обрабатывает неявное дерево, а DFS - явное. Это кажется тривиальным, но это очень много значит. Когда поисковая область проблемы посещается с возвратом, неявное дерево просматривается и обрезается посередине. Тем не менее, для DFS дерево / граф, с которым он имеет дело, явно сконструирован, и неприемлемые случаи уже были отброшены, то есть отсечены до того, как будет выполнен какой-либо поиск.

Таким образом, обратное отслеживание - это DFS для неявного дерева, а DFS - это обратное отслеживание без сокращения.


Я думаю, что ошибочно думать об обратном отслеживании как об обработке неявного дерева. Когда я впервые прочитал это, я согласился, но, копнув глубже, я понял, что «неявное дерево» на самом деле ... дерево рекурсии. Возьмите любой пример, который использует отслеживание с возвратом, например перестановку строки символов, нет логического дерева (нет неявного дерева) что бы то ни было, что касается проблемы, но у нас есть дерево рекурсии, которое моделирует процесс инкрементного построения строки. Что касается обрезки, это обрезка, сделанная для дерева рекурсии, где выполняется полная грубая сила ... (продолжение следует)
Gang Fang

(продолжение) Например, выведите все перестановки строки «ответ» и скажем, третий символ должен быть символом «а». Первые 2 уровня дерева рекурсии подчиняются O (n!), Но на 3-м уровне все ветви, кроме тех, которые добавляют «a», отсекаются (выполняется возврат с возвратом).
Gang Fang

6

Отслеживание с возвратом обычно реализуется как DFS плюс сокращение поиска. Вы просматриваете дерево пространства поиска в глубину, попутно строя частичные решения. DFS методом грубой силы может построить все результаты поиска, даже те, которые практически не имеют смысла. Это также может быть очень неэффективным для построения всех решений (n! Или 2 ^ n). Таким образом, на самом деле, когда вы делаете DFS, вам также необходимо сокращать частичные решения, которые не имеют смысла в контексте фактической задачи, и сосредоточиться на частичных решениях, которые могут привести к действительным оптимальным решениям. Это настоящая методика поиска с возвратом - вы как можно раньше отбрасываете частичные решения, делаете шаг назад и снова пытаетесь найти локальный оптимум.

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


6

По словам Дональда Кнута, это то же самое. Вот ссылка на его статью об алгоритме Dancing Links, который используется для решения таких "не-древовидных" проблем, как N-королевы и решатель судоку.

Возврат, также называемый поиском в глубину


Упоминается на странице 1 связанного PDF-файла.
Стив Чавес,

5

Обычно поиск в глубину - это способ итерации по реальной структуре графа / дерева в поисках значения, тогда как поиск с возвратом - это итерация по проблемному пространству в поисках решения. Отслеживание с возвратом - это более общий алгоритм, который даже не обязательно относится к деревьям.


5

Я бы сказал, DFS - это особая форма поиска с возвратом; обратное отслеживание - это общая форма DFS.

Если мы расширим DFS до общих проблем, мы можем назвать это откатом. Если мы используем возврат к проблемам, связанным с деревом / графом, мы можем назвать это DFS.

Они несут ту же идею в алгоритмическом аспекте.


На самом деле связь между DFS и Backtracking прямо противоположна. Проверьте мой ответ, в котором это подробно описано.
KGhatak

5

ИМХО, большинство ответов либо в значительной степени неточны, либо без какой-либо ссылки для проверки. Итак, позвольте мне поделиться очень четким объяснением со ссылкой .

Во-первых, DFS - это общий алгоритм обхода (и поиска) графа. Таким образом, его можно применить к любому графу (или даже лесу). Дерево - это особый вид графа, поэтому DFS работает и с деревом. По сути, давайте перестанем говорить, что это работает только для дерева или тому подобного.

Согласно [1], Backtracking - это особый вид DFS, используемый в основном для экономии места (памяти). Различие, о котором я собираюсь упомянуть, может показаться запутанным, поскольку в алгоритмах Graph такого типа мы так привыкли к представлениям списков смежности и использованию итеративного шаблона для посещения всех непосредственных соседей ( для дерева это непосредственные дочерние элементы ) узла. , мы часто игнорируем, что плохая реализация get_all_immediate_neighbors может вызвать разницу в использовании памяти базовым алгоритмом.

Кроме того, если узел графа имеет коэффициент ветвления b и диаметр h ( для дерева это высота дерева ), если мы сохраняем всех непосредственных соседей на каждом этапе посещения узла, требования к памяти будут большими -O (bh) . Однако, если мы возьмем только одного (непосредственного) соседа за раз и расширим его, то сложность памяти снизится до big-O (h) . В то время как первый вид реализации называется DFS , второй называется обратным отслеживанием .

Теперь вы видите, что если вы работаете с языками высокого уровня, скорее всего, вы на самом деле используете Backtracking под видом DFS. Более того, отслеживание посещенных узлов для очень большого набора задач может потребовать значительных затрат памяти; призывая к тщательному проектированию get_all_immediate_neighbors (или алгоритмов, которые могут обрабатывать повторное посещение узла без попадания в бесконечный цикл).

[1] Стюарт Рассел и Питер Норвиг, Искусственный интеллект: современный подход, 3-е изд.


2

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


2

ИМО, на любом конкретном узле обратного отслеживания вы пытаетесь сначала углубить ветвление в каждый из его дочерних узлов, но перед тем, как перейти к любому из дочерних узлов, вам нужно «стереть» предыдущее дочернее состояние (этот шаг эквивалентен возврату перейти к родительскому узлу). Другими словами, состояние каждого брата и сестры не должно влиять друг на друга.

Напротив, во время обычного алгоритма DFS у вас обычно нет этого ограничения, вам не нужно стирать (отслеживать назад) предыдущее состояние братьев и сестер, чтобы построить следующий одноуровневый узел.


2

DFS описывает способ, которым вы хотите исследовать или перемещаться по графу. Он фокусируется на идее как можно глубже с учетом выбора.

Отслеживание с возвратом, хотя обычно реализуется через DFS, больше фокусируется на концепции сокращения бесперспективных подпространств поиска как можно раньше.


1

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

Отслеживание с возвратом - это обобщенный термин, обозначающий начало в конце цели и постепенное движение назад, постепенно выстраивая решение.


4
Возврат не означает начинать с конца и двигаться назад. Он ведет журнал посещенных узлов, чтобы вернуться в случае обнаружения тупика.
Гюнтер Йена

1
"начиная с конца ...", ага !!
7kemZmani

1

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

Теперь отслеживание с возвратом и DFS - это два разных названия, данные одной и той же идее, применяемой к двум различным абстрактным типам данных.

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

Если та же идея применяется к дереву или графу, мы называем это DFS.

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

Идея обоих алгоритмов одинакова.


0

Отслеживание с возвратом - это просто поиск в глубину с определенными условиями завершения.

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

Поэтому, когда я думаю об обратном пути, меня волнует

  1. государство
  2. Решения
  3. Базовые случаи (условия прекращения действия)

Я объясняю это в своем видео об откате здесь .

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

class Solution:    

"""

Approach: Backtracking 

State
    -candidates 
    -index 
    -target 

Decisions
    -pick one --> call func changing state: index + 1, target - candidates[index], path + [candidates[index]]
    -pick one again --> call func changing state: index, target - candidates[index], path + [candidates[index]]
    -skip one --> call func changing state: index + 1, target, path

Base Cases (Termination Conditions)
    -if target == 0 and path not in ret
        append path to ret
    -if target < 0: 
        return # backtrack 

"""

def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
    """
    @desc find all unique combos summing to target
    @args
        @arg1 candidates, list of ints
        @arg2 target, an int
    @ret ret, list of lists 
    """
    if not candidates or min(candidates) > target: return []

    ret = []
    self.dfs(candidates, 0, target, [], ret)
    return ret 

def dfs(self, nums, index, target, path, ret):
    if target == 0 and path not in ret: 
        ret.append(path)
        return #backtracking 
    elif target < 0 or index >= len(nums): 
        return #backtracking 


    # for i in range(index, len(nums)): 
    #     self.dfs(nums, i, target-nums[i], path+[nums[i]], ret)

    pick_one = self.dfs(nums, index + 1, target - nums[index], path + [nums[index]], ret)
    pick_one_again = self.dfs(nums, index, target - nums[index], path + [nums[index]], ret)
    skip_one = self.dfs(nums, index + 1, target, path, ret)
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.