Tree
Dissecting a Serialized Binary Tree.
Last updated
Dissecting a Serialized Binary Tree.
Last updated
We will write a seeking algorithm that takes in the file data.bin
, which contains a binary tree that has been serialized and reads the array using pre-order notation. The flag can be printed by printing the value when reading each node.
You have been provided data.bin
with the serialized array. Each element takes the following form:
The parts of the array entry are:
Index: The index of the current array entry.
c: The value at this index, a char
.
left: The index of the left child of this node.
right: The index of the right child of this node.
The flag is generated by printing the value at each index when the tree is read using pre-order notation. Please remember that indices are meant for sorting, are not part of the value, and may not be in the correct location in the binary file.
The flag will be represented in ASCII.
This challenge builds a binary tree and then reads it using pre-order notation. We must read the binary, build the tree, and then read the tree using pre-order notation.
We are told each element contains four pieces: a two-byte index, a one-byte character, a two-byte index for the left child, and a two-byte index for the right. We can use this to define the following struct:
Since the left and the right are indices and not pointers, we use values instead of pointers. We can manage the overhead of connecting the parents to the children later.
We will read the data like the rest of the challenges in this section. First, we open the file and ensure it is properly opened. Then, we initialize the structure holding our nodes and read each node.
First, we open the file. Nothing new here.
We can use a static array to hold the structure. This actually makes a lot of sense for us because we have the indices of the child nodes. Therefore, if we want to find a child, we can use a sorted copy of the array and access the child by index. This avoids having to link the children.
If you want to pre-determine the side of nodes
...
There are two ways to get the number of elements in the array:
Get the file size using ls -lh data.bin
(you can verify this is 245 bytes) and divide by the size of the struct (7 bytes). This gives us 35. We can then allocate the array using this number.
Use a preliminary count to count the number of elements, and then reset the file pointer to the beginning of the file.
Both ways are equally effective.
Now, let's read the data. We can use an infinite while loop that breaks when we no longer read any more bytes. We only need to check when we read the index since we are told each element is complete. This section is almost identical to LinkedOps, except this time we don't need to check how many bytes to read once we read the opcode
.
Once done, we must sort the array to access roots and children by index. We build a compare
function that sorts in descending order based on the index.
Then, we use qsort()
to sort the nodes.
We need to run a pre-order read on the binary. The best way to do this is to allocate a string and append the letters to the string as we read them. This way, we can print the string at the end as our flag.
A pre-order read is a recursive printing method for a binary tree that prints the current node, the left child, then the right. Our function needs the string storing our pre-order read, a node*
to the list of nodes, the index of the node we're currently reading, and then a uint*
to the number of nodes we've read thus far.
Why uint*
?
We need the state of the number to be consistent across calls, so that backtracking works properly. Therefore, we pass the depth as a pointer so we can increment it across calls. As we backtrack, it automatically decrements.
Here is the signature of the method we will write:
In this method, we need to do three things:
Append the current node's letter to the string.
Ensure we're not at a leaf node.
If applicable, recursively call the method on the left and right children in that order.
To add the current letter to the string, we can simply access by index since the string was allocated to the number of nodes. We then update the depth parameter.
Then, we check to see if we're at a leaf node. We can do this by checking whether the left and right children are both zero. If they are, we can return.
Finally, we simply do the recursive travel. We do this by checking if the left child is not null, then call the preOrder
method on the left child. Then, we do the same for the right child.
This is all the processing we need. To start the algorithm, we allocate a string, initialize the depth to zero, and call the method on the root node.
Printing the flag is just printing the string:
Running this gets the flag:
Here is the full solution: