Skip to content

Commit

Permalink
add BinaryTree class and tests (#3)
Browse files Browse the repository at this point in the history
* add class BinaryTree

* add BinaryTree and Node tests

* decrement size if node is deleted

* correct else if condition in get
  • Loading branch information
jeromecovington authored Feb 8, 2024
1 parent e57c3a1 commit f9f98aa
Show file tree
Hide file tree
Showing 3 changed files with 328 additions and 0 deletions.
252 changes: 252 additions & 0 deletions lists-and-trees/bst.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
import Node from "./bst-node";

export default class BinaryTree {
constructor(n) {
// Initialize the tree with either an array of ordered Nodes, each with
// unique data, or a single Node to set as root.
if (n && Array.isArray(n) && n.length) {
n.forEach((current, i) => {
if (!(current instanceof Node)) {
throw new Error(
"BinaryTree constructor was called with an Array with non-Node members.",
);
}

if (i === 0) {
this.root = current;
}

this.insert(n);
});
this.size = n.length;
} else if (n instanceof Node) {
this.root = n;
this.size = 1;
} else {
throw new Error(
"BinaryTree constructor was not called with a Node or Array as its argument.",
);
}
}

getSize() {
return this.size;
}

// Start: Day-Stout-Warren algorithm
balance(n) {
let pseudoRoot = new Node();
pseudoRoot.setRight(n);

this.treeToVine(n);
this.vineToTree(n, this.getHeight());

pseudoRoot.setRight(null);
pseudoRoot = null;
}

treeToVine(n) {
let tail = n;
let rest = tail.getRight();

while (rest !== null) {
if (rest.getLeft() === null) {
tail = rest;
rest = rest.getRight();
} else {
const temp = rest.getLeft();

rest.left = temp.getRight();
temp.setRight(rest);
rest = temp;
tail.setRight(temp);
}
}
}

vineToTree(n) {
let sz = this.size;
const leaves = sz + 1 - Math.pow(2, Math.log2(sz + 1));
this.compress(n, leaves);
sz = sz - leaves;

while (sz > 1) {
this.compress(n, Math.ceil(sz / 2));
sz = Math.ceil(sz / 2);
}
}

compress(n, count) {
let scanner = n;

for (let i = 1; i <= count; i++) {
const child = scanner.getRight();
scanner.setRight(child.getRight());
scanner = scanner.getRight();
child.setRight(scanner.getLeft());
scanner.setLeft(child);
}
}
// End: DSW algorithm

// Given node with data, insert into tree.
insert(n) {
let current = this.root;
while (current) {
if (n.getData() === current.getData()) {
return;
}

if (!current.getLeft() && n.getData() < current.getData()) {
current.setLeft(n);
this.size++;
return;
}

if (!current.getRight() && n.getData() > current.getData()) {
current.setRight(n);
this.size++;
return;
}

if (current.getLeft()) {
current = current.getLeft();
}

if (current.getRight()) {
current = current.getRight();
}
}
}

getMinimum(n) {
while (n.getLeft()) {
n = n.getLeft();
}

return n;
}

// Given data, deletes node with matching data, and re-sort.
delete(n, d) {
let parent = null;
let current = n;

while (current && current.getData() !== d) {
parent = current;

if (d < current.getData()) {
current = current.getLeft();
} else {
current = current.getRight();
}
}

if (current === null) {
return n;
}

if (current.getLeft() === null && current.getRight() === null) {
if (current !== n) {
if (parent.getLeft() === current) {
parent.setLeft(null);
this.size--;
} else {
parent.setRight(null);
}
} else {
n = null;
this.size--;
}
} else if (current.getLeft() && current.getRight()) {
const successor = this.getMinimum(current.getRight());
const sData = successor.getData();
this.delete(n, sData);
current.setData(sData);
} else {
let child;

if (current.getLeft()) {
child = current.getLeft();
} else {
child = current.getRight();
}

if (current !== n) {
if (current === parent.getLeft()) {
parent.setLeft(child);
} else {
parent.setRight(child);
}
} else {
n = child;
}
}

return n;
}

// Given data, retun the node.
get(d) {
let current = this.root;
while (current) {
if (current.getData() === d) {
return current;
}

if (d < current.getData()) {
current = current.getLeft();
} else if (d > current.getData()) {
current = current.getRight();
}
}
}

// Get height of tree or sub-tree.
getHeight(n) {
if (!n) {
return 0;
}

const leftHeight = this.getHeight(n.getLeft());
const rightHeight = this.getHeight(n.getRight());

if (leftHeight > rightHeight) {
return leftHeight + 1;
}

return rightHeight + 1;
}

// Walk the tree starting at given node, and run callback for each node.
walk(n, cb) {
if (!n) {
return;
}

this.walk(n.getLeft(), cb);

cb(n);

this.walk(n.getRight(), cb);
}

// Recurse the tree and verify whether it satisfies the constraints required
// for a BST. (Every node on the left subtree has to be smaller than the
// current node, and every node on the right subtree has to be larger than
// the current node.)
isBST(n, min, max) {
if (!n) {
return true;
}

if (n.getData() < min || n.getData() > max) {
return false;
}

return (
this.isBST(n.getLeft(), min, n.getData() - 1) &&
this.isBST(n.getRight(), n.getData() + 1, max)
);
}
}
32 changes: 32 additions & 0 deletions lists-and-trees/test/bst-node.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/* eslint-disable prefer-arrow-callback */

import { expect } from "chai";
import Node from "../bst-node";

describe("BinaryTree Node class", function () {
let node;

beforeEach(function () {
node = new Node(10);
});

it("should set and get data correctly", function () {
node.setData(20);

expect(node.getData()).to.equal(20);
});

it("should set and get left node correctly", function () {
const leftNode = new Node(5);
node.setLeft(leftNode);

expect(node.getLeft()).to.equal(leftNode);
});

it("should set and get right node correctly", function () {
const rightNode = new Node(15);
node.setRight(rightNode);

expect(node.getRight()).to.equal(rightNode);
});
});
44 changes: 44 additions & 0 deletions lists-and-trees/test/bst.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/* eslint-disable prefer-arrow-callback */

import { expect } from "chai";
import Node from "../bst-node";
import BinaryTree from "../bst";

describe("BinaryTree class", function () {
it("should insert nodes correctly", function () {
const tree = new BinaryTree(new Node(10));
tree.insert(new Node(5));
tree.insert(new Node(15));

expect(tree.getSize()).to.equal(3);
});

it("should delete nodes correctly", function () {
const tree = new BinaryTree(new Node(10));
tree.insert(new Node(5));
tree.insert(new Node(15));

tree.delete(tree.root, 5);

expect(tree.getSize()).to.equal(2);
expect(tree.get(5)).to.be.undefined;
});

it("should return correct node for given data", function () {
const tree = new BinaryTree(new Node(10));
tree.insert(new Node(5));
tree.insert(new Node(15));

const node = tree.get(5);

expect(node.getData()).to.equal(5);
});

it("should verify whether it satisfies BST constraints", function () {
const tree = new BinaryTree(new Node(10));
tree.insert(new Node(5));
tree.insert(new Node(15));

expect(tree.isBST(tree.root, -Infinity, Infinity)).to.be.true;
});
});

0 comments on commit f9f98aa

Please sign in to comment.