계획
주요 클래스
BSTreeNode
트리의 각 노드가 될 클래스
BSTree
BSTreeNode가 구조적으로 모여있는 트리클래스
기능
모든 기능에서 최대한 재귀호출을 지양하는 방식으로 구현
초기화
트리 구조로 구성된 일반 객체를 인자로 받아 BSTreeNode, BSTree 객체 초기화
삽입
새 노드를 어디에 삽입할지 다음의 과정을 따라 계산.
- ROOT 노드부터 시작하여 삽입할 key와 노드의 key를 비교해서, 해당 노드의 왼쪽자식 또는 오른쪽자식과 다시 비교
- 더이상 자식노드로 이동할 수 없을 때 해당 노드의 자식으로 새 노드 삽입
재귀호출을 피하기 위해 BSTreeNode 객체에 key 비교 + 왼쪽/오른쪽 자식으로 redirect 시키는 메서드를 두고,
해당 메서드를 반복적으로 호출하는 방식으로 구현.
순회
재귀호출 없이 스택과 반복문으로 전위(pre-order), 중위(in-order), 후위(post-order) 순회 구현
탐색
반복문 만으로 구현.
삭제
메서드 호출 시 전달한 옵션값에 따라 해당 노드만 삭제하거나 그 하위 모든 노드들도 삭제.
BSTreeNode 클래스 구현
클래스 구성
멤버 변수
- key: 노드의 대표값. 순회, 탐색, 삽입, 삭제 등의 모든 기능의 알고리즘에 사용된다.
- left: 자식 노드 (왼쪽)
- right: 자식 노드 (오른쪽)
- data: 노드에 포함시킬 커스텀 데이터
메서드
- redirect: 트리에 새로운 데이터 삽입 시, 어느 노드의 어느 방향 자식으로 추가할지 찾아가기 위한 메서드.
- getLeftLeaf: 노드 삭제 시, 그 자식 중 해당 노드의 위치를 대신할 새 노드를 찾기 위해 사용.
- getRightLeaf: 사용 계획은 없지만 getLeftLeaf 구현하는 김에 같이 구현해둔 메서드.
redirect() 구현
삽입할 key와 해당 노드의 key를 비교하여,
왼쪽/오른쪽 자식에게 다시 물어보라고 알려주거나
혹은 자기 자신의 왼쪽/오른쪽 자식으로 삽입하면 된다고 알려주는 역할
시그니처
redirect(newKey: number): {
node: BSTreeNode<T>;
direction: -1 | 0 | 1;
redirect: boolean;
}redirect
자신이 부모노드가 될 건지 혹은 자신의 자식노드에게 다시 물어봐야 하는지.
true: 그 자식 노드에게 다시 redirect() 메서드를 호출하여 값을 비교false: 반복적인 redirect()를 멈추고 노드를 삽입함.
node
redirect = true인 경우: 다음으로 물어볼 노드redirect = false인 경우: 새 노드의 부모가 될 노드
direction
redirect = true인 경우: 'node는 자신의 왼쪽 자식이다'redirect = false인 경우: 'node의 왼쪽 자식으로 삽입하면 된다'를 의미
메서드 전체 코드
redirect(newKey) {
// 중복 방지
if (newKey === this.key)
return {
node: this,
direction: 0,
redirect: false,
};
// redirect to left
if (newKey < this.key) {
if (this.left === null)
return {
node: this,
direction: -1,
redirect: false,
};
return {
node: this.left,
direction: -1,
redirect: true,
};
}
// redirect to right
if (newKey > this.key) {
if (this.right === null)
return {
node: this,
direction: 1,
redirect: false,
};
return {
node: this.right,
direction: 1,
redirect: true,
};
}
}getLeftLeaf() 구현
시그니처
getLeftLeaf(node?: BSTreeNode<T>): BSTreeNode<T>메서드 전체 코드
getLeftLeaf(node = this) {
let currentNode = node;
while (currentNode.left) {
currentNode = currentNode.left;
}
return currentNode;
}getRightLeaf() 메서드 전체 코드
getLeftLeaf()와 동일
getRightLeaf(node = this) {
let currentNode = node;
while (currentNode.right) {
currentNode = currentNode.right;
}
return currentNode;
}BSTree 클래스 구현
클래스 구성
멤버 변수
- root: 트리의 ROOT 노드
메서드
- insert: 삽입
- traverse: 순회
- search: 탐색
- deleteByKey: 삭제. 추후 조건에 해당하는 모든 노드를 삭제하는 기능을 만들고자 네이밍에 'ByKey'를 넣음.
insert() 구현
BSTreeNode.redirect() 메서드를 활용하여 삽입 지점을 찾고, 트리에 새 노드 추가.
삽입된 새 노드가 몇번째 depth인지 반환.
메서드 전체 코드
insert(newKey, data) {
if (!this.root) {
this.root = new BSTreeNode(newKey, data);
return { depth: 0 };
}
let result = null;
let currentNode = this.root;
let depth = 0;
while (true) {
depth++;
result = currentNode.redirect(newKey);
currentNode = result.node;
if (!result.redirect) {
if (result.direction === 0) break;
currentNode[result.direction === -1 ? "left" : "right"] =
new BSTreeNode(newKey, data);
break;
}
}
return { depth };
}traverse() 메서드 구현
전위/중위/후위 등 원하는 방향으로 순회 후, 순회한 key 배열을 응답
시그니처
traverse: (
orderWay: TBSTreeTraverseOrderWay,
cb?: (node: BSTreeNode<TData>) => void
) => BSTreeNode<TData>[];orderWay
어느 방향으로 순회할지.
- pre: 전위(pre-order) 순회
- in: 중위(in-order) 순회 - 오름차순
- out: 중위(in-order) 순회 - 내림차순
- post: 후위(post-order) 순회
cb
순회하는 각 노드를 대상으로 실행시킬 콜백함수
메서드 전체 코드
각 순회방향별로 다른 방식으로 스택 자료구조와 반복문을 사용하여 순회
traverse(orderWay, cb) {
if (!this.root) return [];
const result = [];
const stack = [];
let currentNode = this.root;
switch (orderWay) {
case "pre":
while (true) {
if (!currentNode) {
if (!stack.length) break;
currentNode = stack.pop();
}
if (cb) cb(currentNode);
result.push(currentNode.key);
if (currentNode.right) stack.push(currentNode.right);
currentNode = currentNode.left;
}
return result;
case "in":
while (true) {
let isPopped = false;
if (!currentNode) {
if (!stack.length) break;
currentNode = stack.pop();
isPopped = true;
}
if (isPopped) {
if (cb) cb(currentNode);
result.push(currentNode.key);
currentNode = currentNode.right;
continue;
}
stack.push(currentNode);
currentNode = currentNode.left;
}
return result;
case "out":
while (true) {
let isPopped = false;
if (!currentNode) {
if (!stack.length) break;
currentNode = stack.pop();
isPopped = true;
}
if (isPopped) {
if (cb) cb(currentNode);
result.push(currentNode.key);
currentNode = currentNode.left;
continue;
}
stack.push(currentNode);
currentNode = currentNode.right;
}
return result;
case "post":
const stackPost = [];
while (true) {
if (!currentNode) {
if (!stackPost.length) break;
const popped = stackPost.pop();
currentNode = popped.node;
if (popped.isHead) {
if (cb) cb(currentNode);
result.push(currentNode.key);
currentNode = null;
continue;
}
}
stackPost.push({ node: currentNode, isHead: true });
if (currentNode.right)
stackPost.push({ node: currentNode.right, isHead: false });
currentNode = currentNode.left;
}
return result;
default:
return [];
}
}search() 메서드 구현
시그니처
search: (key: number) => {
node: BSTreeNode<TData> | null;
parentNode: BSTreeNode<TData> | null;
direction: -1 | 0 | 1 | null;
depth: number;
};- node: 검색된 노드. 찾지 못했을 경우 null.
- parentNode: 검색된 노드의 부모 노드. 찾지 못했을 경우, 마지막으로 도달한 노드.
- direction: node가 parentNode의 어느 방향 자식인지.
- depth: 검색된 노드의 depth. 찾지 못했을 경우 parentNode의 depth.
메서드 전체 코드
search(key) {
if (!this.root) return null;
let currentNode = this.root;
let parentNode = null;
let direction = null;
let depth = 0;
while (true) {
if (currentNode.key === key)
return { node: currentNode, parentNode, direction, depth };
if (key < currentNode.key) {
if (currentNode.left === null)
return {
node: null,
parentNode: currentNode,
direction: null,
depth,
};
parentNode = currentNode;
currentNode = currentNode.left;
direction = -1;
depth++;
}
if (key > currentNode.key) {
if (currentNode.right === null)
return {
node: null,
parentNode: currentNode,
direction: null,
depth,
};
parentNode = currentNode;
currentNode = currentNode.right;
direction = 1;
depth++;
}
}
}deleteByKey() 메서드 구현
시그니처
deleteByKey: (key: number, options?: { cascade?: boolean }) => booleancascade
- true: key에 해당하는 노드와 모든 자식노드를 지움
- false: key에 해당하는 노드 하나만 지움
기본값: false
메서드 전체 코드
deleteByKey(key, options) {
if (!this.root) return false;
const found = this.search(key);
if (!found.node) return false;
const cascade = options?.cascade ?? false;
found.parentNode[found.direction === -1 ? "left" : "right"] = null;
if (cascade) return true;
// not cascade
found.parentNode[found.direction === -1 ? "left" : "right"] =
found.node.right;
found.node.right.getLeftLeaf().left = found.node.left;
return true;
}클래스 전체 코드
BSTreeNode
class BSTreeNode {
constructor(key, data) {
this.key = key;
this.left = null;
this.right = null;
this.data = data ?? null;
}
redirect(newKey) {
// 중복 방지
if (newKey === this.key)
return {
node: this,
direction: 0,
redirect: false,
};
// redirect to left
if (newKey < this.key) {
if (this.left === null)
return {
node: this,
direction: -1,
redirect: false,
};
return {
node: this.left,
direction: -1,
redirect: true,
};
}
// redirect to right
if (newKey > this.key) {
if (this.right === null)
return {
node: this,
direction: 1,
redirect: false,
};
return {
node: this.right,
direction: 1,
redirect: true,
};
}
}
getLeftLeaf(node = this) {
let currentNode = node;
while (currentNode.left) {
currentNode = currentNode.left;
}
return currentNode;
}
getRightLeaf(node = this) {
let currentNode = node;
while (currentNode.right) {
currentNode = currentNode.right;
}
return currentNode;
}
}BSTree
class BSTree {
constructor(initialData) {
this.root = null;
if (!initialData) return;
this.root = this.initialize(initialData);
}
initialize(initialData) {
const node = new BSTreeNode(initialData.key, initialData.data);
if (initialData.left) node.left = this.initialize(initialData.left);
if (initialData.right) node.right = this.initialize(initialData.right);
return node;
}
insert(newKey, data) {
if (!this.root) {
this.root = new BSTreeNode(newKey, data);
return { depth: 0 };
}
let result = null;
let currentNode = this.root;
let depth = 0;
while (true) {
depth++;
result = currentNode.redirect(newKey);
currentNode = result.node;
if (!result.redirect) {
if (result.direction === 0) break;
currentNode[result.direction === -1 ? "left" : "right"] =
new BSTreeNode(newKey, data);
break;
}
}
return { depth };
}
traverse(orderWay, cb) {
if (!this.root) return [];
const result = [];
const stack = [];
let currentNode = this.root;
switch (orderWay) {
case "pre":
while (true) {
if (!currentNode) {
if (!stack.length) break;
currentNode = stack.pop();
}
if (cb) cb(currentNode);
result.push(currentNode.key);
if (currentNode.right) stack.push(currentNode.right);
currentNode = currentNode.left;
}
return result;
case "in":
while (true) {
let isPopped = false;
if (!currentNode) {
if (!stack.length) break;
currentNode = stack.pop();
isPopped = true;
}
if (isPopped) {
if (cb) cb(currentNode);
result.push(currentNode.key);
currentNode = currentNode.right;
continue;
}
stack.push(currentNode);
currentNode = currentNode.left;
}
return result;
case "out":
while (true) {
let isPopped = false;
if (!currentNode) {
if (!stack.length) break;
currentNode = stack.pop();
isPopped = true;
}
if (isPopped) {
if (cb) cb(currentNode);
result.push(currentNode.key);
currentNode = currentNode.left;
continue;
}
stack.push(currentNode);
currentNode = currentNode.right;
}
return result;
case "post":
const stackPost = [];
while (true) {
if (!currentNode) {
if (!stackPost.length) break;
const popped = stackPost.pop();
currentNode = popped.node;
if (popped.isHead) {
if (cb) cb(currentNode);
result.push(currentNode.key);
currentNode = null;
continue;
}
}
stackPost.push({ node: currentNode, isHead: true });
if (currentNode.right)
stackPost.push({ node: currentNode.right, isHead: false });
currentNode = currentNode.left;
}
return result;
default:
return [];
}
}
search(key) {
if (!this.root) return null;
let currentNode = this.root;
let parentNode = null;
let direction = null;
let depth = 0;
while (true) {
if (currentNode.key === key)
return { node: currentNode, parentNode, direction, depth };
if (key < currentNode.key) {
if (currentNode.left === null)
return {
node: null,
parentNode: currentNode,
direction: null,
depth,
};
parentNode = currentNode;
currentNode = currentNode.left;
direction = -1;
depth++;
}
if (key > currentNode.key) {
if (currentNode.right === null)
return {
node: null,
parentNode: currentNode,
direction: null,
depth,
};
parentNode = currentNode;
currentNode = currentNode.right;
direction = 1;
depth++;
}
}
}
deleteByKey(key, options) {
if (!this.root) return false;
const found = this.search(key);
if (!found.node) return false;
const cascade = options?.cascade ?? false;
found.parentNode[found.direction === -1 ? "left" : "right"] = null;
if (cascade) return true;
// not cascade
found.parentNode[found.direction === -1 ? "left" : "right"] =
found.node.right;
found.node.right.getLeftLeaf().left = found.node.left;
return true;
}
}'JavaScript' 카테고리의 다른 글
| [Tree 라이브러리 제작] (2) npm 배포 (0) | 2025.09.05 |
|---|---|
| Destructuring Assignment; 구조분해할당 (0) | 2025.09.02 |
| Buffer.from() 메서드에 배열을 인자로 넘기면 일어나는 일 (1) | 2025.09.02 |
| Buffer; 버퍼 (0) | 2025.09.02 |
| TypedArray; 형식화 배열 (0) | 2025.09.02 |