სიღრმეში ძებნა (DFS)

ლაბირინთის გავლა კედელზე ხელმიდებით, საქაღალდეების ხის დათვალიერება, დამოკიდებულებების გრაფში ციკლის ძებნა: ყველა მათგანი რაც შეიძლება ღრმად ჩადის და უკან მხოლოდ მაშინ ბრუნდება, როცა გზა აღარ არის. ეს სიღრმეში ძებნაა.

საშუალო⏱ 12 წთ
01

ისწავლე

იდეა, მექანიზმი და ფასი.

01ჯერ სიღრმეში, მერე უკან

BFS ტალღასავით ვრცელდება; DFS ცნობისმოყვარე მკვლევარივით იქცევა. მიმდინარე წვეროდან გადადის ნებისმიერ მოუნახულებელ მეზობელზე და აგრძელებს. როცა წვეროს მოუნახულებელი მეზობელი აღარ დარჩა, უკან ბრუნდება იმ წვეროში, საიდანაც მოვიდა, და მის შემდეგ მეზობელს ცდის.

ერთსა და იმავე ხეზე BFS წვეროებს დონე-დონე ნომრავს, DFS კი მთელ განშტოებას ამთავრებს, სანამ მეორე მხარეს შეეხება.

ბუნებრივი რეალიზაცია რეკურსიაა, ამიტომ აღრიცხვას გამოძახებების სტეკი აკეთებს: void dfs(int v) { used[v] = true; for (int to : g[v]) if (!used[to]) dfs(to); }. ეს BFS-ია, სადაც რიგი სტეკითაა შეცვლილი. DFS უმოკლეს გზებს არ პოულობს, მაგრამ სტრუქტურას ამჟღავნებს: ციკლებს, კომპონენტებს, ამოცანების რიგს, ხიდებს.

BFS: რიგი, დონე-დონე1234567DFS: სტეკი, ჯერ სიღრმეში1253467
რიცხვები ერთსა და იმავე ხეზე მონახულების რიგს აჩვენებს.

02ფერები და დროის ნიშნულები

BFS-ის მსგავსად DFS-იც ღებავს წვეროებს: თეთრი = მოუნახულებელი, რუხი = შესულია, მაგრამ ჯერ არ დასრულებულა (რეკურსიის სტეკზეა, მიმდინარე გზაზე), შავი = დასრულებული. დავამატოთ გლობალური საათი და ჩავიწეროთ ორი დრო:

  • tin[v]: როდის გახდა v რუხი (როდის შევედით);
  • tout[v]: როდის გახდა v შავი (მისი ყველა მეზობელი დამუშავდა).

ნახაზზე DFS BFS-ის გაკვეთილის გრაფზე ეშვება 4-დან და მეზობლებს სიის რიგით განიხილავს. ის მიდის 4 → 5 → 7 → 9, ბრუნდება 7-ში, გადადის 1-ზე და იქ ხედავს 5-ს, რომელიც რუხია და 1-ის მშობელი არ არის: ეს უკუწიბოა. არაორიენტირებულ გრაფში უკუწიბო ყოველთვის ციკლს კრავს, აქ 5 → 7 → 1 → 5. წიბოები, რომლებიც ახალ წვეროს აღმოაჩენს, ხის წიბოებია; ერთად ისინი DFS-ის ხეს ქმნიან.

ყოველ წვეროში ერთხელ შევდივართ და ყოველ სიას ერთხელ ვათვალიერებთ: O(V + E).

16/7210/13314/1741/1852/9611/1273/8815/1694/5tin / toutხის წიბოუკუწიბოუკუწიბო 1–5 → ციკლი
DFS BFS-ის ლექციის გრაფზე 4-დან: ნიშნებზე tin/tout ჩანს.

03ფრჩხილების სტრუქტურა

ჩაწერე „(v“, როცა v-ში შედიხარ, და „v)“, როცა გამოდიხარ, და მთელი DFS სწორად ჩალაგებულ ფრჩხილებად წაიკითხება. ინტერვალების ენაზე: ნებისმიერი ორი წვეროს [tin, tout] ინტერვალები ან არ იკვეთება, ან ერთი მეორეს შეიცავს; ნაწილობრივ გადაფარვა არასდროს ხდება.

აქედან გამომდინარეობს წინაპრის O(1) შემოწმება: u არის v-ს წინაპარი DFS-ის ხეში ზუსტად მაშინ, როცა tin[u] < tin[v] და tout[v] < tout[u]. ნახაზზე 5-ის ინტერვალი [2, 9] შეიცავს 7-ის [3, 8]-ს, ის კი 9-ის [4, 5]-ს და 1-ის [6, 7]-ს; 2 [10, 13] და 3 [14, 17] ცალკე განშტოებებია.

იგივე ნიშნულები შემდგომ ალგორითმებსაც ამოძრავებს. ტოპოლოგიური სორტირება tout-ის რიგს იყენებს; ხიდები და არტიკულაციის წერტილები tin-ს ადარებს უმცირეს მიღწევად tin-ს; ეილერის შემოვლის ხრიკი კი ყოველ ქვეხეს უწყვეტ შუალედად აქცევს, [tin, tout], პრეფიქსული ჯამებისა თუ სეგმენტების ხისთვის.

