Skip to content

Commit 2ebd781

Browse files
committed
Added solution to ch4-11 and associated tests.
1 parent 8cf0feb commit 2ebd781

File tree

2 files changed

+225
-0
lines changed

2 files changed

+225
-0
lines changed

src/chapter4/ch4-q11.js

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
'use strict';
2+
3+
class Node {
4+
constructor(value, parent) {
5+
this.left = this.right = null;
6+
this.parent = parent || null;
7+
this.val = value;
8+
this.size = 1; // including itself
9+
}
10+
}
11+
12+
/**
13+
* RandomNode routine first gets a random number between 1 and the number of
14+
* elements in the tree. Every node in the tree keeps track of the size of the
15+
* subtree that is it a part of (number of nodes in left and right subtrees
16+
* as well as itself). Using these counts we can go through the tree and find
17+
* the node whose index would match that random number if the trees values
18+
* where considered in order.
19+
*
20+
* If the random number is:
21+
* 1. smaller than the nodes left subtree size then go left.
22+
* 2. equal to the left subtree size + 1 then return current node.
23+
* 3. otherwise go right and subtract the left subtree size from the random
24+
* number as we have already skipped over that many values.
25+
*
26+
* N = |tree|
27+
* Time: insert O(lg N), delete O(lg N), find O(lg N), randomNode O(lg N) - this
28+
* assumes a balanced tree, otherwise all of these would be O(N) in the worst case
29+
* Additional space: insert O(N) - to hold values and keep track of subtree sizes
30+
* delete O(lg N), find O(1), randomNode O(1)
31+
*/
32+
export class RandomBinarySearchTree {
33+
constructor() {
34+
this.root = null;
35+
}
36+
37+
insert(value) {
38+
let node = this,
39+
branch = 'root';
40+
while (node[branch]) {
41+
node = node[branch];
42+
++node.size;
43+
branch = value < node.val ? 'left' : 'right';
44+
}
45+
node[branch] = new Node(value, node);
46+
}
47+
48+
delete(value) {
49+
return this._deleteRecursive(this.root, 'root', value);
50+
}
51+
52+
_deleteRecursive(node, parentBranch, value) {
53+
if (node) {
54+
if (node.val === value) {
55+
if (!node.left && !node.right) {
56+
node.parent[parentBranch] = null;
57+
return true;
58+
}
59+
else if (node.left && !node.right) {
60+
node.parent[parentBranch] = node.left;
61+
return true;
62+
}
63+
else if (!node.left && node.right) {
64+
node.parent[parentBranch] = node.right;
65+
return true;
66+
}
67+
else {
68+
let minNode = node.right;
69+
while (minNode.left) {
70+
minNode = minNode.left;
71+
}
72+
node.val = minNode.val;
73+
--node.size;
74+
return this._deleteRecursive(node.right, 'right', minNode.val);
75+
}
76+
}
77+
else {
78+
let branch = value < node.val ? 'left' : 'right';
79+
if (this._deleteRecursive(node[branch], branch, value)) {
80+
--node.size;
81+
return true;
82+
}
83+
}
84+
}
85+
86+
return false;
87+
}
88+
89+
find(value) {
90+
let node = this.root,
91+
branch;
92+
while (node) {
93+
if (node.val === value) {
94+
return node;
95+
}
96+
branch = value < node.val ? 'left' : 'right';
97+
node = node[branch];
98+
}
99+
return undefined;
100+
}
101+
102+
randomNode() {
103+
if (!this.root) {
104+
return undefined;
105+
}
106+
107+
let idx = Math.ceil(Math.random() * this.root.size),
108+
node = this.root;
109+
while (idx > 0) {
110+
if (node.left) {
111+
if (idx === node.left.size + 1) {
112+
return node;
113+
}
114+
else if (idx <= node.left.size) {
115+
node = node.left;
116+
}
117+
else if (node.right) {
118+
idx -= node.left.size + 1;
119+
node = node.right;
120+
}
121+
}
122+
else {
123+
if (idx <= 1) {
124+
return node;
125+
}
126+
else if (node.right) {
127+
--idx;
128+
node = node.right;
129+
}
130+
}
131+
}
132+
133+
throw new Error('Should never reach this code, this is just an assertion that we dont');
134+
}
135+
}

src/chapter4/ch4-q11.spec.js

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { expect } from 'chai';
2+
import * as classes from './ch4-q11';
3+
4+
function runSampling(values, obj, samples) {
5+
for (let i = 0; i < samples; ++i) {
6+
let val = obj.randomNode().val;
7+
values[val - 1] = true;
8+
}
9+
}
10+
11+
for (let key in classes) {
12+
let Class = classes[key];
13+
14+
describe('ch4-q11: ' + key, function() {
15+
16+
beforeEach(function() {
17+
this.obj = new Class();
18+
});
19+
20+
it('returns single number when only one item in tree', function() {
21+
this.obj.insert(1);
22+
expect(this.obj.randomNode().val).to.equal(1);
23+
expect(this.obj.randomNode().val).to.equal(1);
24+
expect(this.obj.randomNode().val).to.equal(1);
25+
});
26+
27+
[
28+
{
29+
desc: 'balanced',
30+
values: [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15]
31+
},
32+
{
33+
desc: 'left imbalanced',
34+
values: [15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
35+
},
36+
{
37+
desc: 'right imbalanced',
38+
values: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
39+
},
40+
{
41+
desc: 'random',
42+
values: [3, 15, 2, 1, 8, 4, 5, 7, 6, 14, 13, 12, 10, 11, 9]
43+
}
44+
].forEach(context => {
45+
46+
describe(`with ${context.desc} tree`, function () {
47+
48+
beforeEach(function() {
49+
context.values.forEach(v => this.obj.insert(v));
50+
});
51+
52+
it('returns random number in range', function() {
53+
expect(this.obj.randomNode().val).to.be.within(1, 15);
54+
});
55+
56+
it('returns all numbers in range over 1000 calls', function() {
57+
let gotValue = (new Array(15)).fill(false);
58+
runSampling(gotValue, this.obj, 1000);
59+
expect(gotValue.every(v => v)).to.be.true;
60+
});
61+
62+
it('returns valid numbers in range over 1000 calls where deletions are done', function() {
63+
let pickedValues;
64+
65+
this.obj.delete(1);
66+
pickedValues = (new Array(15)).fill(false);
67+
runSampling(pickedValues, this.obj, 1000);
68+
expect(pickedValues).to.eql([false, true, true, true, true, true, true,
69+
true, true, true, true, true, true, true, true]);
70+
71+
this.obj.delete(8);
72+
pickedValues = (new Array(15)).fill(false);
73+
runSampling(pickedValues, this.obj, 1000);
74+
expect(pickedValues).to.eql([false, true, true, true, true, true, true,
75+
false, true, true, true, true, true, true, true]);
76+
77+
this.obj.delete(12);
78+
pickedValues = (new Array(15)).fill(false);
79+
runSampling(pickedValues, this.obj, 1000);
80+
expect(pickedValues).to.eql([false, true, true, true, true, true, true,
81+
false, true, true, true, false, true, true, true]);
82+
});
83+
84+
});
85+
86+
});
87+
88+
});
89+
90+
}

0 commit comments

Comments
 (0)