working version
This commit is contained in:
parent
5c4077cd6e
commit
bfdf4ffd86
7 changed files with 2465 additions and 0 deletions
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
node_modules
|
||||||
|
.eslint*
|
||||||
|
.prettier*
|
||||||
1945
package-lock.json
generated
Normal file
1945
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
22
package.json
Normal file
22
package.json
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"name": "js-binary-search-tree",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Balanced Binary Search Tree implemented in ES6 JS",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "ssh://_gitea@git.alexanderk.net:3022/ak/js-binary-search-tree.git"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"devDependencies": {
|
||||||
|
"eslint": "^8.43.0",
|
||||||
|
"eslint-config-prettier": "^8.8.0",
|
||||||
|
"prettier": "^2.8.8"
|
||||||
|
},
|
||||||
|
"type" : "module"
|
||||||
|
}
|
||||||
8
src/Node.js
Normal file
8
src/Node.js
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
// node factory
|
||||||
|
export const Node = (value = null, left = null, right = null) => {
|
||||||
|
return {
|
||||||
|
value,
|
||||||
|
left,
|
||||||
|
right,
|
||||||
|
};
|
||||||
|
};
|
||||||
422
src/Tree.js
Normal file
422
src/Tree.js
Normal file
|
|
@ -0,0 +1,422 @@
|
||||||
|
import { mergeSort } from "./mergeSort.js";
|
||||||
|
import { Node } from "./Node.js";
|
||||||
|
|
||||||
|
// node factory which accepts an array when initialized
|
||||||
|
export const Tree = (array) => {
|
||||||
|
// reusable function for finding midpoint of array
|
||||||
|
function findMiddle(arr) {
|
||||||
|
return Math.floor(arr.length / 2);
|
||||||
|
}
|
||||||
|
// cached var for sorted array - to not have to reuse the function repeatedly
|
||||||
|
const sorted = mergeSort(array);
|
||||||
|
// takes an array of data and turns it into a balanced binary tree full of Node objects
|
||||||
|
const buildTree = (passedArr = sorted) => {
|
||||||
|
// termination condition
|
||||||
|
if (passedArr.length < 1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// assign as right
|
||||||
|
const right = [...passedArr];
|
||||||
|
// find middle
|
||||||
|
const middle = findMiddle(right);
|
||||||
|
// get root node
|
||||||
|
const root = Node(right[middle]);
|
||||||
|
// left is all digits from 0 to current index
|
||||||
|
const left = right.splice(0, middle);
|
||||||
|
// current index / parent node not included and deleted
|
||||||
|
right.splice(0, 1);
|
||||||
|
// right array now only has "right side" values
|
||||||
|
// assign root node's left to a new tree - will re-call function recursively
|
||||||
|
root.left = buildTree(left);
|
||||||
|
// do the same with root node's right - will re-call function recursively
|
||||||
|
root.right = buildTree(right);
|
||||||
|
//returns the root node
|
||||||
|
return root;
|
||||||
|
};
|
||||||
|
// root node
|
||||||
|
let root = buildTree();
|
||||||
|
// accepts a value to insert
|
||||||
|
const insertValue = (value) => {
|
||||||
|
// keep temporary vars for looping logic
|
||||||
|
const nodes = {
|
||||||
|
current: root,
|
||||||
|
finalNode: null,
|
||||||
|
};
|
||||||
|
// logic - while there are nodes to loop through
|
||||||
|
while (nodes.current) {
|
||||||
|
// get final leaf node after loop completion - this will be the node appended to
|
||||||
|
nodes.finalNode = nodes.current;
|
||||||
|
// disregard all duplicate values immediately
|
||||||
|
if (value === nodes.current.value)
|
||||||
|
return console.log("Duplicate entered!");
|
||||||
|
// step left or right based on value
|
||||||
|
value < nodes.current.value
|
||||||
|
? (nodes.current = nodes.current.left)
|
||||||
|
: (nodes.current = nodes.current.right);
|
||||||
|
}
|
||||||
|
// disregard all duplicate values immediately
|
||||||
|
if (value === nodes.finalNode.value)
|
||||||
|
return console.log("Duplicate entered!");
|
||||||
|
// append node to correct side of final node
|
||||||
|
value < nodes.finalNode.value
|
||||||
|
? (nodes.finalNode.left = Node(value))
|
||||||
|
: (nodes.finalNode.right = Node(value));
|
||||||
|
};
|
||||||
|
// accepts a value to delete
|
||||||
|
const deleteValue = (value) => {
|
||||||
|
// find value - call find function with specific parents boolean to get parent vars
|
||||||
|
const found = find(value, (parents = true));
|
||||||
|
// if the node exists
|
||||||
|
if (found) {
|
||||||
|
// gets current node
|
||||||
|
// nodes found by find() with parents boolean are in array format: [found node, parent node]
|
||||||
|
const node = found[0];
|
||||||
|
// if value has two children
|
||||||
|
if (node.left && node.right) {
|
||||||
|
// get in-order successor
|
||||||
|
const successor = ioSucc(node);
|
||||||
|
// get the parent of the successor by looking it up, finding value and returning parent via parents boolean
|
||||||
|
// nodes found by find() with parents boolean are in array format: [found node, parent node]
|
||||||
|
const successorParent = find(successor.value, (parents = true))[1];
|
||||||
|
// set node value to successor
|
||||||
|
node.value = successor.value;
|
||||||
|
// based on the way in-order succession works, there is no left on the successor
|
||||||
|
// if any rights are hanging around, assign them to successor parent's left
|
||||||
|
successorParent.left = successor.right;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// if value has one child
|
||||||
|
if (node.left || node.right) {
|
||||||
|
// clone children and their properties
|
||||||
|
if (node.left) {
|
||||||
|
node.value = node.left.value;
|
||||||
|
node.left = node.left.left;
|
||||||
|
node.right = node.left.right;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
node.value = node.right.value;
|
||||||
|
node.left = node.right.left;
|
||||||
|
node.right = node.right.right;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// if value has no children (leaf node)
|
||||||
|
// depending on if current node is parent's left or right, delete from parent
|
||||||
|
return nodes.current === nodes.parent.left
|
||||||
|
? (nodes.parent.left = null)
|
||||||
|
: (nodes.parent.right = null);
|
||||||
|
}
|
||||||
|
console.log("No such value found!");
|
||||||
|
};
|
||||||
|
// accepts a value and returns the node with the given value
|
||||||
|
const find = (value, parents = false) => {
|
||||||
|
// temp var - start searching at root
|
||||||
|
const nodes = { current: root, parent };
|
||||||
|
// logic - while there are nodes to loop through
|
||||||
|
while (nodes.current) {
|
||||||
|
// if values match, stop loop
|
||||||
|
if (value === nodes.current.value) break;
|
||||||
|
if (parents) nodes.parent = nodes.current;
|
||||||
|
// otherwise step left or right based on value
|
||||||
|
value < nodes.current.value
|
||||||
|
? (nodes.current = nodes.current.left)
|
||||||
|
: (nodes.current = nodes.current.right);
|
||||||
|
}
|
||||||
|
if (nodes.current) {
|
||||||
|
return nodes.parent ? [nodes.current, nodes.parent] : nodes.current;
|
||||||
|
}
|
||||||
|
console.log("No such value found!");
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
// console.log your tree in a structured format. This function will expect to receive the root of your tree as the value for the node parameter.
|
||||||
|
// code from Odin Project
|
||||||
|
const prettyPrint = (node = root, prefix = "", isLeft = true) => {
|
||||||
|
if (node === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (node.right) {
|
||||||
|
prettyPrint(node.right, `${prefix}${isLeft ? "│ " : " "}`, false);
|
||||||
|
}
|
||||||
|
console.log(`${prefix}${isLeft ? "└── " : "┌── "}${node.value}`);
|
||||||
|
if (node.left) {
|
||||||
|
prettyPrint(node.left, `${prefix}${isLeft ? " " : "│ "}`, true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// traverse the tree in breadth-first level order and provide each node
|
||||||
|
const levelOrder = (callback = null) => {
|
||||||
|
// make vars
|
||||||
|
const output = [];
|
||||||
|
const queue = [];
|
||||||
|
// push root
|
||||||
|
callback ? callback(root) : output.push(root.value);
|
||||||
|
const getChildren = (arr) => {
|
||||||
|
// exits at 0
|
||||||
|
if (arr.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// appends both nodes to both arrays and removes current from queue
|
||||||
|
if (arr[0].left && arr[0].right) {
|
||||||
|
arr.push(arr[0].left);
|
||||||
|
callback ? callback(arr[0].left) : output.push(arr[0].left.value);
|
||||||
|
arr.push(arr[0].right);
|
||||||
|
callback ? callback(arr[0].right) : output.push(arr[0].right.value);
|
||||||
|
// delete self from queue
|
||||||
|
arr.splice(0, 1);
|
||||||
|
// runs until all done
|
||||||
|
return getChildren(arr);
|
||||||
|
}
|
||||||
|
// if has one child
|
||||||
|
if (arr[0].left || arr[0].right) {
|
||||||
|
// left child
|
||||||
|
if (arr[0].left) {
|
||||||
|
arr.push(arr[0].left);
|
||||||
|
callback ? callback(arr[0].left) : output.push(arr[0].left.value);
|
||||||
|
arr.splice(0, 1);
|
||||||
|
return getChildren(arr);
|
||||||
|
}
|
||||||
|
// right child
|
||||||
|
else {
|
||||||
|
arr.push(arr[0].right);
|
||||||
|
callback ? callback(arr[0].right) : output.push(arr[0].right.value);
|
||||||
|
arr.splice(0, 1);
|
||||||
|
return getChildren(arr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (root.left && root.right) {
|
||||||
|
// push node to queue
|
||||||
|
queue.push(root.left);
|
||||||
|
callback ? callback(root.left) : output.push(root.left.value);
|
||||||
|
// push node to main array or function
|
||||||
|
queue.push(root.right);
|
||||||
|
callback ? callback(root.right) : output.push(root.right.value);
|
||||||
|
// run shim function
|
||||||
|
getChildren(queue);
|
||||||
|
}
|
||||||
|
// if one or the other, push the correct one
|
||||||
|
else if (root.left || root.right) {
|
||||||
|
// if left
|
||||||
|
if (root.left) {
|
||||||
|
queue.push(root.left);
|
||||||
|
callback ? callback(root.left) : output.push(root.left.value);
|
||||||
|
getChildren(queue);
|
||||||
|
}
|
||||||
|
// if right
|
||||||
|
else {
|
||||||
|
queue.push(root.right);
|
||||||
|
callback ? callback(root.right) : output.push(root.right.value);
|
||||||
|
getChildren(queue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// return array
|
||||||
|
return output;
|
||||||
|
};
|
||||||
|
// finds and returns the leftmost node of the specified node
|
||||||
|
function leftmostNode(node = root, parents = false) {
|
||||||
|
// set up temp var
|
||||||
|
const nodes = { current: node };
|
||||||
|
if (parents) {
|
||||||
|
const pArr = [];
|
||||||
|
}
|
||||||
|
// get leftest node in subtree
|
||||||
|
while (nodes.current.left) {
|
||||||
|
if (parents) pArr.splice(0, 0, nodes.current);
|
||||||
|
nodes.current = nodes.current.left;
|
||||||
|
}
|
||||||
|
// return
|
||||||
|
return parents ? [nodes.current, pArr] : nodes.current;
|
||||||
|
}
|
||||||
|
// finds and returns in-order successor of node (default root)
|
||||||
|
function ioSucc(node) {
|
||||||
|
// if there is a right node
|
||||||
|
if (node.right) return leftmostNode(node.right);
|
||||||
|
// if there is no right node, successor lies "above" - "walk" down from root
|
||||||
|
if (node === root) return null;
|
||||||
|
// make temp vars
|
||||||
|
const nodes = { current: root, last: null };
|
||||||
|
// get last node in search
|
||||||
|
while (nodes.current) {
|
||||||
|
// if this is the current node, break loop
|
||||||
|
if (node === node.current) break;
|
||||||
|
// if the value is less than the current node
|
||||||
|
if (node.value < nodes.current.value) {
|
||||||
|
// set parent
|
||||||
|
nodes.last = nodes.current;
|
||||||
|
// move left
|
||||||
|
nodes.current = nodes.current.left;
|
||||||
|
// continue loop
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// else move right
|
||||||
|
nodes.current = nodes.current.right;
|
||||||
|
}
|
||||||
|
// return
|
||||||
|
return nodes.last;
|
||||||
|
}
|
||||||
|
// returns nodes in order
|
||||||
|
const inorder = (callback = null) => {
|
||||||
|
// set up vars
|
||||||
|
const arr = [];
|
||||||
|
const nodes = {
|
||||||
|
current: leftmostNode(),
|
||||||
|
};
|
||||||
|
// get successors of leftest node and append to array
|
||||||
|
while (nodes.current) {
|
||||||
|
// depending on callback's existence, either pass node to callback or append to array
|
||||||
|
callback ? callback(nodes.current) : arr.push(nodes.current.value);
|
||||||
|
nodes.current = ioSucc(nodes.current);
|
||||||
|
}
|
||||||
|
// return array
|
||||||
|
return arr;
|
||||||
|
};
|
||||||
|
// returns nodes in preorder
|
||||||
|
const preorder = (callback = null) => {
|
||||||
|
// make vars
|
||||||
|
const output = [];
|
||||||
|
const nodes = [];
|
||||||
|
// push root to nodes array and output
|
||||||
|
nodes.push(root);
|
||||||
|
// recurse as long as there is a nodes array
|
||||||
|
const getChildren = (arr) => {
|
||||||
|
// exits at 0
|
||||||
|
if (arr.length === 0) {
|
||||||
|
// returns output array
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
// pushes the current node
|
||||||
|
callback ? callback(arr[0]) : output.push(arr[0].value);
|
||||||
|
// captures left and right values
|
||||||
|
const left = arr[0].left;
|
||||||
|
const right = arr[0].right;
|
||||||
|
// delete current node from nodes array
|
||||||
|
arr.splice(0, 1);
|
||||||
|
// if node has both left and right
|
||||||
|
if (left && right) {
|
||||||
|
// add left node to front of nodes array
|
||||||
|
arr.splice(0, 0, left);
|
||||||
|
// add right node to nodes array behind left node
|
||||||
|
arr.splice(1, 0, right);
|
||||||
|
// runs until all done
|
||||||
|
return getChildren(arr);
|
||||||
|
}
|
||||||
|
// if has one child
|
||||||
|
if (left || right) {
|
||||||
|
// left child
|
||||||
|
if (left) {
|
||||||
|
// add left node to front of nodes array
|
||||||
|
arr.splice(0, 0, left);
|
||||||
|
// runs until all done
|
||||||
|
return getChildren(arr);
|
||||||
|
}
|
||||||
|
// right child
|
||||||
|
else {
|
||||||
|
// add right node to front of nodes array
|
||||||
|
arr.splice(0, 0, right);
|
||||||
|
// runs until all done
|
||||||
|
return getChildren(arr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return getChildren(arr);
|
||||||
|
};
|
||||||
|
// return array
|
||||||
|
return getChildren(nodes);
|
||||||
|
};
|
||||||
|
// returns nodes in post-order
|
||||||
|
const postorder = (callback = null) => {
|
||||||
|
// make var
|
||||||
|
const output = [];
|
||||||
|
// recurse on a node as long as node exists
|
||||||
|
const getChildren = (node) => {
|
||||||
|
if (node === null) {
|
||||||
|
// returns output array
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// traverse left side
|
||||||
|
getChildren(node.left);
|
||||||
|
// traverse right side
|
||||||
|
getChildren(node.right);
|
||||||
|
// print current node
|
||||||
|
callback ? callback(node) : output.push(node.value);
|
||||||
|
};
|
||||||
|
getChildren(root);
|
||||||
|
return output;
|
||||||
|
};
|
||||||
|
// accepts a node and returns its height
|
||||||
|
const height = (passedNode = root) => {
|
||||||
|
// make vars
|
||||||
|
const heights = [];
|
||||||
|
// recurse
|
||||||
|
const getChildren = (node = passedNode, height = 0) => {
|
||||||
|
// if no children, push final height to array
|
||||||
|
if (node.left === null && node.right === null)
|
||||||
|
return heights.push(height);
|
||||||
|
if (node.left && node.right) {
|
||||||
|
height++;
|
||||||
|
getChildren(node.left, height);
|
||||||
|
getChildren(node.right, height);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (node.left) {
|
||||||
|
height++;
|
||||||
|
return getChildren(node.left, height);
|
||||||
|
}
|
||||||
|
height++;
|
||||||
|
return getChildren(node.right, height);
|
||||||
|
};
|
||||||
|
getChildren();
|
||||||
|
return Math.max(...heights);
|
||||||
|
};
|
||||||
|
// accepts a node and returns its depth (from root)
|
||||||
|
const depth = (node) => {
|
||||||
|
// make vars
|
||||||
|
const nodes = {
|
||||||
|
current: root,
|
||||||
|
depth: 0,
|
||||||
|
};
|
||||||
|
// step through nodes and find ours
|
||||||
|
while (nodes.current) {
|
||||||
|
// break on current
|
||||||
|
if (nodes.current.value === node.value) break;
|
||||||
|
// add to depth value
|
||||||
|
nodes.depth += 1;
|
||||||
|
// if left, go left otherwise go right
|
||||||
|
node.value < nodes.current.value
|
||||||
|
? (nodes.current = nodes.current.left)
|
||||||
|
: (nodes.current = nodes.current.right);
|
||||||
|
}
|
||||||
|
// returns depth value
|
||||||
|
return nodes.depth;
|
||||||
|
};
|
||||||
|
// checks if the tree is balanced
|
||||||
|
const isBalanced = () => {
|
||||||
|
const left = height(root.left);
|
||||||
|
const right = height(root.right);
|
||||||
|
if (right > left + 1) {
|
||||||
|
console.log("Tree is NOT balanced");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (left > right + 1) {
|
||||||
|
console.log("Tree is NOT balanced");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
console.log("Tree is balanced");
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
// rebalances an unbalanced tree and returns it
|
||||||
|
const rebalance = () => {
|
||||||
|
root = buildTree(inorder());
|
||||||
|
};
|
||||||
|
// return relevant functions
|
||||||
|
return {
|
||||||
|
prettyPrint,
|
||||||
|
insertValue,
|
||||||
|
deleteValue,
|
||||||
|
inorder,
|
||||||
|
levelOrder,
|
||||||
|
preorder,
|
||||||
|
postorder,
|
||||||
|
height,
|
||||||
|
depth,
|
||||||
|
isBalanced,
|
||||||
|
rebalance,
|
||||||
|
};
|
||||||
|
};
|
||||||
38
src/main.js
Normal file
38
src/main.js
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
import { Tree } from "./Tree.js";
|
||||||
|
|
||||||
|
// driver for tree functions
|
||||||
|
|
||||||
|
// creates a binary search tree from an array of random numbers < 100
|
||||||
|
const arr = [];
|
||||||
|
for (let i = 0; i < 100; i++) {
|
||||||
|
// pushes a number between 0 and 100 to array
|
||||||
|
arr.push(Math.round(Math.random() * 100));
|
||||||
|
}
|
||||||
|
const tree = Tree(arr);
|
||||||
|
tree.prettyPrint();
|
||||||
|
// confirms that the tree is balanced
|
||||||
|
tree.isBalanced();
|
||||||
|
// prints out all elements in level, pre, post, and in order
|
||||||
|
console.log(tree.levelOrder());
|
||||||
|
console.log(tree.preorder());
|
||||||
|
console.log(tree.postorder());
|
||||||
|
console.log(tree.inorder());
|
||||||
|
// unbalances the tree by adding several numbers > 100
|
||||||
|
tree.insertValue(101);
|
||||||
|
tree.insertValue(108);
|
||||||
|
tree.insertValue(105);
|
||||||
|
tree.insertValue(118);
|
||||||
|
tree.insertValue(110);
|
||||||
|
tree.prettyPrint();
|
||||||
|
// confirms that the tree is unbalanced
|
||||||
|
tree.isBalanced();
|
||||||
|
// balances the tree
|
||||||
|
tree.rebalance();
|
||||||
|
tree.prettyPrint();
|
||||||
|
// confirms the tree is balanced
|
||||||
|
tree.isBalanced();
|
||||||
|
// prints out all elements in level, pre, post, and in order
|
||||||
|
console.log(tree.levelOrder());
|
||||||
|
console.log(tree.preorder());
|
||||||
|
console.log(tree.postorder());
|
||||||
|
console.log(tree.inorder());
|
||||||
27
src/mergeSort.js
Normal file
27
src/mergeSort.js
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
// merge sort - recursive
|
||||||
|
export const mergeSort = (arr) => {
|
||||||
|
if (arr.length < 2) return arr;
|
||||||
|
// the sauce
|
||||||
|
function mergeArrays(left, right) {
|
||||||
|
// create output array
|
||||||
|
let output = [];
|
||||||
|
// while both halves of the array have values
|
||||||
|
while (left.length && right.length) {
|
||||||
|
// remove first index from correct array and add to output array
|
||||||
|
if (left[0] === right[0]) {
|
||||||
|
output.push(left.shift());
|
||||||
|
// removes duplicates
|
||||||
|
right.shift();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
left[0] < right[0]
|
||||||
|
? output.push(left.shift())
|
||||||
|
: output.push(right.shift());
|
||||||
|
}
|
||||||
|
// add the stragglers
|
||||||
|
return output.concat(left, right);
|
||||||
|
}
|
||||||
|
const half = Math.floor(arr.length / 2);
|
||||||
|
const left = arr.splice(0, half);
|
||||||
|
return mergeArrays(mergeSort(left), mergeSort(arr));
|
||||||
|
};
|
||||||
Loading…
Add table
Reference in a new issue