μ•Œκ³ λ¦¬μ¦˜

[κ·Έλž˜ν”„ 탐색] 깊이 μš°μ„  탐색(Depth First Search, DFS)

μ„œμ•„λž‘πŸ˜ 2025. 2. 12. 18:32

 

 

깊이 μš°μ„  탐색(Depth-First Search, DFS)

깊이 μš°μ„  탐색(DFS)은 κ·Έλž˜ν”„ 탐색 μ•Œκ³ λ¦¬μ¦˜ 쀑 ν•˜λ‚˜λ‘œ, ν•œ μ •μ μ—μ„œ μ‹œμž‘ν•˜μ—¬ κ°€λŠ₯ν•œ κΉŠμ΄κΉŒμ§€ νƒμƒ‰ν•œ ν›„, 더 이상 갈 곳이 μ—†μœΌλ©΄ λ˜λŒμ•„μ˜€λŠ” 방식(λ°±νŠΈλž˜ν‚Ή)으둜 λ™μž‘ν•©λ‹ˆλ‹€.


πŸ”Ή DFS κ°œλ…

DFSλŠ” μŠ€νƒ(μž¬κ·€ 호좜 or λͺ…μ‹œμ  μŠ€νƒ)을 μ‚¬μš©ν•˜μ—¬ κ·Έλž˜ν”„λ₯Ό 깊게 νƒμƒ‰ν•˜λŠ” λ°©λ²•μž…λ‹ˆλ‹€.

  • λ¨Όμ € ν˜„μž¬ λ…Έλ“œλ₯Ό λ°©λ¬Έν•˜κ³ ,
  • λ°©λ¬Έν•˜μ§€ μ•Šμ€ 인접 λ…Έλ“œκ°€ 있으면 ν•΄λ‹Ή λ…Έλ“œλ‘œ μ΄λ™ν•˜μ—¬ 탐색을 계속함.
  • 더 이상 λ°©λ¬Έν•  λ…Έλ“œκ°€ μ—†μœΌλ©΄ 이전 λ…Έλ“œ(λΆ€λͺ¨ λ…Έλ“œ)둜 λŒμ•„κ°(λ°±νŠΈλž˜ν‚Ή).

πŸ”Ή DFS λ™μž‘ 방식

DFS의 κΈ°λ³Έ λ™μž‘ μˆœμ„œλŠ” λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.

  1. μ‹œμž‘ λ…Έλ“œλ₯Ό λ°©λ¬Έν•˜κ³  λ°©λ¬Έ 처리
  2. ν˜„μž¬ λ…Έλ“œμ—μ„œ 갈 수 μžˆλŠ” λ…Έλ“œλ₯Ό 확인
    • λ°©λ¬Έν•˜μ§€ μ•Šμ€ λ…Έλ“œκ°€ 있으면 ν•΄λ‹Ή λ…Έλ“œλ‘œ 이동
    • λ°©λ¬Έν•œ λ…Έλ“œλΌλ©΄ μŠ€ν‚΅
  3. 더 이상 λ°©λ¬Έν•  λ…Έλ“œκ°€ μ—†μœΌλ©΄ 이전 λ…Έλ“œλ‘œ λ˜λŒμ•„κ°
  4. λͺ¨λ“  λ…Έλ“œλ₯Ό λ°©λ¬Έν•  λ•ŒκΉŒμ§€ 2~3 κ³Όμ • 반볡

πŸ”Ή DFS κ΅¬ν˜„ 방법

DFSλŠ” 두 가지 λ°©μ‹μœΌλ‘œ κ΅¬ν˜„ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

  1. μž¬κ·€(Recursive) 방식
  2. μŠ€νƒ(Stack) μ‚¬μš© 방식(반볡문)

🟒 1. DFS μž¬κ·€(Recursive) 방식 κ΅¬ν˜„ (Python)

def dfs_recursive(graph, node, visited):
    if node in visited:  # 이미 λ°©λ¬Έν•œ 경우 μ’…λ£Œ
        return
    visited.add(node)  # 방문 처리
    print(node, end=" ")  # λ°©λ¬Έν•œ λ…Έλ“œ 좜λ ₯

    for neighbor in graph[node]:  # 인접 λ…Έλ“œ 탐색
        dfs_recursive(graph, neighbor, visited)

# κ·Έλž˜ν”„ (인접 리슀트 방식)
graph = {
    1: [2, 3],
    2: [4, 5],
    3: [6, 7],
    4: [],
    5: [],
    6: [],
    7: []
}

visited_set = set()
dfs_recursive(graph, 1, visited_set)

좜λ ₯:

1 2 4 5 3 6 7

