너비 우선 탐색 & 깊이 우선 탐색, 이진 탐색

2025. 5. 7. 23:02·PS/알고리즘
728x90

그래프에서는 모든 정점을 방문해야 할 때가 자주 있다.

모든 정점을 방문하는 방법은 다양하지만, 대표적으로 BFS와 DFS 방법을 사용할 수 있다.

두 방법은 매우 간단하나, 그래프 알고리즘에서 핵심적인 위치를 차지한다.
∴ 두 탐색 방법에 대해 아주 깊은 직관을 가져야 할 필요가 있다.

트리에 대해 BFS, DFS 수행

BFS & DFS

  • 루트를 시작으로 탐색을 한다면 `BFS`는 먼저 루트의 자식을 차례로 방문함,
  • 다음으로 루트의 자식의 자식, 즉 루트에서 두 개의 간선을 거쳐서 도달할 수 있는 정점을 방문
    = 루트에서 가까운 거리 순으로 방문
  • `DFS`는 루트의 자식 정점을 하나 방문한 다음 아래로 내려갈 수 있는 곳까지 내려감
  • 더 이상 내려갈 수가 없으면 위로 되돌아 오다가 내려갈 곳이 있으면 즉각 내려감

 

일반적인 그래프 G =(V, E)에 BFS, DFS 수행

1) BFS 수행

BFS

  1. 시작 정점으로 정해진 정점 방문 (1)
  2. 1에서 방문한 정점에 인접한 정점 모두 방문 (2, 3, 4)
  3. 정점 2에 인접한 정점 중 방문하지 않은 정점은 없음
    • 정점 3에 인접한 정점 중 방문하지 않은 정점 모두 방문 (5)
    • 정점 4에 인접한 정점 중 방문하지 않은 정점 모두 방문 (6, 7)
  4. 정점 5에 인접한 정점 중 방문하지 않은 정점은 없음
    • 정점 6에 인접한 정점 중 방문하지 않은 정점 모두 방문 (8)
    • 정점 7에 인접한 정점 중 방문하지 않은 정점은 없음
  5. 정점 8에 인접한 정점 중 방문하지 않은 정점은 없음
📌 너비 우선 트리
그림에서 검은색 굵은 간선은 각 정점을 처음으로 방문할 때 사용한 간선임
그래프에서 이 간선들만 남기면 트리가 되는데, 이를 너비 우선 트리라고 함

BFS 알고리즘 기술

BFS(start) {
       Set<Integer> visited = new HashSet<>();
       Queue<Integer> queue = new LinkedList<>();

       visited.add(start);
       queue.add(start);

       while (!queue.isEmpty()) {
           int node = queue.poll();           
           
           for (int neighbor : graph.getOrDefault(node, new ArrayList<>())) {
               if (!visited.contains(neighbor)) {
                   visited.add(neighbor);
                   queue.add(neighbor);
               }
           }
       }
   }
  • 시작 정점을 제외한 모든 정점을 방문하지 않은 상태에서 시작.
  • 큐의 맨 앞에 있는 정점(시작점)을 빼내고 이에 인접한 정점 중 방문하지 않은 정점을 모두 방문표시 후 큐에 삽입
  • BFS에 수행되는 동안 add()와 poll()을 정확히 V번 호출 = 각 정점이 큐에 한 번씩 들어갔다가 나옴
  • `시간 복잡도: O(V + E)`
❓ 왜 O(V+E)인가?

정점은 한 번만 큐에 들어가고 한 번만 방문 처리되므로 O(V)
각 간선은 양 끝점 중 하나에서만 확인되므로 O(E)

무방향 그래프의 경우, 각 간선은 두 정점에서 한 번씩 확인되지만,
중복 체크를 통해 한 번만 큐에 들어가므로 결국 총 E개의 간선만큼 순회함


2) DFS 수행

DFS 수행

  1. 시작 정점으로 정해진 정점 방문 (1)
  2. 정점 1에 인접한 정점 중 하나 방문 (2)
  3. 정점 2에 인접하면서 방문하지 않은 정점 방문 → 3개 中 하나 방문 (3)
  4. 정점 3에 인접하면서 방문하지 않은 정점 방문 → 2개 中 하나 방문 (4)
  5. 정점 4에 인접하면서 방문하지 않은 정점 방문 (5)
  6. 정점 5에 인접한 정점 중 방문하지 않은 정점은 없으므로 왔던 길을 되돌아 감.
    • 정점 4, 3에 인접한 정점 중 방문하지 않은 정점은 없음
  7. 정점 2로 되돌아오면 인접한 정점 중 방문하지 않은 정점 방문 (6)
  8. 정점 6에 인접하면서 방문하지 않은 정점 방문 → 2개 中 하나 방문 (7)
  9. 정점 7에 인접하면서 방문하지 않은 정점은 없으므로 왔덜 길을 되돌아 감
    • 정점 6으로 되돌아감 = 방문하지 않은 정점X
    • 정점 2로 되돌아감 = 방문하지 않은 정점X
    • 정점 1로 되돌아감 = 방문하지 않은 정점X
  10. 시작 정점에서 더 이상 갈 곳이 없으므로 탐색 종료
📌 깊이 우선 트리
검은색 굵은 간선은 각 정점을 처음으로 방문할 때 사용한 간선임
그래프에서 이 간선들만 남기면 트리가 되는데, 이를 깊이 우선 트리라고 함

DFS 알고리즘 기술

