너비 우선 탐색(BFS)이란 무엇인가

2020.03.13

메인

너비 우선 탐색(BFS)이란?

너비 우선 탐색(Breadth First Search), 보통 약자인 BFS라 부른다.

깊이 우선 탐색(DFS, DFS 문서 참고)와 함께 그래프를 순회하는 대표적인 방법이다.

간선을 따라 너비 방향으로 먼저 방문하고, 그 다음 아래로 내려가며(깊이 방향으로) 방문하면서
아직 방문하지 않은 정점을 방문 큐에 추가하고 그 큐를 참조하여 정점을 찾아가면서 인접정점을 방문한다.

구현 - JavaScript

class Graph {
  constructor() {
    // 정점목록
    this.vertices = [];
    // 인접리스트
    this.adjList = {};
  }

  // 정점 추가
  addVertex(v) {
    this.vertices.push(v);
    this.adjList[v] = [];
  }

  // 간선 추가
  addEdge(v, w) {
    this.adjList[v] ? this.adjList[v].push(w) : (this.adjList[v] = []);
    this.adjList[w] ? this.adjList[w].push(v) : (this.adjList[w] = []);
  }

  // BFS - 반복문 이용
  bfsWithLoop(startV) {
    let queue = [];
    const visiteds = new Set();
    let neighbors = this.adjList[startV] || [];
    let target;
    neighbors.sort();
    queue.push(startV);
    queue = [...queue, ...neighbors];

    const result = [];

    while (queue.length > 0) {
      target = queue.shift();
      result.push(target);
      visiteds.add(target);
      neighbors = this.adjList[target] || [];
      neighbors.sort();
      neighbors.forEach(val => {
        if (!visiteds.has(val) && queue.indexOf(val) < 0) {
          queue.push(val);
        }
      });
    }

    return result;
  }

  // BFS - 재귀 이용
  bfsWithRecursion(startV) {
    return bfs([startV], this.adjList, new Set());

    function bfs(queue, adjList, visiteds) {
      let result = [];

      // base case
      if (queue.length === 0) {
        return result;
      }

      const target = queue.shift();
      result.push(target);
      visiteds.add(target);

      const neighbors = adjList[target] || [];
      neighbors.sort();
      neighbors.forEach(val => {
        if (!visiteds.has(val) && queue.indexOf(val) < 0) {
          queue.push(val);
        }
      });

      return [...result, ...bfs(queue, adjList, visiteds)];
    }
  }
}

// Test
it("bfsWithLoop 메서드는 잘 동작한다.", () => {
  const graph = new Graph();
  const myVertices = [1, 2, 3, 4];
  myVertices.forEach((_, i) => {
    graph.addVertex(myVertices[i]);
  });
  graph.addEdge(1, 2);
  graph.addEdge(1, 3);
  graph.addEdge(1, 4);
  graph.addEdge(2, 4);
  graph.addEdge(3, 4);

  expect(graph.vertices).toEqual([1, 2, 3, 4]);
  expect(graph.adjList).toEqual({
    1: [2, 3, 4],
    2: [1, 4],
    3: [1, 4],
    4: [1, 2, 3],
  });

  expect(graph.bfsWithLoop(1)).toEqual([1, 2, 3, 4]);
});

it("bfsWithRecursion 메서드는 잘 동작한다.", () => {
  const graph = new Graph();
  const myVertices = [1, 2, 3, 4];
  myVertices.forEach((_, i) => {
    graph.addVertex(myVertices[i]);
  });
  graph.addEdge(1, 2);
  graph.addEdge(1, 3);
  graph.addEdge(1, 4);
  graph.addEdge(2, 4);
  graph.addEdge(3, 4);

  expect(graph.vertices).toEqual([1, 2, 3, 4]);
  expect(graph.adjList).toEqual({
    1: [2, 3, 4],
    2: [1, 4],
    3: [1, 4],
    4: [1, 2, 3],
  });

  expect(graph.bfsWithRecursion(1)).toEqual([1, 2, 3, 4]);
});

한계

가중치가 있는 그래프의 최단 경로를 구하는데는 사용할 수 없다.

가중치가 있는 그래프의 최단경로는 다익스트라 알고리즘(다익스트라 알고리즘 문서 참고)을 사용해 구할 수 있다.

사용 영역

  • 가중치 없는 그래프의 최단 경로 찾기
  • 아디트야 바르가바, 『Hello Coding 그림으로 개념을 이해하는 알고리즘』, 김도형, 한빛미디어(2017)
  • 스기우라 켄, 『그림으로 배우는 알고리즘』, 서재원, 영진(2016)
  • 이소 히로시, 『모던 자바스크립트 입문』, 서재원, 길벗(2018)
  • Martin Erwig, 『그들은 알고리즘을 알았을까?』, 송원형, 영진(2017)
  • Loiane Groner, 『자바스크립트 자료 구조와 알고리즘』, 이일웅, PACKT-에이콘(2015)
글쓴이쿠스, Qus
직업은 소프트웨어 개발자.
기술로 사회문제를 해결하는 시빅해킹(Civic Hacking)에 관심이 많고,
이것저것 생각나는 것들을 글로 정리하는 것을 좋아합니다.