This second assignment focuses on a DFS search. You will accomplish a number of tasks in this assignment:
Let’s get started!
Earlier in the term you made a copy of my GitLab repository over to your account, we called this a fork. You then cloned your GitLab repository to your local computer. Your computer now has a local repository, which is linked to a remote repository, the one in your GitLab account. The name origin is used to identify this remote repository. You can see this when you look in GitKraken. You should see a “Remotes” section on the left, and an “origin” repository in there.
Since then you have added many commits to your local repository and pushed them onto your remote repository (origin). At the same time, I have created the second assignment files and added them to my GitLab repository. What you will need to do is copy these updates from my repository over to yours. This is a 2/3-step process:
Then you’ll be able to start work on the assignment.
Let’s get to it!
Before we start anything, make sure that you have NO uncommitted changes. You must either commit what you have, if it is ready to commit, or stash the changes, allowing you to retrieve them later.
To add a new upstream remote, we will use GitKraken’s interface. Hover over the “Remotes” section on the left, and a Plus button should appear to add a new remote. Click on it, and a little window will appear to let you choose the remote. Find the remote that is in the skiadas account and called algorithms-assignments. Its name will likely be skiadas/algorithms-assignments. Then as the name for this repo put upstream. Then click “Add Remote”. You should now be seeing a new remote on the left, called upstream, and you should be seeing some new branches showing up in the main screen, corresponding to that remote.
Once again the first key step is to make sure that you have NO uncommitted changes. You must either commit what you have, if it is ready to commit, or stash the changes, allowing you to retrieve them later.
We now want to merge in the changes that the upstream remote has into your remote.
In the assignment you work with a simple graph implementation and depth-first-search implementations on it. You should already be familiar with the depth-first-search material from the book.
As in the previous assignment, you will find numerous TODO comments throughout the file. They mark the locations where you need to add something to the implementations. Some times it will be one or two lines, some times it may be more. If you use Eclipse, you can see blue rectangles on the right side of the code indicating the location of all TODOs, and you can click on them to go there. You can keep track of your progress that way, so make sure to remove the TODO from the methods you have already implemented.
A standard printout of a run of the various methods can be obtained by running the main method of the DFS class (so running the DFS class as an application). To do that, you will need to first build using the corresponding gradle task, and then run the runDFS gradle task from the run group of tasks (you may need to refresh the gradle project and possibly close and reopen the gradle tasks to make it appear).
Alternatively, you can go to your terminal (or Eclipse’s console), go to the main project folder and run:
java -cp build/libs/algorithms-assignments.jar utils.DFS
If you like to write more tests, you can create test files under /src/test/java. Ask for help if you are not sure how to start those tests. You can use the :test Gradle task to run the tests.
You should make a new commit in GitKraken after completing each self-contained part, typically after each method you complete. Do not try to commit everything at once.
When you are done with the assignment, in order to “submit” it, simply push your commits via GitKraken. These will now be posted on your account in GitLab and I will be able to follow them from there.
Important: This assignment requires you to use lists at some point. You may use your DLList implementation if you like, or use ArrayList or LinkedList.
There are two files of concern in this assignment. SimpleGraph.java, and DFS.java.
The SimpleGraph class contains a simple implementation of a graph. You do not need to make any changes to it, but you should take a look at it and see what methods are provided (You can also run the :javadoc task to generate documetation for these files and look at that documentation, but the actual file is short and contains more details).
The SimpleGraph class represents vertices via integers \(0\), \(1\), …, \(n-1\). There is an order method that returns the number n of vertices. The edges of the graph are stored in adjacency list form: There is an array of DLList<Integer> lists, whose i-th entry contains a list of all the neighbors of i. So if j is contained in the list stored at edges[i], then there is an edge from i to j. The method outgoingEdges returns this list.
The class can be used for either directed graphs, using the addEdge method to add edges, or as undirected graphs, using the addBidirectionalEdge method to add edges. You will need these methods if you want to write your own example graphs.
visited array to keep track of which vertices have been visited. When a vertex is visited for the first time, it must be marked as visited.parent array that holds the parent links between a vertex and its parent. The parent array must be initialized with values equal to -1. The Arrays.fill method can help out with that. When the DFS search follows a tree edge, it also sets an entry in the parent array.run method which starts the DFS search. It will typically do two things:
dfs descent from that vertex. Repeat this until all vertices are visited.dfs method that does a search starting from a vertex. It is the heart of the search, and it is called by the run method. It has the following main elements:
dfs on this vertex.All classes must do the following things in addition to their own extra needs. The code you add must ensure these steps happen.
visited array entries to false and the parent array entries to -1 at the start of the run method.dfs method.parent array whenever a tree edge is encountered, then recursively calling dfs on the target/child vertex of that tree edge.The four inner classes are as follows:
simpleDFS demonstrates the algorithm process and it keeps track of any relevant information, printing diagnostic messages along the way. In it you will maintain a number of properties/fields:
pushOrder and popOrder fields record the order in which vertices are being put in the stack or removed from it.ComponentsDFS uses a depth-first search to collect the connected components of the graph. Uses a Set<Integer> structure to store each component, and a DLList of such structures to hold the list of all components. Its main elements are:
components DLList holding the components as they are being constructed. Depending on how you implement things, the “current component” on which vertices are to be added will be either the last or the first element on that list.run method starts a new dfs from a root vertex, a new component set must be created. You will need to use a concrete class like HashSet rather than the interface Set to actually create a set for this new component.CycleDFS tries to detect if there is a cycle in the graph. Cycles exist whenever a back edge is encountered. Such an edge connects the vertex currently being visited to one of its ancestors. It is therefore an indication that this vertex and its ancestor are connected in two different ways: Through the dfs-tree that brought us from the ancestor down to the current vertex, and this new back-edge that closes the loop. This class contains the following elements:
cycle list that starts empty, and if there is a cycle then we will store in the list the vertices in the cycle.cycle variable and return.cycle list and if it is non-empty we must return early to stop the search, since a cycle has been found.BipartiteDFS tries to determine if the graph is bipartite. It does so by assigning one of two colors to each vertex as we descend on it, and watching for conflicts. It contains the following elements:
colors array uses the numbers 1 and 2 to place the vertices in one of two “groups” based on their color.failed boolean variable keeps track of whether the coloring has failed. Whenever we look at an edge we check if the coloring has failed and return early if it has.Now on to the tasks you need to complete. Make sure you have read the previous section for an understanding of the structure of the files.
SimpleDFS class.
run method. At the beginning of the method you must initialize the visited and parent arrays, create empty pushOrder and popOrder lists, and an empty stack.dfs method.
stack, mark it as visited, and also add it to the pushOrder list.dfs for the new vertex. Don’t forget to update the parent entry before you do that.popOrder list.ComponentsDFS class. This is probably the easiest of the four classes to implement.
run method.
visited array, and create an empty components list.run method is about to visit a brand new root vertex, a new component must be created and placed in the components list.dfs method.
run method above, this would be either the first or the last element of the components list.dfs on the new vertex. Don’t forget to update the parent entry before you do so.else clause.CycleDFS class. It looks for a back-edge and uses it to construct a cycle.
run method.
visited and parent arrays, and also create an empty cycle list to hold the cycle if you find one. The parent array will contain the dfs tree link back from a vertex to its parent and we will use it to construct the cycle when we encounter it.run method.dfs method.
parent entry and recursively call dfs on the new vertex.cycle variable the vertices that comprise the cycle loop that the back-edge just closed. We can then return early from our dfs method as a cycle has been found.BipartiteDFS class. It uses the DFS search to determine if the graph is bipartite, and what the two parts of it are. It keeps “colors” for the vertices in the colors array, and also a boolean failed variable to indicate if the search has failed or not.
run method.
visited array, and a colors array filled with 0s, and also set the failed boolean to false.run method is about to start a dfs search from a root vertex, you must assign a color to that vertex. It is safe to use the color 1, since this is a new component that will not connect to the previous components (If you want a challenge, make it so that brand new root vertices are colored alternately, so 1 for the first one, 2 for the second, 1 or the third etc. You will probably need to add a new field to achieve that).dfs method.
failed variable to true and return. If the color assignments are opposite, then this back-edge is not a problem and we can simply continue our run.