In this lab you will explore using Breadth First Search and Depth First Search on a tree structure.
With your partner, have one of you walk through the code in tree.h & tree.c. You should be able to answer the following questions:
- What is the purpose of the struct?
- What is the difference between addNode and addNodeBreadthFirst?
- Why would we need to remove
\n
inread_file_into_tree
? - How are we adding nodes to a tree? Given the file letters.txt, how does the tree look like?
The other partner should walk through printer.c. You should be able to answer the following questions:
- Why would be need a queue for a breadth first search? (it may help to draw it out)
- Would we need a queue for the Depth First Prints? Why or why not?
Looking at letters.txt, we have A-G, in order. We can use this to create a tree structure, and we will be focusing on a breadth first creation of the tree, by using the letters.txt file.
flowchart TD
A --> B
A --> C
B --> D
B --> E
C --> F
C --> G
Breadth first search is a search algorithm that starts at the root node, and explores all of the neighbor nodes at the present depth prior to moving on to the nodes at the next depth level. Printing the letters in breadth first order would be: A, B, C, D, E, F, G
. This is especially true as the order we added nodes was in a breadth first manner for this particular tree.
Preorder is a depth first search, where the root is visited first, then the left subtree, then the right subtree.
Printing the letters in preorder would be: A, B, D, E, C, F, G
. Take a moment to draw out the order
of the visits, and how this would look on the tree.
Postorder is a depth first search, where the left subtree is visited first, then the right subtree, then the root.
Postorder would be: D, E, B, F, G, C, A
. Take a moment to draw out the order based on the tree node traversal.
Inorder is a depth first search, where the left subtree is visited first, then the root, then the right subtree.
Inorder would be: D, B, E, A, F, C, G
. Take a moment to draw out the order based on the tree node traversal.
In printer.c, you will find a function print_bfs
that prints the tree in a breadth first manner. Your task is to write the print_dfs_preorder
, print_dfs_postorder
, and print_dfs_inorder
functions. These functions are much simpler than the print_bfs
function. Also, they are recursive functions. It is recommended you get one of the functions working first, as the other other two are very similar with only a change on where you put the print statement.
To test your breadth first print, you can run the following commands:
make
./dfs_print.out letters.txt breadthfirst
To test the other print types, replace breadthfirst with preorder, postorder, or inorder.
Using the names in gnome_names.txt[2], draw out the tree structure, each of the types of prints. Then run what you think the output would be against the output of your program. This file contains 10 names, so you would end up with an unbalanced tree.
In programming, we often need to test our code with larger files. In this case, we have a file elf_names.txt that contains 40 names. While this file isn't as large as some of the files we deal with in the real world, it is large to make calculating the DFS and BFS prints by hand difficult.
We have provided three test files:
- elf_names_preorder.txt
- elf_names_postorder.txt
- elf_names_inorder.txt
The names are as they sound, the preorder, postorder, and inorder prints of the tree from elf_names.txt. In order to learn some of the tools we can use to test our code, we will be using the diff
command. Run your code with the elf_names.txt file, with the following commands
./dfs_print.out elf_names.txt preorder > my_preorder.txt
diff elf_names_preorder.txt my_preorder.txt
By default if the files are exactly the same, you will get no output. If there is a difference, you will get a line by line comparison of the two files. You can add a flag to tell it when the files are exactly the same, by adding the -s flag.
diff -s elf_names_preorder.txt my_preorder.txt
Try that with each of the ordering types.
With your partners, discuss the following questions:
- What is the difference between a breadth first search and a depth first search?
- What tasks are suited for a breadth first search?
- What tasks are suited for a depth first search?
- Can you come up with everyday examples of inorder, postorder, and preorder? (hint, look at your calculator)
Also for the tree:
- Define a Binary Search Tree. Write out the steps to add a node to a Binary Search Tree, and use a gnomes_names.txt file to draw out the tree that is a BST format.
- How many steps is needed to get to
Canonsbass
as compared to just looking line by line in the file?
- How many steps is needed to get to
- Discuss how you would implement a Binary Search Tree (BST) using the tree structure we have created.
- You may want to look at addNode() in tree.c to see how that can be modified to add nodes to a BST.
- If you have time, modify the add node to add as one would in a BST node.
strcmp
can give you the high/low for each node.
In this lab, we have learned about the different types of search algorithms, and how they can be used to traverse a tree. We have also learned about the different types of tree traversals, and how they can be used to print out the tree in different orders.
We will come back to the traversal types when we explore graphs, which are a more complex data structure than trees but the fundamentals are the same. Searching is used throughout computer science, and knowing your data and which search is best often helps with the efficiency of your program. For a Binary Search Tree (your homework), that is almost exclusively depth first traversal of your code, as you always know "which direction" to traverse until you find the node you are looking for.
- Ordering Differences of DFS
- BFS vs DFS Binary Tree
- Difference Between BFS and DFS
- DFS Traversal of a Tree Using Recursion
- Tree Traversal BFS and DFS
- Visual BST
- GDB Cheat Sheet
- GNU Make Manual
You will notice for this lab, we added a Makefile. A Makefile is a file that contains a set of instructions for the make program to execute. The make program is a program that is used to automate the build process of a program. In this case, we are using it to automate the compilation of our program. This helps when you have multiple files, and you want to compile them all at once. If you have used gradle or maven in java, this is similar to those tools (arguably those tools are inspired by make
).
If you view the file, you will see comments on each line for what it does. Inside of linux, you can simply type "make" in the same
directory as the Makefile, and it will follow those rules. You can use make clean
to remove the executable and object files. Right now the make files seems simple, but we will cover it a bit more in depth as we will have a few assignments in the future
that require more files.
[2]: Names were generated by Fantasy Name Generator: https://www.fantasynamegenerators.com/gnome-names.php