// Written by Claire Cavanaugh, 2016
tlnode<T>* get_node(std::size_t n) {
/* Gets the nth node of the tree or returns an error if 0 is
* received as an argument. This was a bit of a weird thing to
* write. Essentially, say you have a tree that looks like
* this: __________64(1)__________________
* ____28(2)_______ ___53(3)____
* __13(4)___ ___19(5)__ 43(6) 0(7)
* 2(8) 1(9) 3(10) XX(11)
*
* Where the number in parenthasis is the index that node
* would be if we were using a vector-based approach, OR the
* number in the parentheses is the number assigned to the
* node starting at 1 for the root and counting up, going left
* right, top to bottom. I'll refer to that number as simply
* the node number.
*
* In the diagram our tree size is 10. Let's say we wanted to
* add another value (bringing the total size up to 11). In
* order to add a node at 11 (notated with XX's in the
* diagram), we need the address of the parent of the
* soon-to-be node (node 5, value of 19). Finding a path down
* to this node seems rather tricky given just the number 5,
* and at first this really confused me, but I figured out
* (all by myself!! I'm proud of this) that when you convert a
* node number to binary, you wind up with "directions" to the node.
*
* Let's try this out. The binary representation of 5 is
* 101. The root node is always going to be a 1 in the number
* because all binary numbers except zero start with 1
* (discounting leading zeros). The 0 tells us to go to the
* left child, then the 1 tells us to go to the left. If we
* try this with 11, our representation is 1011, and as you
* can see, following the "zero is left, one is right"
* convention, 0 1 1 leads us to node 11.
*
* So this function uses recursion to cleverly convert to
* binary in a way. We give it the node number we want, and it
* says "okay, I'm node 11. 11 % 2 is 1 so whatever the path
* to me is, it ends in 1, which makes me the right child of
* my parent, but who is my parent?. My parent's node number
* must be 11 / 2 = 5, so I'll call get_node(5) to find out
* the address of my parent.". Get_node(5) deduces that it too
* is the right child of it's parent (5 % 2 = 1), and that its
* parent is node 2 (5 / 2), and so it requests the address of
* its parent with get_node(2). get_node(2) sees that 2 % 2 is
* 0, so it's the left child of it's parent, and goes to find
* out who it's parent is with get_node(2 / 2),
* i.e. get_node(1). And hey! We know where node number 1 is, that's
* the root node! So we hand the root node over to
* get_node(2), get_node(2) uses the knowledge to return
* root->left to get_node(5), get_node(5) uses it to return
* root->left->right to get_node(11), and get_node(11) finally
* returns root->left->right->right to its caller.
*
* That is quite the journey. I think I had more fun writing
* this one function than I did the rest of the project.
*/
if (n == 0) {
throw std::logic_error("Tried to get 0th node. Is the queue empty?");
}
if (n == 1) {
return root;
}
tlnode<T>* my_parent = get_node(n / 2);
if (n % 2 == 0) {
return my_parent->left;
}
else {
return my_parent->right;
}
}
Comments