ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 너비 우선 탐색 알고리즘 (BFS Algorithm)
    알고리즘, 자료구조/정리 2020. 7. 18. 15:45

    DFS(Depth-First Search) 알고리즘에 이어 BFS(Breadth-First Search)를 정리해본다


    BFS(Breadth-First Search)

    탐색 알고리즘 중 하나인 BFS는 트리의 root 노드(그래프일 경우 임의의 노드)부터 시작해 모든 이웃 노드를 탐색한 뒤 아래 depth로 내려가 탐색을 반복하는 방식이다. 즉 시작 노드에서 가장 가까운 노드들을 모두 탐색하고, 그다음으로 가까운 노드들을 모두 탐색하는 것.

    출처: https://en.wikipedia.org/wiki/Breadth-first_search

    DFS 알고리즘과 달리 BFS 알고리즘은 재귀 함수로 구현할 수 없다. 현재 노드에서 가까운 노드들을 모두 탐색한 뒤 그다음으로 가까운 노드들을 탐색하기 위해선 queue(FIFO: First In First Out)을 이용해야 하고, 어떤 노드를 방문했는지에 대해 검사를 해야 된다. 시간 복잡도는 DFS와 동일하게 O(|V|+|E|)으로써 그래프 사이즈만큼의 선형 시간이 걸린다.

     

    아래 문제를 통해 BFS 알고리즘을 연습해볼 수 있다. begin에서 target으로 변환하는 과정 중 가장 짧은 과정을 찾는 문제이며, 한 번에 한 개의 알파벳만 바꿀 수 있고 배열 words에 있는 단어로만 변환할 수 있다.

    https://programmers.co.kr/learn/courses/30/lessons/43163?language=javascript

     

    코딩테스트 연습 - 단어 변환

    두 개의 단어 begin, target과 단어의 집합 words가 있습니다. 아래와 같은 규칙을 이용하여 begin에서 target으로 변환하는 가장 짧은 변환 과정을 찾으려고 합니다. 1. 한 번에 한 개의 알파벳만 바꿀 수

    programmers.co.kr

    더보기

    2022.09.25 다시 풀어봄

    function solution(begin, target, words) {
        let answer = 0;
        
        if (!words.includes(target)) {
            return answer;
        };
    
        let queue = [[begin, words, 0]];
        
        while(queue.length) {
            let [pNode, remain, cnt] = queue.shift();
            if (pNode === target) {
                answer = cnt;
                break;
            };
    
            for (let i = 0; i < remain.length; i++) {
                let cNode = remain[i];
                let diff = cNode.split('').filter((s, j) => s !== pNode[j]);
     
                if (diff.length === 1) {
                    remain.splice(i, 1);
                    queue.push([cNode, remain, cnt + 1]);
                };
            };
        };
        
        return answer;
    };

     

    function solution(begin, target, words) {
        let queue = [], depth = [];
        
        // 두 단어간 다른 알파벳이 몇 개인지 반환해주는 함수
        const checkDiff = (b, w) => {
            let diff = 0;
            for(let i = 0; i < b.length; i++){
                if(b[i] !== w[i]) diff++;
            }
            return diff;
        }
        
        // 두 배열 사이에 겹치는 단어가 있는지 체크하는 함수
        const checkQ = (arr1, arr2) => {
        	// 두 배열에 겹치는 단어가 있으면 false 반환
            if(arr1.length >= arr2.length){
                for(let i = 0; i < arr2.length; i++){
                    if(arr1.includes(arr2[i])) return false;
                }
            }else{
                for(let i = 0; i < arr1.length; i++){
                    if(arr2.includes(arr1[i])) return false;
                }
            }
            // 두 배열에 겹치는 단어가 없으면 true 반환
            return true;
        }
        
        // 맨 처음에는 queue에 begin을 넣고 시작
        queue.push(begin);
        // queue에 요소가 들어있을 때 동안 반복
        while(queue.length > 0){
        	// equalDepth 변수에 queue를 복제한 배열 할당
            let equalDepth = [].concat(queue);
            // 배열 depth가 비어있다면, 배열 depth에 첫 단어(시작 노드 개념)를 push
            if(depth.length === 0){
                depth.push(equalDepth);
            // 아니라면 함수 checkQ를 통해 
            // depth의 마지막 요소와 equalDepth를 비교해 겹치는 단어가 있는지 확인 
            }else if(checkQ(depth[depth.length - 1], equalDepth)){
                // 겹치는 단어가 없으면 새로운 depth의 단어들만 있는 것이므로 depth에 push
                depth.push(equalDepth);
            }
            // depth에 최근 투입된 배열 중 target과 일치하는 단어가 있다면, 인덱스 반환
            if(depth[depth.length - 1].includes(target)) return depth.length - 1;
            
            // queue에 먼저 들어간 단어를 빼내어 tmp에 할당
            let tmp = queue.shift();
            // words 배열 처음부터 끝까지 순환하며 확인
            for(let i = 0; i < words.length; i++){
                // 처음 마주하는 단어이며 두 단어간 다른 알파벳의 개수가 1개라면
                if(checkDiff(tmp, words[i]) === 1){
                    // 배열 queue에 단어를 push
                    queue.push(words[i]);
                }
            }
        }
        // 만약 위에서 값을 반환하지 못했다면 변경할 수 없는 경우이므로 0 반환
        return 0;
    }

     

     

    >> BFS 알고리즘은 언제 사용해야 되고 장단점은 뭘까? <<

     

    BFS를 사용하는 경우

    • 목표 노드까지의 최단 경로를 알아내고 싶을 때 사용한다

    장점

    • 출발 노드에서 목표 노드까지의 최단 경로를 보장한다
    • 가중치가 없는 그래프에서는 시작점에서 끝 점까지의 최단 경로를 알 수 있다

    단점

    • 경로가 긴 경우, 탐색 가지가 급격히 증가함에 따라 많은 기억 공간이 필요하다
    • 해가 존재하지 않는다면 유한 그래프(finite graph)의 경우에는 모든 그래프를 탐색한 후에 실패로 끝난다
    • 무한 그래프(infinite graph)의 경우에는 해를 찾지도 못하고, 끝내지도 못한다.

     

     

     


    참고 자료

    댓글

jaejade's blog ٩( ᐛ )و