DFS(node, visited) {
       visited.add(node);
       
       for (int neighbor : graph.getOrDefault(node, new ArrayList<>())) {
           if (!visited.contains(neighbor)) {
               DFS(neighbor, visited);
           }
       }
   }
  • 정점 node를 방문하였다고 표시하고, 이와 인접한 정점 중 방문하지 않은 정점에 대해 각 DFS 호출
  • 정점 V에 인접한 정점 중 방문하지 않은 정점 x, y, z가 있더라고 x에 대해 DFS를 호출하면 깊이 들어감
  • y나 z가 다른 정점과 인접하면 방문될 수 있음

궁극적으로 모든 정점에 대해 DFS가 한 번씩 호출되므로 시간복잡도는 `O(V + E)`


이진 탐색 (Binary Search)

이진 탐색(Binary Search)은 정렬된 배열에서 특정한 값을 효율적으로 찾는 탐색 알고리즘이다.

이 알고리즘의 기본 원리는 탐색 범위를 반으로 줄여가며 데이터를 찾는 것이며,
이 과정을 반복함으로써 탐색의 효율성을 극대화한다.

이진 탐색은 분할 정복(Divide and Conquer) 방식의 대표적인 예시로 볼 수 있다.


기본 원리

  1. 정렬된 배열: 이진 탐색을 수행하기 전에 데이터가 정렬되어 있어야 한다.
    데이터가 정렬되어 있지 않은 경우, 이진 탐색은 올바른 결과를 보장할 수 없음
  2. 중간점 선택: 탐색 범위 내에서 중간 위치의 요소를 선택,
    배열이 0부터 시작한다고 가정할 때, 중간점의 인덱스는 (left + right) / 2로 계산
  3. 탐색 조건 비교: 중간점의 데이터와 찾고자 하는 값(target)을 비교.이 때, 세 가지 경우가 발생할 수 있음
    • 중간점의 데이터 = 찾고자 하는 값: 탐색 종료
    • 중간점의 데이터 < 찾고자 하는 값: 탐색 범위의 왼쪽 부분을 버리고 오른쪽 부분을 새로운 탐색 범위로 설정
    • 중간점의 데이터 > 찾고자 하는 값: 탐색 범위의 오른쪽 부분을 버리고 왼쪽 부분을 새로운 탐색 범위로 설정
  4. 새로운 탐색 범위를 기반으로 2번과 3번의 과정을 찾고자 하는 값이 발견되거나 탐색 범위가 더 이상 존재하지 않을 때까지 반복

이진 탐색의 장단점

장점

  • 효율성: 이진 탐색은 `O(log n)`의 시간복잡도를 가지므로 대규모 데이터셋에서도 매우 빠름
  • 간결함: 알고리즘의 구현이 간결하며, 이해하기 쉬움

단점

  • 정렬된 데이터: 데이터가 미리 정렬되어 있어야 함
  • 동적 데이터셋: 데이터셋이 자주 변경되어 재정렬이 필요한 경우, 효율 저하

알고리즘 구현

이진 탐색을 구현하는 방법은 크게 재귀적 방법과 반복적 방법으로 나눌 수 있음

재귀적 방법은 함수가 자기 자신을 호출하여 문제를 해결하는 방식이며,
반복적 방법은 while 루프 등을 사용하여 같은 작업을 반복하는 방식입니다.

재귀적 방법

 binarySearchRecursive(int[] arr, int target, int left, int right) {
        if (left > right) {
            return false;
        }

        int mid = (left + right) / 2;

        if (arr[mid] == target) {
            return true;
        } else if (arr[mid] < target) {
            return binarySearchRecursive(arr, target, mid + 1, right);
        } else {
            return binarySearchRecursive(arr, target, left, mid - 1);
        }
    }

반복적 방법

  binarySearchIterative(int[] arr, int target) {
        int left = 0;
        int right = arr.length - 1;

        while (left <= right) {
            int mid = (left + right) / 2;

            if (arr[mid] == target) {
                return true;
            } else if (arr[mid] < target) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }

        return false;
    }

참고

https://wikidocs.net/233716

728x90
'PS/알고리즘' 카테고리의 다른 글
  • 암시적 그래프
  • 그리디 알고리즘 (탐욕법, Greedy)
  • 그래프
  • 재귀
0woy
0woy
Algorithm, CS, Web 등 배운 내용을 기록합니다.
  • 0woy
    0woy dev
    0woy
  • 전체
    오늘
    어제
  • 🌐 LANGUAGE
    • 분류 전체보기 (80)
      • Backend (21)
        • JAVA (7)
        • DB (11)
        • Spring (1)
        • Spring Security (2)
      • Computer Science (22)
        • 네트워크 (9)
        • 운영체제 (5)
        • 보안 (7)
      • Frontend (15)
        • HTML5 (1)
        • CSS (1)
        • JS (4)
        • Vue 3 (9)
      • PS (16)
        • LeetCode (2)
        • Baekjoon (1)
        • Programmers (1)
        • 알고리즘 (12)
      • Dev Trivia (6)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    Filter
    dfs
    Props
    tcp
    function
    https
    leetcode
    비밀키
    security
    대칭키
    BFS
    DP
    select
    공개키
    JS
    Graph
    CA
    JDBC
    가용성
    Spring
    Vue3
    그래프
    속성
    트리
    shortestpath
    java
    set
    PreparedStatement
    javascript
    RDB
  • 최근 댓글

  • 최근 글

  • 250x250
  • hELLO· Designed By정상우.v4.10.3
0woy
너비 우선 탐색 & 깊이 우선 탐색, 이진 탐색
상단으로

티스토리툴바