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

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
  • 전체
    오늘
    어제
    • 분류 전체보기 (45) N
      • Backend (4) N
        • JAVA (3)
        • DB (1) N
      • Frontend (15)
        • HTML5 (1)
        • CSS (1)
        • JS (4)
        • Vue 3 (9)
      • Computer Science (14) N
        • 네트워크 (8) N
        • 운영체제 (5)
      • PS (8)
        • LeetCode (2)
        • Baekjoon (1)
        • Programmers (0)
        • 알고리즘 (5)
      • Dev Trivia (4) N
  • 블로그 메뉴

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

  • 공지사항

  • 인기 글

  • 태그

    JS
    javascript
    set
    l7 스위치
    list
    map
    트리
    implicit graph
    function
    select
    fastretransmit
    BFS
    그래프
    java
    DNS
    html
    HTTP
    속성
    Props
    상속
    Vue3
    함수
    DP
    dom
    dfs
    tcp
    udp
    암시적그래프
    leetcode
    https
  • 최근 댓글

  • 최근 글

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

티스토리툴바