Reputation: 397
My tree class:
public class BT<E>{
E value;
BT<E> left, right;
public BT(E value)
{
this.value=value;
}
public BT (E value, BT left, BT right)
{
this.value = value;
this.left = left;
this.right = right;
}
How would I go about returning a random node from a tree after I have generated a tree? I know the depth and the number of nodes for every tree I generate.
Upvotes: 9
Views: 6184
Reputation: 24954
The problem is also from book <Cracking the coding interview 6th>
, at section: IX Interview Questions
| 4. Trees and graphs
| Question 4.11
.
(It's a great book by Gayle Laakmann McDowell
, a software engineer from Google, who has interviewed a lot people. )
(A) Make decision on random number, with subtree size recorded.
Logic:
Impl binary tree node.
Impl binary tree, better be a BST.
BSTNode getRandomNode(BSTNode current, int rand)
Compare rand with subtree size:
Node<T> left = current.getLeft(); // left,
int leftSize = (left == null ? 0 : left.getSize()); // get size of left subtree,
if (rand == leftSize) return current; // current is chosen,
if (rand < leftSize) return randomNode(current.getLeft(), rand); // call on left subtree recursively,
else return randomNode(current.getRight(), rand - leftSize - 1); // call on right subtree recursively,
[0, size)
.getRandomNode(tree.getRoot(), rand)
, and get random node.Complexity:
O(h)
O(1)
, (method stack use O(h)
) Where:
h
is hight of tree.(B) Put into list, access via random index.
Logic:
[0, size)
, then get node at given index.Complexity:
Time: O(1)
(If the tree is static)
Space: O(n)
Where:
n
is the size of tree.Tips:
O(n)
.O(1)
if there are empty space left at end, but on array resize, it's O(n)
.Compare 2 algorithms:
A
uses less memory, and runs faster. The winner for sure.B
, it could be faster when loopup, after the array is built.(Following is an implementation in Java
using algorithm A
mentioned above, with test case.)
RandomBST.java
import java.util.concurrent.ThreadLocalRandom;
/**
* Random BST impl.
*
* @param <T>
* @author eric
* @date 2/10/19 5:02 PM
*/
public class RandomBST<T extends Comparable> extends BST<T> implements RandomBSTDesign<T> {
@Override
public void add(T v) {
root = add((RandomBSTNode<T>) root, v, (RandomBSTNode<T>) root);
}
/**
* Add to a subtree start from given node.
*
* @param current root of a subtree to add node to,
* @param currentParent parent of current,
* @param v
* @return the new root of subtree,
*/
protected RandomBSTNode<T> add(RandomBSTNode<T> current, T v, RandomBSTNode<T> currentParent) {
if (current == null) { // subtree is empty,
RandomBSTNode<T> newNode = new RandomBSTNode<>(v, currentParent); // create new node,
newNode.incSizeOfParents(); // increase size of parents,
size++;
return newNode;
}
// compare,
int compareFlag = v.compareTo(current.value);
// check this or subtree,
if (compareFlag < 0) { // smaller, go to left subtree,
current.left = add(current.getLeft(), v, current);
} else if (compareFlag > 0) { // larger, go to right subtree,
current.right = add(current.getRight(), v, current);
} else { // equals, ignore it,
}
return current;
}
@Override
protected RandomBSTNode<T> delete(BSTNode<T> current, T v) {
return delete((RandomBSTNode<T>) current, v); // cast to subtype to call another method,
}
/**
* Delete given value in subtree, if any.
*
* @param current
* @param v
* @return new root of the subtree
*/
protected RandomBSTNode<T> delete(RandomBSTNode<T> current, T v) {
if (current == null) return null;
// compare,
int compareFlag = v.compareTo(current.value);
// check this or subtree,
if (compareFlag < 0) { // smaller, go to left subtree,
current.left = delete(current.left, v);
} else if (compareFlag > 0) { // larger, go to right subtree,
current.right = delete(current.right, v);
} else { // equals, delete it,
// no child,
if (current.left == null && current.right == null) {
current.decSizeOfParents(); // decrease size of parents,
size--;
return null;
}
// one child - right,
if (current.left == null) {
current.decSizeOfParents(); // decrease size of parents,
size--;
RandomBSTNode<T> randomRight = current.getRight(); // cast to subtype,
randomRight.parent = current.parent;
return randomRight;
}
// one child - left,
if (current.right == null) {
current.decSizeOfParents(); // decrease size of parents,
size--;
RandomBSTNode<T> randomLeft = (RandomBSTNode<T>) current.left; // cast to subtype,
randomLeft.parent = current.parent;
return randomLeft;
}
// 2 child,
T smallestInRight = findSmallestValue(current.right);
current.value = smallestInRight; // replace current with smallest in right subtree,
current.right = delete(current.right, smallestInRight); // delete original smallest value in right subtree,
}
return current;
}
@Override
public RandomBSTNode<T> getRoot() {
return (RandomBSTNode<T>) root;
}
@Override
public T randomValue() {
if (size == 0) return null; // empty tree,
int rand = ThreadLocalRandom.current().nextInt(size); // random value,
RandomBSTNode rnode = randomNode(getRoot(), rand); // get random node,
// System.out.printf("rand = %d, random node: %s\n", rand, rnode);
return rnode == null ? null : (T) rnode.value;
}
protected RandomBSTNode randomNode(RandomBSTNode<T> current, int rand) {
RandomBSTNode<T> left = current.getLeft(); // left,
int leftSize = (left == null ? 0 : left.getSize()); // get size of left subtree,
if (rand == leftSize) return current; // current is chosen,
if (rand < leftSize) return randomNode(current.getLeft(), rand); // call on left subtree recursively,
else return randomNode(current.getRight(), rand - leftSize - 1); // call on right subtree recursively,
}
/**
* Random BST node impl.
*
* @param <T>
*/
static class RandomBSTNode<T extends Comparable> extends BSTNode<T> implements RandomBSTNodeDesign<T> {
protected int size; // size of subtree which has this node as root,
protected RandomBSTNode<T> parent; // parent of this node, or null if this is root,
public RandomBSTNode(T value, RandomBSTNode<T> parent) {
this(value, 1, parent); // on creation, got size 1,
}
public RandomBSTNode(T value, int size, RandomBSTNode<T> parent) {
super(value);
this.size = size;
this.parent = parent;
}
@Override
public int incSize() {
return ++size;
}
@Override
public int decSize() {
return --size;
}
@Override
public void incSizeOfParents() {
RandomBSTNode<T> pn = parent;
while (pn != null) {
pn.incSize();
pn = pn.parent;
}
}
@Override
public void decSizeOfParents() {
RandomBSTNode<T> pn = parent;
while (pn != null) {
pn.decSize();
pn = pn.parent;
}
}
@Override
public boolean isRoot() {
return parent == null;
}
@Override
public RandomBSTNode<T> getLeft() {
return (RandomBSTNode<T>) left;
}
@Override
public RandomBSTNode<T> getRight() {
return (RandomBSTNode<T>) right;
}
@Override
public int getSize() {
return size;
}
@Override
public RandomBSTNode getParent() {
return parent;
}
@Override
protected void appendMoreToString(StringBuilder sb) {
sb.append(", size = ").append(size); // size,
sb.append(", parent value = ").append(parent == null ? null : parent.getValue()); // parent,
}
/*------ unsupported methods ------*/
@Override
void setLeft(BSTNode<T> left) {
throw new UnsupportedOperationException("Should not set subtree directly, because size is difficult to maintain.");
}
@Override
void setRight(BSTNode<T> right) {
throw new UnsupportedOperationException("Should not set subtree directly, because size is difficult to maintain.");
}
}
}
RandomBSTTest.java (partly)
(unit test, via TestNG
)
import org.testng.Assert;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.Consumer;
/**
* RandomBST test.
*
* @author eric
* @date 2/10/19 5:50 PM
*/
public class RandomBSTTest {
private int n = 100;
private int avgRound = (1 << 18); // avg round for each node, when get random value,
private RandomBST<Integer> mbst; // minimal BST,
private RandomBST<Integer> ebst; // empty BST,
@BeforeMethod
public void init() {
// init minimal BST,
mbst = CreateMinimalBST.createRandomFromNumRange(0, n);
// init empty BST,
ebst = new RandomBST<>();
}
// other non-random related test cases are removed,
// randomValue()
@Test
public void testRandomValue() {
int round = n * avgRound; // total round,
System.out.printf("tree size: %d, total round: %d, result:\n", n, round);
Map<Integer, Integer> freqMap = new LinkedHashMap<>();
// init map,
for (int i = 0; i < n; i++) {
freqMap.put(i, 0);
}
// get random value & count frequency,
for (int i = 0; i < round; i++) {
int rv = mbst.randomValue();
freqMap.put(rv, freqMap.get(rv) + 1);
}
double avgPercentage = 100 / n; // average percentage,
// print result,
for (int x : freqMap.keySet()) {
int freq = freqMap.get(x);
double percentage = freq * 100.0 / round;
double diffPercentage = percentage - avgPercentage;
System.out.printf("\trandom value: %2d, chosen count: %d, percentage: %.4f %%, differ: %.4f %%\n", x, freq, percentage, diffPercentage);
Assert.assertTrue(Math.abs((freq - avgRound) * 1.0 / avgRound) <= 0.01); // differ <= 0.01, means <= 1 / 100,
}
// empty tree,
Assert.assertNull(ebst.randomValue());
}
}
(All test cases would pass.)
Output of test method testRandomValue()
:
tree size: 100, total round: 26214400, result: random value: 0, chosen count: 262162, percentage: 1.0001 %, differ: 0.0001 % random value: 1, chosen count: 262166, percentage: 1.0001 %, differ: 0.0001 % random value: 2, chosen count: 262897, percentage: 1.0029 %, differ: 0.0029 % random value: 3, chosen count: 261887, percentage: 0.9990 %, differ: -0.0010 % random value: 4, chosen count: 262428, percentage: 1.0011 %, differ: 0.0011 % random value: 5, chosen count: 262094, percentage: 0.9998 %, differ: -0.0002 % random value: 6, chosen count: 261662, percentage: 0.9982 %, differ: -0.0018 % random value: 7, chosen count: 262034, percentage: 0.9996 %, differ: -0.0004 % random value: 8, chosen count: 261218, percentage: 0.9965 %, differ: -0.0035 % random value: 9, chosen count: 262054, percentage: 0.9997 %, differ: -0.0003 % random value: 10, chosen count: 261821, percentage: 0.9988 %, differ: -0.0012 % random value: 11, chosen count: 262401, percentage: 1.0010 %, differ: 0.0010 % random value: 12, chosen count: 262056, percentage: 0.9997 %, differ: -0.0003 % random value: 13, chosen count: 262119, percentage: 0.9999 %, differ: -0.0001 % random value: 14, chosen count: 261878, percentage: 0.9990 %, differ: -0.0010 % random value: 15, chosen count: 262191, percentage: 1.0002 %, differ: 0.0002 % random value: 16, chosen count: 261224, percentage: 0.9965 %, differ: -0.0035 % random value: 17, chosen count: 262471, percentage: 1.0012 %, differ: 0.0012 % random value: 18, chosen count: 261906, percentage: 0.9991 %, differ: -0.0009 % random value: 19, chosen count: 262752, percentage: 1.0023 %, differ: 0.0023 % random value: 20, chosen count: 262762, percentage: 1.0024 %, differ: 0.0024 % random value: 21, chosen count: 262245, percentage: 1.0004 %, differ: 0.0004 % random value: 22, chosen count: 261942, percentage: 0.9992 %, differ: -0.0008 % random value: 23, chosen count: 262339, percentage: 1.0007 %, differ: 0.0007 % random value: 24, chosen count: 262266, percentage: 1.0005 %, differ: 0.0005 % random value: 25, chosen count: 261584, percentage: 0.9979 %, differ: -0.0021 % random value: 26, chosen count: 263036, percentage: 1.0034 %, differ: 0.0034 % random value: 27, chosen count: 261412, percentage: 0.9972 %, differ: -0.0028 % random value: 28, chosen count: 262305, percentage: 1.0006 %, differ: 0.0006 % random value: 29, chosen count: 261236, percentage: 0.9965 %, differ: -0.0035 % random value: 30, chosen count: 261681, percentage: 0.9982 %, differ: -0.0018 % random value: 31, chosen count: 262829, percentage: 1.0026 %, differ: 0.0026 % random value: 32, chosen count: 262411, percentage: 1.0010 %, differ: 0.0010 % random value: 33, chosen count: 263766, percentage: 1.0062 %, differ: 0.0062 % random value: 34, chosen count: 262448, percentage: 1.0012 %, differ: 0.0012 % random value: 35, chosen count: 262074, percentage: 0.9997 %, differ: -0.0003 % random value: 36, chosen count: 261986, percentage: 0.9994 %, differ: -0.0006 % random value: 37, chosen count: 261971, percentage: 0.9993 %, differ: -0.0007 % random value: 38, chosen count: 262403, percentage: 1.0010 %, differ: 0.0010 % random value: 39, chosen count: 261510, percentage: 0.9976 %, differ: -0.0024 % random value: 40, chosen count: 262020, percentage: 0.9995 %, differ: -0.0005 % random value: 41, chosen count: 262410, percentage: 1.0010 %, differ: 0.0010 % random value: 42, chosen count: 261910, percentage: 0.9991 %, differ: -0.0009 % random value: 43, chosen count: 262240, percentage: 1.0004 %, differ: 0.0004 % random value: 44, chosen count: 261923, percentage: 0.9992 %, differ: -0.0008 % random value: 45, chosen count: 261856, percentage: 0.9989 %, differ: -0.0011 % random value: 46, chosen count: 262708, percentage: 1.0022 %, differ: 0.0022 % random value: 47, chosen count: 262695, percentage: 1.0021 %, differ: 0.0021 % random value: 48, chosen count: 262882, percentage: 1.0028 %, differ: 0.0028 % random value: 49, chosen count: 262596, percentage: 1.0017 %, differ: 0.0017 % random value: 50, chosen count: 262872, percentage: 1.0028 %, differ: 0.0028 % random value: 51, chosen count: 262343, percentage: 1.0008 %, differ: 0.0008 % random value: 52, chosen count: 261969, percentage: 0.9993 %, differ: -0.0007 % random value: 53, chosen count: 261301, percentage: 0.9968 %, differ: -0.0032 % random value: 54, chosen count: 262206, percentage: 1.0002 %, differ: 0.0002 % random value: 55, chosen count: 261923, percentage: 0.9992 %, differ: -0.0008 % random value: 56, chosen count: 262577, percentage: 1.0017 %, differ: 0.0017 % random value: 57, chosen count: 262308, percentage: 1.0006 %, differ: 0.0006 % random value: 58, chosen count: 262600, percentage: 1.0017 %, differ: 0.0017 % random value: 59, chosen count: 261682, percentage: 0.9982 %, differ: -0.0018 % random value: 60, chosen count: 261983, percentage: 0.9994 %, differ: -0.0006 % random value: 61, chosen count: 262450, percentage: 1.0012 %, differ: 0.0012 % random value: 62, chosen count: 263582, percentage: 1.0055 %, differ: 0.0055 % random value: 63, chosen count: 261755, percentage: 0.9985 %, differ: -0.0015 % random value: 64, chosen count: 262773, percentage: 1.0024 %, differ: 0.0024 % random value: 65, chosen count: 261581, percentage: 0.9979 %, differ: -0.0021 % random value: 66, chosen count: 262071, percentage: 0.9997 %, differ: -0.0003 % random value: 67, chosen count: 262053, percentage: 0.9997 %, differ: -0.0003 % random value: 68, chosen count: 261491, percentage: 0.9975 %, differ: -0.0025 % random value: 69, chosen count: 261840, percentage: 0.9988 %, differ: -0.0012 % random value: 70, chosen count: 261837, percentage: 0.9988 %, differ: -0.0012 % random value: 71, chosen count: 262293, percentage: 1.0006 %, differ: 0.0006 % random value: 72, chosen count: 261928, percentage: 0.9992 %, differ: -0.0008 % random value: 73, chosen count: 260978, percentage: 0.9956 %, differ: -0.0044 % random value: 74, chosen count: 262260, percentage: 1.0004 %, differ: 0.0004 % random value: 75, chosen count: 262151, percentage: 1.0000 %, differ: 0.0000 % random value: 76, chosen count: 262981, percentage: 1.0032 %, differ: 0.0032 % random value: 77, chosen count: 261602, percentage: 0.9979 %, differ: -0.0021 % random value: 78, chosen count: 261938, percentage: 0.9992 %, differ: -0.0008 % random value: 79, chosen count: 262335, percentage: 1.0007 %, differ: 0.0007 % random value: 80, chosen count: 262213, percentage: 1.0003 %, differ: 0.0003 % random value: 81, chosen count: 262336, percentage: 1.0007 %, differ: 0.0007 % random value: 82, chosen count: 262356, percentage: 1.0008 %, differ: 0.0008 % random value: 83, chosen count: 261782, percentage: 0.9986 %, differ: -0.0014 % random value: 84, chosen count: 262610, percentage: 1.0018 %, differ: 0.0018 % random value: 85, chosen count: 261772, percentage: 0.9986 %, differ: -0.0014 % random value: 86, chosen count: 261874, percentage: 0.9990 %, differ: -0.0010 % random value: 87, chosen count: 262319, percentage: 1.0007 %, differ: 0.0007 % random value: 88, chosen count: 262144, percentage: 1.0000 %, differ: 0.0000 % random value: 89, chosen count: 261808, percentage: 0.9987 %, differ: -0.0013 % random value: 90, chosen count: 261980, percentage: 0.9994 %, differ: -0.0006 % random value: 91, chosen count: 261742, percentage: 0.9985 %, differ: -0.0015 % random value: 92, chosen count: 261994, percentage: 0.9994 %, differ: -0.0006 % random value: 93, chosen count: 262365, percentage: 1.0008 %, differ: 0.0008 % random value: 94, chosen count: 261954, percentage: 0.9993 %, differ: -0.0007 % random value: 95, chosen count: 261660, percentage: 0.9982 %, differ: -0.0018 % random value: 96, chosen count: 263220, percentage: 1.0041 %, differ: 0.0041 % random value: 97, chosen count: 261049, percentage: 0.9958 %, differ: -0.0042 % random value: 98, chosen count: 262000, percentage: 0.9995 %, differ: -0.0005 % random value: 99, chosen count: 262692, percentage: 1.0021 %, differ: 0.0021 % =============================================== Default Suite Total tests run: 1, Failures: 0, Skips: 0 ===============================================
Relevant class & interface:
BST
is a simple binary tree impl.BSTNode
is node impl of BST
, contains fields: value
/ left
/ right
.
RandomBSTDesign
is interface for RandomBST
.
RandomBSTDesign$RandomBSTNodeDesign
is interface for RandomBST$RandomBSTNode
.
RandomBST
is random bst impl, extends BST
, mainly override the add() / delete() method to maintain parent
/ size
fields.
RandomBST$RandomBSTNode
is node impl for RandomBST
, extends BSTNode
, and add fields: parent
/ size
.
CreateMinimalBST
is a tool that help to build a binary search tree of minimal height.
Upvotes: 4
Reputation: 3486
The algorithms from Dennis and Jeroen are simple to implement but O(n)
. I believe I have a O(log n)
algorithm that is slightly more complicated.
Every node needs an equal chance of being selected. So at some tree T
, let LN(T)
be the number of nodes in the left tree, RN(T)
be the number of nodes in the right tree, and N(T)
be the number of total nodes, including this one (so N(T) = 1 + LN(T) + RN(T)
). Select a random number R
from 0 to N(T) - 1
. If R == 0
, return this node. If 1 <= R <= LT(N)
, recurse this method on the left subtree. Otherwise, recurse this method on the right subtree.
Untested code (assuming BT
has a .size()
method that works in O(1)
):
public BT randomNode() {
int r = new Random().nextInt(this.size());
if (r == 0) {
return this;
} else if (left != null && 1 <= r && r <= left.size()) {
return left.randomNode();
} else {
return right.randomNode();
}
}
And of course you can do things like hoist the new Random()
out of the method but the algorithm is the same.
Edit: fixed null pointer exception when left subtree was null.
Upvotes: 9
Reputation:
So use a Random Integer method and return an integer between 0 and the tree size.
Then do a breadth / depth first traversal on the tree, with a counter, that returns the node when it reaches the random integer.
Random rand = new Random();
int randomNum = rand.nextInt(treesize);
int count = -1;
Node randNode;
public static void getRandomTraversal(Node root){
count++;
if(count == randomNum)
randNode = root;
if(root.leftChild != null)
getRandomTraversal(root.leftChild);
if(root.rightChild != null)
getRandomTraversal(root.rightChild);
}
Alternatively you could remove count as being global and add it as an argument to the recursive function. Though this is not as easy with binary trees when the tree has more than one child.
Upvotes: 6
Reputation: 44448
new Random().nextInt(numberOfNodes)
)Upvotes: 3