πŸ“Œ μž¬κ·€ 방식은 μŠ€νƒμ„ μ΄μš©ν•œ 것과 λ™μΌν•œ μ›λ¦¬λ‘œ λ™μž‘ν•©λ‹ˆλ‹€.


 

🟒 2. DFS μŠ€νƒ(Stack) μ‚¬μš© 방식 κ΅¬ν˜„ (반볡문)

def dfs_stack(graph, start):
    visited = set()
    stack = [start]  # μŠ€νƒ μ΄ˆκΈ°ν™”

    while stack:
        node = stack.pop()  # μŠ€νƒμ—μ„œ λ…Έλ“œ κΊΌλ‚΄κΈ°
        if node not in visited:
            visited.add(node)  # 방문 처리
            print(node, end=" ")  

            # μŠ€νƒμ— 인접 λ…Έλ“œλ₯Ό μΆ”κ°€ (λ°˜λŒ€λ‘œ λ„£μœΌλ©΄ μˆœμ„œ μœ μ§€)
            stack.extend(reversed(graph[node]))

# κ·Έλž˜ν”„ (인접 리슀트 방식)
graph = {
    1: [2, 3],
    2: [4, 5],
    3: [6, 7],
    4: [],
    5: [],
    6: [],
    7: []
}

dfs_stack(graph, 1)

좜λ ₯:

1 2 4 5 3 6 7

πŸ“Œ μŠ€νƒμ„ μ‚¬μš©ν•˜λ©΄ μž¬κ·€ 없이 DFSλ₯Ό κ΅¬ν˜„ν•  수 μžˆμŠ΅λ‹ˆλ‹€.


 

πŸ”Ή DFS vs BFS 차이점

비ꡐ ν•­λͺ© DFS(Depth-First Search) BFS(Breadth-First Search)
탐색 방식 μ΅œλŒ€ν•œ 깊이 탐색 ν›„ λ°±νŠΈλž˜ν‚Ή ν˜„μž¬ 레벨(λ„ˆλΉ„)μ—μ„œ 탐색 ν›„ λ‹€μŒ 레벨둜 이동
자료ꡬ쑰 μŠ€νƒ (or μž¬κ·€ 호좜) 큐 (Queue)
경둜 탐색 λͺ¨λ“  경둜λ₯Ό 탐색해야 ν•˜λŠ” 경우 적합 μ΅œλ‹¨ 경둜(κ°€μ€‘μΉ˜ μ—†λŠ” κ·Έλž˜ν”„) 탐색에 적합
κ΅¬ν˜„ λ‚œμ΄λ„ 비ꡐ적 간단 μƒλŒ€μ μœΌλ‘œ 볡작
μ‚¬μš© μ˜ˆμ‹œ λ°±νŠΈλž˜ν‚Ή(미둜 μ°ΎκΈ°, 퍼즐 ν•΄κ²°, μ‘°ν•©) μ΅œλ‹¨ 경둜 탐색 (κΈΈ μ°ΎκΈ°, λ„€νŠΈμ›Œν¬ 탐색)


πŸ”Ή DFS μ‘μš© 사둀

  1. κ·Έλž˜ν”„ 탐색
    • μ—°κ²° μš”μ†Œ μ°ΎκΈ°, 경둜 탐색
  2. λ°±νŠΈλž˜ν‚Ή 문제 ν•΄κ²°
    • 퍼즐 문제 (예: N-Queen 문제)
    • μ‘°ν•©(combination)κ³Ό μˆœμ—΄(permutation)
  3. λ„€νŠΈμ›Œν¬ μ—°κ²° μ—¬λΆ€ 확인
    • λ„€νŠΈμ›Œν¬ κ·Έλž˜ν”„μ—μ„œ μ—°κ²° μ—¬λΆ€ 검사
  4. 사이클 κ²€μΆœ
    • λ°©ν–₯/무방ν–₯ κ·Έλž˜ν”„μ—μ„œ 사이클 쑴재 μ—¬λΆ€ 확인
  5. 미둜 μ°ΎκΈ° (DFS 기반 λ°±νŠΈλž˜ν‚Ή)
    • ν•œ 경둜λ₯Ό λκΉŒμ§€ νƒμƒ‰ν•œ ν›„ λ‹€λ₯Έ 경둜 탐색



πŸ”Ή DFS μ‹œκ°„ λ³΅μž‘λ„ & 곡간 λ³΅μž‘λ„

  • μ‹œκ°„ λ³΅μž‘λ„:
    • 인접 리슀트: O(V + E) (정점 V, κ°„μ„  E)
    • 인접 ν–‰λ ¬: O(V²)
  • 곡간 λ³΅μž‘λ„:
    • O(V) (λ°©λ¬Έ 처리 λ°°μ—΄, μŠ€νƒ/μž¬κ·€ 호좜 μŠ€νƒ)


