소프트웨어/알고리즘

탐색 - 7. 깊이 우선 탐색(DFS, Depth First Search)

개발_노트 2025. 2. 24. 14:57

📌 7. 깊이 우선 탐색(DFS, Depth First Search)


7.1 DFS 개념 및 원리

🔹 DFS(Depth First Search)란?

DFS(깊이 우선 탐색, Depth First Search)는 그래프에서 한 방향으로 최대한 깊이 탐색한 후, 더 이상 갈 곳이 없으면 되돌아가면서 탐색하는 방식입니다.
트리나 그래프 탐색에 사용되며, 미로 찾기, 경로 탐색, 위상 정렬 등에 유용합니다.

💡 DFS 탐색 원리:

  1. 시작 정점에서 가장 깊은 곳까지 탐색
  2. 더 이상 갈 곳이 없으면 되돌아가서 다른 경로 탐색
  3. 모든 정점을 방문할 때까지 반복

🔹 DFS의 동작 방식

예제 그래프가 다음과 같다고 가정하겠습니다.

  1 - 2
  |   |
  3 - 4 - 5

DFS 순서는 다음과 같습니다.
1 → 2 → 4 → 5 → 3 (깊이 우선 탐색)


🔹 DFS의 시간 복잡도

그래프 형태 시간 복잡도
인접 리스트(Adjacency List) 사용 O(V + E)
인접 행렬(Adjacency Matrix) 사용 O(V²)

V: 정점(Vertex) 개수, E: 간선(Edge) 개수
인접 리스트 방식이 일반적으로 더 효율적입니다.


7.2 재귀(Recursive) 방식과 스택(Stack) 기반 반복(Iterative) 방식 비교

DFS는 재귀 함수(Recursive)와 스택(Stack)을 이용한 반복(Iterative) 방식으로 구현할 수 있습니다.

방식  장점 단점
재귀(Recursive) 코드가 간결하고 이해하기 쉬움 재귀 호출이 깊어지면 스택 오버플로우 발생 가능
스택(Stack) 기반 반복(Iterative) 메모리 절약, 스택 오버플로우 방지 코드가 상대적으로 복잡함

7.3 그래프에서 DFS를 활용하는 예시

🔹 1) 미로 찾기 (Maze Solving)

  • DFS를 이용해 미로의 출구를 찾을 수 있음
  • 방문한 경로를 추적하며 길을 탐색

🔹 2) 위상 정렬 (Topological Sorting)

  • 방향 그래프(DAG)에서 작업의 순서를 정하는 알고리즘
  • DFS 후 역순으로 정점을 정렬하면 위상 정렬 결과가 나옴

🔍 실습: DFS 구현

1️⃣ 재귀 함수 기반 DFS 구현

#include <stdio.h>

#define V 5  // 정점 개수

// 그래프 인접 행렬
int graph[V][V] = {
    {0, 1, 1, 0, 0},
    {1, 0, 0, 1, 0},
    {1, 0, 0, 1, 1},
    {0, 1, 1, 0, 1},
    {0, 0, 1, 1, 0}
};

int visited[V] = {0};  // 방문 여부 저장

// DFS 재귀 함수
void DFS_recursive(int node) {
    printf("%d ", node);
    visited[node] = 1; // 방문 표시

    for (int i = 0; i < V; i++) {
        if (graph[node][i] == 1 && !visited[i]) {
            DFS_recursive(i);
        }
    }
}

int main() {
    printf("DFS 탐색 순서 (재귀 방식): ");
    DFS_recursive(0); // 0번 정점부터 시작
    printf("\n");
    return 0;
}

📌 실행 결과

DFS 탐색 순서 (재귀 방식): 0 1 3 2 4

각 정점에서 연결된 가장 깊은 정점부터 탐색


2️⃣ 스택을 이용한 반복 DFS 구현

#include <stdio.h>
#include <stdlib.h>

#define V 5

// 그래프 인접 행렬
int graph[V][V] = {
    {0, 1, 1, 0, 0},
    {1, 0, 0, 1, 0},
    {1, 0, 0, 1, 1},
    {0, 1, 1, 0, 1},
    {0, 0, 1, 1, 0}
};

// DFS 스택 기반 반복 구현
void DFS_iterative(int start) {
    int visited[V] = {0}; // 방문 여부 저장
    int stack[V];
    int top = -1; // 스택 포인터

    stack[++top] = start; // 시작 정점을 스택에 추가

    while (top >= 0) {
        int node = stack[top--]; // 스택에서 정점 꺼내기

        if (!visited[node]) {
            printf("%d ", node);
            visited[node] = 1;

            // 인접 정점을 스택에 추가 (큰 숫자부터 작은 숫자 순서로 추가)
            for (int i = V - 1; i >= 0; i--) {
                if (graph[node][i] == 1 && !visited[i]) {
                    stack[++top] = i;
                }
            }
        }
    }
}

int main() {
    printf("DFS 탐색 순서 (반복 스택 방식): ");
    DFS_iterative(0);
    printf("\n");
    return 0;
}

📌 실행 결과

DFS 탐색 순서 (반복 스택 방식): 0 2 4 3 1

스택을 사용하여 DFS를 구현하면 메모리 효율적으로 탐색 가능


3️⃣ 미로 탐색 알고리즘 적용

DFS를 사용하여 미로의 출구를 찾는 예제입니다.

#include <stdio.h>

#define N 5

// 미로 지도 (0: 길, 1: 벽)
int maze[N][N] = {
    {0, 1, 0, 0, 0},
    {0, 1, 0, 1, 0},
    {0, 0, 0, 1, 0},
    {1, 1, 1, 1, 0},
    {0, 0, 0, 0, 0}
};

// 방문 기록 배열
int visited[N][N] = {0};

// 방향 벡터 (상, 하, 좌, 우)
int dx[] = {-1, 1, 0, 0};
int dy[] = {0, 0, -1, 1};

// DFS 탐색 함수
int DFS_maze(int x, int y) {
    if (x == N - 1 && y == N - 1) {
        printf("출구 도착!\n");
        return 1; // 출구 도착
    }

    visited[x][y] = 1;

    for (int i = 0; i < 4; i++) {
        int nx = x + dx[i];
        int ny = y + dy[i];

        if (nx >= 0 && nx < N && ny >= 0 && ny < N) {
            if (maze[nx][ny] == 0 && !visited[nx][ny]) {
                if (DFS_maze(nx, ny)) return 1;
            }
        }
    }

    return 0; // 길이 없음
}

int main() {
    if (DFS_maze(0, 0)) {
        printf("미로 탈출 성공!\n");
    } else {
        printf("출구에 도달할 수 없습니다.\n");
    }
    return 0;
}

📌 실행 결과

출구 도착!
미로 탈출 성공!

DFS를 사용하여 미로 출구를 찾음


📌 마무리 및 정리

  • DFS는 그래프를 깊이 우선 탐색하는 방식
  • 재귀 vs 스택 기반 반복 방식 비교
  • 미로 찾기, 위상 정렬 등에 활용 가능