오상우
오상우

Categories

  • algorithm
  • data-structure
  • programming

DFS(depth first search)와 BFS(breadth first search)는 트리와 그래프를 탐색할때 사용하는 알고리즘이다. 여기서는 그래프 탐색에 대해 알아보자.

  • graph

그래프는 정점(vertex, v)과 간선(edge, e)로 이루어진 자료구조로, 현실에서도 많이 사용되기에 꼭 알아두어야 한다. 대표적으로 지도, sns 서비스의 친구 관계등이 있다. 또한 뱡향(directed), 무방향(undirected), 가중치 그래프 등 여러 종류의 그래프가 있는데, 가장 간단한 무방향 그래프를 구현할 것이다. 그래프를 구현하는 방법으로 2 차원 배열을 이용하거나 연결리스트를 활용할 수 있는데, 우리는 연결 리스트로 구현해보자.

class Graph {
  constructor() {
    this.adjacencyList = {};
  }

  addVertex(v) {
    // 정점 추가
    if (this.adjacencyList[v]) return 'vertex already exist!'; // 만약 이미 존재하는 정점일경우 종료
    this.adjacencyList[v] = [];
  }

  addEdge(v1, v2) {
    // 간선 추가
    this.adjacencyList[v1].push(v2);
    this.adjacencyList[v2].push(v1);
  }

  removeEdge(v1, v2) {
    // 간선 삭제
    this.adjacencyList[v1] = this.adjacencyList[v1].filter(v => v !== v2);
    this.adjacencyList[v2] = this.adjacencyList[v2].filter(v => v !== v1);
  }

  removeVertex(v) {
    // 정점 삭제
    this.adjacencyList[v].forEach(adjV => this.removeEdge(v, adjV)); // 삭제할 정점과 연결된 간선들 모두 삭제

    this.adjacencyList[v] = null;
  }
}
  • DFS

한국어로는 ‘깊이 우선 탐색’인데, 깊이를 우선한다는게 무슨 말일까? 말 그대로, 같은 레벨의 다른 노드들 보다 아랫 레벨 노드를 우선적으로 방문한다는 의미이다.

그래프의 경우 시작 노드에서의 거리가 멀 수록 트리에서 낮은 레벨의 노드에 해당한다고 생각하면 된다. DFS 는 stack 을 이용하여 탐색하는데, 방문한 노드들을 하나씩 stack 에 넣은 뒤, 더이상 내려갈 레벨이 없으면 스택에서 꺼내서 윗 레벨의 노드로 돌아가는 방식이다.
코드를 보자.

class Graph {
  constructor() {
    this.adjacencyList = {};
  }

  // ...
  // ...

  // 재귀알고리즘으로 구현한 dfs. 재귀 호출 자체가 콜 스택을 사용하기 때문에 따로 스택을 구현할 필요가 없다.
  dfsRecursive(startV) {
    let result = []; // 방문순서를 저장할 배열
    let visited = {}; // 이미 방문한 정점을 기록하는 객체

    const dfs = v => {
      visited[v] = true;
      result.push(v);

      this.adjacencyList[v].forEach(nearV => {
        if (!visited[nearV]) dfs(nearV);
      });
    };

    dfs(startV);

    return result;
  }

  // 반복알고리즘으로 구현한 dfs. 스택을 직접 구현한다.
  dfsIterative(startV) {
    let result = [];
    let visited = { [startV]: true };
    let stack = [startV]; // 스택은 배열로 구현

    while (stack.length) {
      // 스택에 정점이 남아있는 동안 반복
      const current = stack.pop(); // 스택에서 하나를 가져온다.
      result.push(current);
      this.adjacencyList[current].forEach(nearV => {
        if (!visited[nearV]) {
          stack.push(nearV);
          visited[nearV] = true;
        }
      });
    }
  }
}
  • BFS

한국어로는 너비 우선 탐색으로, 같은 레벨의 노드드를 모두 탐색한 뒤 다음 레벨로 진행하는 방식이다.

BFS 는 큐를 이용해 탐색한다. 큐의 특성상 먼저 들어간 노드가 먼저 나오게 되므로, 자식 노드보다 형제 노드에 먼저 접근하게 된다. 코드를 보자.

class Graph {
  constructor() {
    this.adjacencyList = [];
  }

  //...
  //...

  bfs(startV) {
    const result = [];
    const visited = { [startV]: true };
    const queue = [startV];

    while (queue.length) {
      const current = queue.shift();
      result.push(current);

      this.adjacencyList[current].forEach(nearV => {
        if (!visited[nearV]) {
          queue.push(nearV);
          visited[nearV] = true;
        }
      });
    }
  }
}

이렇게 큐와 스택을 이용한 두가지 탐색을 알아보았다.