트리 순회(Tree traversal)
- 트리의 모든 노드를 순회하는 2가지 방법(이진탐색트리뿐만 아니라 트리 전반에 대한 방법)
- Breadth-frist Search(BFS, 너비우선탐색)
- Depth-first Seacrh(DFS, 깊이우선탐색)
- 본 포스팅에서 살펴볼 트리 순회 코드는 16. 이진탐색트리 포스팅에 적은 BinarySearchTree 클래스 코드를 기반으로 한 메서드다. 만약 삼진 트리를 다룬다면, Node 클래스 costructor에 this.left, this.right 뿐만 아니라 this.mid 같은 프로퍼티를 하나 더 추가해야 할 것이고, 트리 순회 메서드에서도 this.mid에 대해서도 다루는 알고리즘을 추가해야 한다. 아무튼 이번 포스팅에서는 이진탐색트리를 기반으로 트리 순회에 대해 살펴본다.
class Node {
constructor(value) {
this.value = value;
this.left = null;
this.right = null;
}
}
class BinarySearchTree {
constructor() {
this.root = null;
}
}
1. BFS(너비우선탐색)
- 루트부터 시작하고 기본적으로 트리를 가로질러서 작업한다.
BFS() {
let node = this.root;
const data = [];
const queue = [];
queue.push(node);
while (queue.length) {
node = queue.shift();
data.push(node.value);
if (node.left) queue.push(node.left);
if (node.right) queue.push(node.right);
}
return data;
}
2. DFS(깊이우선탐색)
- 깊이우선탐색의 방향은 트리의 수직 방향이고, 아래로 내려간 다음에 다시 올라온다.
- 깊이우선탐색에는 세 가지 방법이 있다. 모두 재귀를 사용하며, 이 세 가지 방법의 각각의 코드들은 재귀의 헬퍼함수 내에서
data.push(node.value);
한 줄의 위치만 다르고 나머지는 동일한다.
- PreOrder(전위순회)
- InOrder(중위순회)
- PostOrder(후위순회)
2.1. DFS - PreOrder(전위 순회)
DFSPreOrder() {
const data = [];
function traverse(node) {
data.push(node.value);
if (node.left) traverse(node.left);
if (node.right) traverse(node.right);
}
traverse(this.root);
return data;
}
2.2. DFS - InOrder(중위 순회)
DFSInOrder() {
const data = [];
function traverse(node) {
if (node.left) traverse(node.left);
data.push(node.value);
if (node.right) traverse(node.right);
}
traverse(this.root);
return data;
}
2.3. DFS - PostOrder(후위 순회)
DFSPostOrder() {
const data = [];
function traverse(node) {
if (node.left) traverse(node.left);
if (node.right) traverse(node.right);
data.push(node.value);
}
traverse(this.root);
return data;
}
트리순회 각각의 방법들을 언제 쓰면 좋을까?
- 트리순회 방법 중 BFS와 DFS 중 무엇이 나은가?
- 상황에 따라 다르다. 각각 장단점이 있다.
- 일단, BFS나 DFS 모두 모든 노드를 한 번씩 방문하기 때문에, 시간복잡도는 동일하다. 두 방법의 차이점은 공간복잡도에 있다.
- 위와 같이 추적해야 할 노드가 넓게 퍼져서 많은 경우에 BFS(너비우선탐색)을 사용하면, 트리를 가로지르면서 Queue에 데이터를 다 저장해야 해서 결국 많은 데이터가 저장되어 공간복잡도가 높아진다(시간복잡도는 BFS와 DFS가 서로 같아서 상관 없다).
- 반면 위와 같이 동일한 데이터 형태에 DFS(깊이우선탐색)를 사용하면, 트리를 가로지르지 않고 수직으로 내려가며 재귀를 이용하므로 추적해야 하는 노드가 더 적다. 빨간색 점 각각에서 재귀가 발생하여 호출 스택의 한 칸이 된다.
- 즉, 이런 식으로 세로로 깊지 않고
가로로 넓은 트리
의 경우에는 DFS(깊이우선탐색)가 BFS(너비우선탐색)보다 더 적은 공간을 점유하므로, DFS
를 사용하는 것이 적절하다.
- 반대로 아래 이미지처럼 가로는 좁고
세로로만 아주 깊은 트리
라면, BFS(너비우선탐색)가 DFS(깊이우선탐색)보다 더 적은 공간을 점유하므로, BFS
를 사용하는 것이 적절하다.
- DFS의 전위, 중위, 후위 탐색을 각각 언제 사용할까?
- 이진탐색트리에서
DFS-중위 탐색
을 하면, 모든 노드가 오름차순 순서대로 나오게 된다. 예를 들어서, 리스트를 받아서 데이터베이스에 넣어야 한다던가 순서대로 무언가 작업을 해야 하는 경우 도움이 될 수 있다. 물론 이진탐색트리에서 가능한 방법이다.
DFS-전위 탐색
은 root부터 하위 child로 순차적으로 순회하므로, 트리를 외부에 저장했다가 나중에 다시 트리를 그대로 구성해야 하는 경우에 도움이 된다.