Algorithm

BFS(Breadth-first search)

Summer.dev 2023. 5. 11. 21:28

지난번엔 DFS문제를 만나 가볍게 다뤄본 적이 있는데

이렇게나 빨리 BFS를 만났다.

 

BFS는 너비 우선 탐색으로 그래프 탐색 알고리즘 중 하나입니다. 시작 정점으로부터 거리가 가까운 정점부터 탐색해 나가는 방식으로, 레벨 순서대로 탐색하는 방법입니다.

BFS는 큐 자료구조를 이용하여 구현할 수 있습니다. 시작 정점을 큐에 넣고, 해당 정점과 인접한 정점을 모두 큐에 넣어 탐색합니다. 그러고 나서 큐에서 하나씩 정점을 꺼내면서 그 정점과 인접한 정점을 다시 큐에 넣어 탐색을 진행합니다. 이 과정을 큐가 비어질 때까지 반복합니다.

최단 경로를 찾는 문제에서 많이 사용되며, 그래프의 정점들이 거리에 대한 정보를 가지고 있을 때 유용합니다. 
ex) 길찾기, 네트워크 최적 경로, 게임 맵 최단거리, 퍼즐, 탐사(로봇, 드론 경로)

오늘도 문제를 보면서 자세히 살펴보도록 하겠다

 

🏋🏽문제

임의의 tree를 구성하는 노드 중 하나의 Node 객체를 입력받아, 해당 노드를 시작으로 너비 우선 탐색을 합니다.

이때, 탐색되는 순서대로 노드의 값이 저장된 배열을 리턴해야 합니다.

 

🔍풀이

let bfs = function (node) {
  // TODO: 여기에 코드를 작성합니다.
  let result = [];
  let queue = [node]

  while(queue.length >0){
    let current = queue.shift();
    result.push(current.value);

    for(let i=0; i<current.children.length; i++){
      queue.push(current.children[i]);
    }
  }

  return result;
};

// 이 아래 코드는 변경하지 않아도 됩니다. 자유롭게 참고하세요.
let Node = function (value) {
  this.value = value;
  this.children = [];
};

// 위 Node 객체로 구성되는 트리는 매우 단순한 형태의 트리입니다.
// membership check(중복 확인)를 따로 하지 않습니다.
Node.prototype.addChild = function (child) {
  this.children.push(child);
  return child;
};

✅ 입출력 예시

let root = new Node(1);
let rootChild1 = root.addChild(new Node(2));
let rootChild2 = root.addChild(new Node(3));
let leaf1 = rootChild1.addChild(new Node(4));
let leaf2 = rootChild1.addChild(new Node(5));
let output = bfs(root);
console.log(output); // --> [1, 2, 3, 4, 5]

🗝️ 이제 코드를 하나씩 살펴보자

let Node = function (value) {
  this.value = value;
  this.children = [];
};

1. Node 함수를 정의한다.

 

이 Node 함수는 인자로 value를 받아 노드를 생성한다. value는 노드의 value의 값이며 children 배열은 자식 노드들의 배열이다.

 

Node 함수의 인자로 들어간 1value가 되는것을 볼 수 있다.

Node.prototype.addChild = function (child) {
  this.children.push(child);
  return child;
};

2. Node 함수의 prototype객체에 addChild 함수를 정의한다.

 

인자로 자식 노드를 받아 현재 노드의 children 배열

에 추가한다. 반환값은 추가된 자식 노드이다.

 

입출력예시의 두 번째 줄을 실행시켜 보자

new Node(1)로 생성된 객체의 children 값에 new Node(2)라는 객체가 들어온 것을 확인할 수 있다.

그러니까 지금 root객체의 상태는

root = {
	value:1,
	children:[{value:2, children[]}],
	}

이렇게 객체가 중첩된 모양이다.

 

다시 한번 입출력 예시를 보면

let root = new Node(1);
let rootChild1 = root.addChild(new Node(2));
let rootChild2 = root.addChild(new Node(3));
let leaf1 = rootChild1.addChild(new Node(4));
let leaf2 = rootChild1.addChild(new Node(5));
let output = bfs(root);
console.log(output); // --> [1, 2, 3, 4, 5]

이런 모양인데 이것을 객체형태로 표현하자면..

지난번엔 이 부분을 훑고 지나가지 않아서 한번 다뤄봤다.

 

본격적으로 bfs 구현을 살펴보자면

결국 중첩된 객체의 값을 단계별로 탐색하는 것이다.

 

따라서 bfs 함수는 이렇게 구현할 수 있다.

let result = [];
let queue = [node];

result 배열에는 방문한 노드의 값을 저장할 것이고

queue는 시작 node 객체를 배열에 넣어준다.

  while(queue.length >0){
    let current = queue.shift();
    result.push(current.value);

    for(let i=0; i<current.children.length; i++){
      queue.push(current.children[i]);
    }
  }

while(node배열의 요소가 모두 사라질 때까지)

아래를 반복한다.

 

1. current 변수에 현재 노드의 첫 번째 요소를 shift() 메서드를 통해 저장한다.

2. result 배열에 1번에서 가져온 객체요소의 값을 push 한다.

 

해당 요소의 children 값인 배열의 길이만큼 반복문을 돌면서

queue 배열에 현재 1번의 children노드들의 값을 push 한다.

{value: 1, children: Array(2)}

결국 queue에 value와 children값을 가진 객체를 계속 push하면서

value값을 가져오는것!

 

마무리

사실 bfs와 dfs에 대해 자세한 구현사항이 있지만 아직 이해할정도가 못되어서 다루지 못했다 🥹 이 포스팅은 코플릿 문제에 한정되어있고 프로그래머스 문제를 다뤄보면서 더 자세히...알아보기로..