working version

This commit is contained in:
ak 2023-07-05 10:53:34 -07:00
parent 5c4077cd6e
commit bfdf4ffd86
7 changed files with 2465 additions and 0 deletions

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
node_modules
.eslint*
.prettier*

1945
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

22
package.json Normal file
View 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
View 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
View 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
View 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
View 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));
};