В чем разница между поиском с возвратом и поиском в глубину?
В чем разница между поиском с возвратом и поиском в глубину?
Ответы:
Поиск с возвратом - это алгоритм более общего назначения.
Поиск в глубину - это особая форма поиска с возвратом, связанная с поиском в древовидной структуре. Из Википедии:
Один начинается с корня (выбирая какой-либо узел в качестве корня в случае графа) и исследует, насколько это возможно, вдоль каждой ветви перед отслеживанием с возвратом.
Он использует отслеживание с возвратом как часть своих средств работы с деревом, но ограничен древовидной структурой.
Тем не менее, поиск с возвратом может использоваться в структурах любого типа, в которых части домена могут быть исключены - независимо от того, является ли это логическим деревом или нет. В примере Wiki используется шахматная доска и конкретная проблема - вы можете посмотреть на конкретный ход и исключить его, затем вернуться к следующему возможному ходу, исключить его и т. Д.
Я думаю, что этот ответ на другой связанный с этим вопрос предлагает больше информации.
Для меня разница между отслеживанием с возвратом и DFS заключается в том, что отслеживание с возвратом обрабатывает неявное дерево, а DFS - явное. Это кажется тривиальным, но это очень много значит. Когда поисковая область проблемы посещается с возвратом, неявное дерево просматривается и обрезается посередине. Тем не менее, для DFS дерево / граф, с которым он имеет дело, явно сконструирован, и неприемлемые случаи уже были отброшены, то есть отсечены до того, как будет выполнен какой-либо поиск.
Таким образом, обратное отслеживание - это DFS для неявного дерева, а DFS - это обратное отслеживание без сокращения.
Отслеживание с возвратом обычно реализуется как DFS плюс сокращение поиска. Вы просматриваете дерево пространства поиска в глубину, попутно строя частичные решения. DFS методом грубой силы может построить все результаты поиска, даже те, которые практически не имеют смысла. Это также может быть очень неэффективным для построения всех решений (n! Или 2 ^ n). Таким образом, на самом деле, когда вы делаете DFS, вам также необходимо сокращать частичные решения, которые не имеют смысла в контексте фактической задачи, и сосредоточиться на частичных решениях, которые могут привести к действительным оптимальным решениям. Это настоящая методика поиска с возвратом - вы как можно раньше отбрасываете частичные решения, делаете шаг назад и снова пытаетесь найти локальный оптимум.
Ничто не мешает перемещаться по дереву пространства поиска с использованием BFS и выполнять стратегию обратного отслеживания по пути, но на практике это не имеет смысла, потому что вам нужно будет сохранять состояние поиска слой за слоем в очереди, а ширина дерева растет экспоненциально до высоты, поэтому мы очень быстро потеряем много места. Вот почему обход деревьев обычно осуществляется с помощью DFS. В этом случае состояние поиска сохраняется в стеке (стек вызовов или явная структура) и не может превышать высоту дерева.
По словам Дональда Кнута, это то же самое. Вот ссылка на его статью об алгоритме Dancing Links, который используется для решения таких "не-древовидных" проблем, как N-королевы и решатель судоку.
Обычно поиск в глубину - это способ итерации по реальной структуре графа / дерева в поисках значения, тогда как поиск с возвратом - это итерация по проблемному пространству в поисках решения. Отслеживание с возвратом - это более общий алгоритм, который даже не обязательно относится к деревьям.
Я бы сказал, DFS - это особая форма поиска с возвратом; обратное отслеживание - это общая форма DFS.
Если мы расширим DFS до общих проблем, мы можем назвать это откатом. Если мы используем возврат к проблемам, связанным с деревом / графом, мы можем назвать это DFS.
Они несут ту же идею в алгоритмическом аспекте.
ИМХО, большинство ответов либо в значительной степени неточны, либо без какой-либо ссылки для проверки. Итак, позвольте мне поделиться очень четким объяснением со ссылкой .
Во-первых, 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-е изд.
Сначала глубина - это алгоритм обхода или поиска по дереву. Смотрите здесь . Отслеживание с возвратом - это гораздо более широкий термин, который используется везде, где формируется кандидат на решение, а затем отбрасывается путем возврата к предыдущему состоянию. Смотрите здесь . При поиске в глубину сначала выполняется поиск с возвратом по ветке (кандидату решения), а в случае неудачи - по другой ветви.
ИМО, на любом конкретном узле обратного отслеживания вы пытаетесь сначала углубить ветвление в каждый из его дочерних узлов, но перед тем, как перейти к любому из дочерних узлов, вам нужно «стереть» предыдущее дочернее состояние (этот шаг эквивалентен возврату перейти к родительскому узлу). Другими словами, состояние каждого брата и сестры не должно влиять друг на друга.
Напротив, во время обычного алгоритма DFS у вас обычно нет этого ограничения, вам не нужно стирать (отслеживать назад) предыдущее состояние братьев и сестер, чтобы построить следующий одноуровневый узел.
DFS описывает способ, которым вы хотите исследовать или перемещаться по графу. Он фокусируется на идее как можно глубже с учетом выбора.
Отслеживание с возвратом, хотя обычно реализуется через DFS, больше фокусируется на концепции сокращения бесперспективных подпространств поиска как можно раньше.
При поиске в глубину вы начинаете с корня дерева, а затем исследуете как можно дальше вдоль каждой ветви, затем вы возвращаетесь к каждому последующему родительскому узлу и проходите по его дочерним узлам.
Отслеживание с возвратом - это обобщенный термин, обозначающий начало в конце цели и постепенное движение назад, постепенно выстраивая решение.
Идея - начните с любой точки, проверьте, является ли это желаемой конечной точкой, если да, то мы нашли решение, иначе идет ко всем следующим возможным позициям, и если мы не можем пойти дальше, вернитесь в предыдущую позицию и найдите другие альтернативы, помечающие этот текущий Путь не приведет нас к решению.
Теперь отслеживание с возвратом и DFS - это два разных названия, данные одной и той же идее, применяемой к двум различным абстрактным типам данных.
Если идея применяется к матричной структуре данных, мы называем это откатом.
Если та же идея применяется к дереву или графу, мы называем это DFS.
Клише здесь состоит в том, что матрица может быть преобразована в граф, а граф может быть преобразован в матрицу. Итак, мы действительно применяем идею. Если на графе, мы называем это DFS, а если на матрице, мы называем это отслеживанием с возвратом.
Идея обоих алгоритмов одинакова.
Отслеживание с возвратом - это просто поиск в глубину с определенными условиями завершения.
Подумайте о том, чтобы пройти через лабиринт, где на каждом шаге вы принимаете решение, это решение является вызовом стека вызовов (который выполняет ваш поиск в глубину) ... если вы дойдете до конца, вы можете вернуться к своему пути. Однако, если вы зашли в тупик, вы захотите вернуться из определенного решения, по сути, вернувшись из функции в стеке вызовов.
Поэтому, когда я думаю об обратном пути, меня волнует
Я объясняю это в своем видео об откате здесь .
Ниже приводится анализ кода обратного отслеживания. В этом коде обратного отслеживания мне нужны все комбинации, которые приведут к определенной сумме или цели. Следовательно, у меня есть 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)