πŸ”Ή μ½”λ”© ν…ŒμŠ€νŠΈ 문제(λ°±μ€€ 2023)

1. 문제 μ„€λͺ…

μˆ˜λΉˆμ΄κ°€ μ„Έμƒμ—μ„œ κ°€μž₯ μ’‹μ•„ν•˜λŠ” 것은 μ†Œμˆ˜μ΄κ³ , μ·¨λ―ΈλŠ” μ†Œμˆ˜λ₯Ό 가지고 λ…ΈλŠ” 것이닀. μš”μ¦˜ μˆ˜λΉˆμ΄κ°€ κ°€μž₯ κ΄€μ‹¬μžˆμ–΄ ν•˜λŠ” μ†Œμˆ˜λŠ” 7331이닀. 7331은 μ†Œμˆ˜μΈλ°, μ‹ κΈ°ν•˜κ²Œλ„ 733도 μ†Œμˆ˜μ΄κ³ , 73도 μ†Œμˆ˜μ΄κ³ , 7도 μ†Œμˆ˜μ΄λ‹€. μ¦‰, μ™Όμͺ½λΆ€ν„° 1자리, 2자리, 3자리, 4자리 μˆ˜ λͺ¨λ‘ μ†Œμˆ˜μ΄λ‹€! μˆ˜λΉˆμ΄λŠ” μ΄λŸ° μˆ«μžλ₯Ό μ‹ κΈ°ν•œ μ†Œμˆ˜λΌκ³  μ΄λ¦„ λΆ™μ˜€λ‹€.