123456789101112131415161718დრო41–1852–973–894–516–7210–13611–12314–17815–16ჩალაგებული ინტერვალი = შთამომავალი

04კომპონენტები, იტერაციული DFS და ხაფანგები

ერთი გამოძახება dfs(s) ზუსტად იმ წვეროებს მოინახულებს, რომლებიც s-დან მიღწევადია. არაორიენტირებული გრაფის ყველა ბმული კომპონენტის საპოვნელად წვეროებს გადავუყვებით და ყოველი ჯერ მოუნახულებელი წვეროდან ახალ DFS-ს ვუშვებთ, გაშვებებს კი ვითვლით. სულ მაინც O(V + E) გამოდის, რადგან ყველა გამოძახებაში ჯამში ყოველ წვეროს ერთხელ ვსტუმრობთ. იგივე ციკლი ბადეში კუნძულებს ან გეგმაზე ოთახებს ითვლის.

ხაფანგები:

  • ღრმა რეკურსია: 10⁶ წვეროიანი გზა 10⁶ ჩადგმულ გამოძახებას ნიშნავს და ბევრ გარემოში სტეკი გადაივსება; მაშინ DFS დაწერე ცხადი stack<int>-ით ან გაზარდე სტეკის ლიმიტი;
  • ერთი used დროშა რუხს შავისგან ვერ განასხვავებს; ორიენტირებულ გრაფში ციკლის აღმოსაჩენად სამივე ფერი გვჭირდება;
  • DFS-ის გზები უმოკლესი არ არის; მანძილებისთვის გამოიყენე BFS.
123456789კომპონენტი 1კომპონენტი 2კომპონენტი 3for v = 1..n: თუ v მოუნიშნავია → dfs(v), comp++

ფასი ერთი შეხედვით

დროO(V + E)
მეხსიერება (რეკურსიის სიღრმე V-მდე)O(V)
წინაპრის შემოწმება tin/tout-ითO(1)
ყველა ბმული კომპონენტიO(V + E)

დაიმახსოვრე

  1. DFS მოუნახულებელ მეზობელთან ჩადის და უკან ბრუნდება, როცა გზა აღარ არის; აღრიცხვას რეკურსია, ანუ სტეკი, აკეთებს.
  2. შესვლისა და გამოსვლის დროები ფრჩხილებივით ილაგება, რაც წინაპრის O(1) შემოწმებას გვაძლევს და ციკლის აღმოჩენასა და ტოპოლოგიურ სორტირებას ამოძრავებს.
  3. ციკლი „თუ მოუნახულებელია, დაიწყე ახალი DFS“ ყველა ბმულ კომპონენტს O(V + E)-ში პოულობს.
02

ითამაშე

გაიარე ალგორითმი ბიჯ-ბიჯ, მერე სცადე შენს მონაცემებზე.

👀 რას უყურო: უყურე გამოძახებების სტეკს: ის ზუსტად რუხი გზაა საწყისიდან მიმდინარე წვერომდე. ნიშნებზე tin/tout ჩანს ჩაწერისთანავე. სცადე სხვა საწყისი წვეროები.

ინტერაქტიული ვიზუალიზატორიაქ დააწკაპე, მერე space ← →
✎ შენი მონაცემებიიგივე გრაფი, რაც BFS-ის გაკვეთილში. შეადარე DFS-ის ხე BFS-ის ხეს იმავე წვეროდან.

გრაფი (DFS, საწყისი: 4)

123456789
რუხი: გზაზეაშავი: დასრულებული

გამოძახებათა სტეკი

(ცარიელია)

დროები

v123456789
tin·········
tout·········
DFS რაც შეიძლება ღრმად ჩადის, სანამ უკან დაბრუნდება. ეს BFS-ია, სადაც რიგი სტეკითაა შეცვლილი (აქ რეკურსიის სტეკით). დავიწყოთ 4-დან.

ფსევდოკოდი

 1 dfs(u): 2   color[u]=gray; tin[u]=++t   // enter 3   for w in adj[u]: if white → tree edge, recurse 4     else if gray & not parent → BACK EDGE (cycle!) 5     else skip (parent edge or already finished) 6   color[u]=black; tout[u]=++t   // exit
1 / 1
03

შეამოწმე

სამი კითხვა. აირჩიე პასუხი და ნახე ახსნა.

№1

არაორიენტირებულ გრაფში DFS u-ში ხედავს მეზობელს w, რომელიც რუხია და u-ს მშობელი არ არის. რას ნიშნავს ეს?

№2

დროის ნიშნულები: u = [2, 9], v = [4, 5], w = [10, 13]. რომელი დებულებაა სწორი?

№3

რომელ ამოცანას სჭირდება BFS და არა DFS?

04

ივარჯიშე

რეალური ამოცანები გასამყარებლად, მარტივიდან რთულისკენ.