Яка різниця між зворотним відстеженням та глибиною першого пошуку?
Яка різниця між зворотним відстеженням та глибиною першого пошуку?
Відповіді:
Зворотний трек - алгоритм більш загального призначення.
Поглиблений пошук - це специфічна форма зворотного відстеження, пов'язана з пошуком деревних структур. З Вікіпедії:
Починається з кореня (вибираючи який-небудь вузол як корінь у випадку графіка) та досліджує, наскільки це можливо, уздовж кожної гілки перед зворотним відстеженням.
Він використовує зворотний трек як частину своїх засобів роботи з деревом, але обмежується структурою дерева.
Однак зворотний трек можна використовувати в будь-якому типі структури, де можна усунути частини домену - незалежно від того, чи це логічне дерево. У прикладі Wiki використовується шахова дошка та конкретна проблема - ви можете переглянути конкретний хід та усунути його, а потім повернутись до наступного можливого руху, усунути його тощо
Я думаю, що ця відповідь на інше пов'язане питання пропонує більше розуміння.
Для мене різниця між зворотним відстеженням і DFS полягає в тому, що зворотний трекінг обробляє неявне дерево, а DFS має справу з явним. Це здається банальним, але це означає багато. Коли простір пошуку проблеми відвідано за допомогою зворотного відстеження, неявне дерево перетинається і обрізається посередині нього. Однак для DFS дерево / графік, яким він займається, явно побудовано, і неприйнятні випадки вже були викинуті, тобто обрізані, перед будь-яким пошуком.
Отже, зворотний трекінг - це DFS для неявного дерева, тоді як DFS здійснює зворотний трек без обрізки.
Зворотний трек зазвичай реалізується як DFS плюс обрізка пошуку. Ви обходите глибину пошукового дерева в глибині спочатку, будуючи часткові рішення на цьому шляху. Брутська сила DFS може побудувати всі результати пошуку, навіть ті, які практично не мають сенсу. Це також може бути дуже неефективним для побудови всіх рішень (n! Або 2 ^ n). Тож насправді, як ви робите DFS, вам також потрібно обрізати часткові рішення, які не мають сенсу в контексті реальної задачі, і зосередитись на часткових рішеннях, які можуть призвести до правильних оптимальних рішень. Це власне техніка зворотного відстеження - ви відкидаєте часткові рішення якомога раніше, робите крок назад і знову намагаєтеся знайти локальний оптимум.
Ніщо не зупиняється, щоб перейти до дерева простору пошуку за допомогою BFS та виконувати стратегію зворотного відстеження на цьому шляху, але це не має сенсу на практиці, оскільки вам потрібно буде зберігати рівень пошуку за шаром у черзі, а ширина дерева зростає експоненціально до висоти, тож ми б дуже швидко витратили багато місця. Ось чому дерева зазвичай обробляють за допомогою DFS. У цьому випадку стан пошуку зберігається у стеку (стек викликів або явна структура) і не може перевищувати висоту дерева.
Зазвичай пошук по глибині - це спосіб ітерації через фактичну структуру графіка / дерева, яка шукає значення, тоді як зворотний трек - це ітерація через проблемний простір, який шукає рішення. Зворотний трек - більш загальний алгоритм, який не обов'язково стосується навіть дерев.
Я б сказав, DFS - це особлива форма зворотного відстеження; зворотний трек - загальна форма DFS.
Якщо ми поширимо DFS на загальні проблеми, ми можемо назвати це зворотним відстеженням. Якщо ми використовуємо зворотний трек для проблем, пов’язаних з деревом / графіком, ми можемо назвати його DFS.
Вони несуть ту саму ідею в алгоритмічному аспекті.
За словами Дональда Кнута, це те саме. Ось посилання на його статті про алгоритм Dancing Links, який використовується для вирішення таких «недеревних» проблем, як N-queens і Sudoku solver.
Зворотний трек, який також називають першим глибинним пошуком
ІМХО, більшість відповідей є в основному неточними та / або без будь-яких посилань на підтвердження. Тож дозвольте мені поділитися дуже чітким поясненням із посиланням .
По-перше, DFS - це загальний алгоритм обходу графіків (і пошуку). Тож його можна застосувати до будь-якого графіка (або навіть лісу). Дерево - це особливий вид Графіка, тому DFS працює і для дерева. По суті, давайте перестанемо говорити, що це працює лише для дерева, або подобається.
На основі [1], Backtracking - це особливий вид DFS, який використовується головним чином для економії місця (пам'яті). Відмінність, яку я збираюся зазначити, може здатися заплутаною, оскільки в таких алгоритмах Graph ми звикли мати представлення списку суміжності та використовувати ітеративний шаблон для відвідування всіх безпосередніх сусідів ( для дерева це безпосередні діти ) вузла , ми часто ігноруємо, що неправильна реалізація get_all_immediate_neighbors може спричинити різницю у використанні пам'яті базового алгоритму.
Крім того, якщо вузол графіка має коефіцієнт розгалуження b і діаметр h ( для дерева це висота дерева ), якщо ми зберігаємо всіх безпосередніх сусідів на кожному кроці відвідування вузла, вимоги до пам'яті будуть великими-O (bh) . Однак якщо взяти за один раз лише одного (негайного) сусіда і розширити його, тоді складність пам'яті зменшується до великої-O (h) . Хоча перший вид реалізації називається DFS , другий вид називається Backtracking .
Тепер ви бачите, якщо ви працюєте з мовами високого рівня, швидше за все, ви фактично використовуєте Backtracking під виглядом DFS. Більше того, відстеження відвіданих вузлів для дуже великої задачі може бути справді великою пам'яттю; закликаючи до ретельного проектування get_all_immediate_neighbors (або алгоритмів, які можуть обробляти перегляд вузла, не потрапляючи в нескінченний цикл).
[1] Стюарт Рассел та Пітер Норвіг, Штучний інтелект: сучасний підхід, 3-е видання
Перша глибина - це алгоритм переходу або пошуку дерева. Дивіться тут . Зворотний трек - це набагато більш широкий термін, який застосовується незалежно від того, де формується кандидат, який вирішується, а пізніше відкидається шляхом зворотного відстеження до колишнього стану. Дивіться тут . Перший глибинний пошук використовує зворотний трек для пошуку першої гілки (кандидат рішення), а якщо не вдалий - пошук іншої гілки.
DFS описує спосіб, яким ви хочете вивчити або пройти графік. Основна увага приділяється концепції проходження якомога глибшого даного вибору.
Хоча зворотний трек, як правило, реалізується за допомогою DFS, акцентує більше уваги на концепції обрізання неперспективних пошукових підпросторів якомога раніше.
Під час першого глибинного пошуку ви починаєте з кореня дерева, а потім досліджуєте якнайдалі по кожній гілці, а потім повертаєтеся до кожного наступного батьківського вузла і обміняєте його дітьми
Зворотний трек - це узагальнений термін для початку в кінці мети і поступово рухаючись назад, поступово будуючи рішення.
IMO, на будь-якому конкретному вузлі зворотного відстеження ви намагаєтеся спочатку глибоко розгалужитись на кожного з його дітей, але перед тим, як ви розгалужите будь-який з дочірніх вузлів, вам потрібно "витерти" попередній стан дитини (цей крок еквівалентний назад ходити до батьківського вузла). Іншими словами, кожний стан побратимів не повинен впливати один на одного.
Навпаки, під час звичайного алгоритму DFS у вас зазвичай немає такого обмеження, вам не потрібно видаляти (зворотній трек) попередній стан побратимів, щоб побудувати наступний вузол побратимів.
Ідея - Почніть з будь-якої точки, перевірте, чи потрібна її кінцева точка, якщо так, то ми знайшли рішення, інакше йде до всіх наступних можливих позицій, і якщо ми не можемо піти далі, поверніться до попереднього положення та шукайте інші альтернативи, що позначають цей струм шлях не буде вести нас до рішення.
Тепер зворотний трек і DFS - це 2 різних імені, що даються одній ідеї, застосованій для двох різних абстрактних типів даних.
Якщо ідея застосовується в матричній структурі даних, ми називаємо її зворотним відстеженням.
Якщо таку саму ідею застосувати на дереві чи графіку, ми називаємо її 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)