μˆ˜λΉˆμ΄λŠ” N자리의 숫자 μ€‘μ—μ„œ μ–΄λ–€ μˆ˜λ“€μ΄ μ‹ κΈ°ν•œ μ†Œμˆ˜μΈμ§€ κΆκΈˆν•΄μ‘Œλ‹€. N이 μ£Όμ–΄μ‘Œμ„ λ•Œ, 수빈이λ₯Ό μœ„ν•΄ N자리 μ‹ κΈ°ν•œ μ†Œμˆ˜λ₯Ό λͺ¨λ‘ μ°Ύμ•„λ³΄μž.(https://www.acmicpc.net/problem/2023)

(μ œν•œ μ‹œκ°„ 2초, 1<= N <= 8)

2. μ²˜μŒμ— μ‚¬μš©ν•œ 방법(C++ 17)

μ²˜μŒμ—λŠ” DFSλ¬Έμ œλΌλŠ” 것을 깨닫지 λͺ»ν•˜κ³ , std::string을 ν™œμš©ν•΄μ„œ 문제λ₯Ό ν’€μ—ˆμŠ΅λ‹ˆλ‹€. 초기 버전은 μ œν•œμ‹œκ°„μ„ ν›Œμ© λ„˜κ²ΌμœΌλ‚˜, 2의 λ°°μˆ˜λŠ” μ†Œμˆ˜κ°€ 될 수 μ—†λ‹€(2 μ œμ™Έ) λΌλŠ” κ·œμΉ™μ„ κΉ¨λ‹«κ³  짝수λ₯Ό μ œμ™Έν•˜μž 1850ms 둜 겨우 ν†΅κ³Όν–ˆμŠ΅λ‹ˆλ‹€. ν•˜μ§€λ§Œ N이 μ‘°κΈˆμ΄λΌλ„ λŠ˜μ—ˆμ„ κ²½μš°μ—λŠ” 볡사연산과 λ”λΆˆμ–΄ μ‹œκ°„μ΄ 많이 μ†Œμš”λ˜λŠ” 쒋지 μ•Šμ€ μ½”λ“œμ˜€μŠ΅λ‹ˆλ‹€. μš°μ„  λ¨Όμ € μ½”λ“œλ₯Ό λ³΄μ—¬λ“œλ¦¬κ² μŠ΅λ‹ˆλ‹€.

// λ°±μ€€ 2023
#include <iostream>
#include <algorithm>
#include <vector>
#include <string>
#include <cmath>
using namespace std;

bool isPrime(int num) {
    if (num <= 1) return false; // 1 μ΄ν•˜μ˜ μˆ«μžλŠ” μ†Œμˆ˜κ°€ μ•„λ‹˜
    if (num <= 3) return true;  // 2와 3은 μ†Œμˆ˜
    if (num % 2 == 0 || num % 3 == 0) return false; // 2와 3으둜 λ‚˜λˆ„μ–΄ 떨어지면 μ†Œμˆ˜κ°€ μ•„λ‹˜

    for (int i = 5; i * i <= num; i += 6) {
        if (num % i == 0 || num % (i + 2) == 0) return false;
    }
    return true;
}

int main()
{
    int N;
    cin >> N;

    int min = pow(10, N-1);
    int max = pow(10, N);
    for (int i = min; i < max; i++)
    {
        if (i > 10 && i % 2 == 0)
            continue;

        bool isPrimeNumber = true;
        string str = to_string(i);
        for (int j = 0; j < str.length(); j++)
        {
            string strNum;
            int idx = 0;
            while (idx <= j)
                strNum += str[idx++];

            int target = stoi(strNum);
            if (!isPrime(target))
            {
                isPrimeNumber = false;
                break;
            }
        }

        if (isPrimeNumber)
            cout << i << endl;
    }
}

 

3. DFSλ₯Ό ν™œμš©ν•œ μ΅œμ’… 방법(C++ 17)

ν—ˆλ¬΄ν•˜κ²Œλ„ DFSλŠ” 0msκ°€ κ±Έλ ΈμŠ΅λ‹ˆλ‹€. 일의 μžλ¦¬μ—μ„œ μ†Œμˆ˜λŠ” 2, 3, 5, 7 뿐이고 κ·Έ 이후에 올 수 μžˆλŠ” μˆ˜λŠ” 5개(ν™€μˆ˜) λΏμž…λ‹ˆλ‹€. 2 -> 23, 23-> 233 -> 2337 처럼 깊이 μš°μ„  λ°©μ‹μœΌλ‘œ μ„œμΉ˜ν•΄λ‚˜κ°€λŠ” DFSλ₯Ό ν™œμš©ν•˜λ©΄ μ‰½κ²Œ ν’€ 수 μžˆλ‹€λŠ” 것을 μ•Œ 수 μžˆμŠ΅λ‹ˆλ‹€. 문제λ₯Ό 보고 DFSλ₯Ό ν™œμš©ν•΄μ•Όκ² λ‹€κ³  μƒκ°ν•˜λŠ” 것이 κ°€μž₯ μ€‘μš”ν•˜λ‹€κ³  λ³Ό 수 μžˆμŠ΅λ‹ˆλ‹€.

// λ°±μ€€ 2023
#include <iostream>
#include <vector>
using namespace std;
int N;

bool isPrime(int num) {
    if (num <= 1) return false; // 1 μ΄ν•˜μ˜ μˆ«μžλŠ” μ†Œμˆ˜κ°€ μ•„λ‹˜
    if (num <= 3) return true;  // 2와 3은 μ†Œμˆ˜
    if (num % 2 == 0 || num % 3 == 0) return false; // 2와 3으둜 λ‚˜λˆ„μ–΄ 떨어지면 μ†Œμˆ˜κ°€ μ•„λ‹˜

    for (int i = 5; i * i <= num; i += 6) {
        if (num % i == 0 || num % (i + 2) == 0) return false;
    }
    return true;
}

void DFS(int num, int digit)
{
    if (digit == N)
    {
        if (isPrime(num))
        {
            cout << num << endl;
            return;
        }
    }

    for (int i = 1; i < 10; i += 2)
    {
        int next = num * 10 + i;
        if (isPrime(next))
            DFS(next, digit + 1);
    }
}

int main()
{
    cin >> N;
    DFS(2, 1);
    DFS(3, 1);
    DFS(5, 1);
    DFS(7, 1);
}

 


πŸ”Ή 정리

  • DFSλŠ” μŠ€νƒ(LIFO) ꡬ쑰λ₯Ό ν™œμš©ν•˜μ—¬ κ·Έλž˜ν”„λ₯Ό 깊이 νƒμƒ‰ν•˜λŠ” μ•Œκ³ λ¦¬μ¦˜
  • μž¬κ·€(Recursive) 방식과 μŠ€νƒ(Stack) λ°©μ‹μœΌλ‘œ κ΅¬ν˜„ κ°€λŠ₯
  • λ°±νŠΈλž˜ν‚Ή, 미둜 탐색, 경둜 탐색 λ“± λ‹€μ–‘ν•œ 문제 해결에 μ‚¬μš©
  • BFS와 λΉ„κ΅ν•˜λ©΄ μ΅œλ‹¨ 경둜 νƒμƒ‰λ³΄λ‹€λŠ” λͺ¨λ“  경우의 수λ₯Ό 탐색할 λ•Œ μœ λ¦¬ν•¨

DFSκ°€ ν•„μš”ν•œ 상황이라면, μž¬κ·€ 방식과 μŠ€νƒ 방식 쀑 μ μ ˆν•œ 방법을 μ„ νƒν•΄μ„œ μ‚¬μš©ν•˜λ©΄ λ©λ‹ˆλ‹€. πŸš€