Old version
This is the CS 112 site as it appeared on May 12, 2022.
Problem Set 7
due by 11:59 p.m. on Tuesday, April 19, 2022
- Preliminaries
- Part I
- Creating the necessary folder
- Creating the necessary file
- Problem 1: Choosing an appropriate list implementation
- Problem 2: Scaling a list of integers
- Problem 3: Working with stacks and queues
- Problem 4: Binary tree basics
- Problem 5: Tree traversal puzzles
- Problem 6: Binary search trees
- Submitting your work for Part I
- Part II
Preliminaries
In your work on this assignment, make sure to abide by the collaboration policies of the course.
If you have questions while working on this assignment, please
come to office hours, post them on Piazza, or email
cs112-staff@cs.bu.edu
.
Part I
50 points total
Creating the necessary folder
Create a subfolder called ps7
within your
cs112
folder, and put all of the files for this assignment in that
folder.
Creating the necessary file
Problems in Part I will be completed in a single PDF file. To create it, you should do the following:
-
Access the template that we have created by clicking on this link and signing into your Google account as needed.
-
When asked, click on the Make a copy button, which will save a copy of the template file to your Google Drive.
-
Select File->Rename, and change the name of the file to
ps7_partI
. -
Add your work for the problems from Part I to this file.
-
Once you have completed all of these problems, choose File->Download->PDF document, and save the PDF file in your
ps7
folder. The resulting PDF file (ps7_partI.pdf
) is the one that you will submit. See the submission guidelines at the end of Part I.
Problem 1: Choosing an appropriate list implementation
9 points total; 3 points each part; individual-only
In lecture, we considered two different implementations of the list
ADT: ArrayList
and LLList
. For each of the following applications,
decide which list implementation would be better for that particular
application. Your should consider both time efficiency and space
efficiency, and you should assume that both implementations have an
associated iterator like the LLListIterator
that we discussed in
lecture. Explain your answers.
-
A local events venue wants you to maintain its monthly list of events. The number of events is roughly the same each month. The events will be added in order by date from the beginning of the month to the end, and they will also be displayed in that order.
-
You are maintaining a list of information about runners in a marathon. Each runner is assigned a number between 1 and 3000, and you decide to use that id number as the index of the runner’s record in the list. Once the deadline for signing up for the race has passed, there are very few changes to the list. However, you need to frequently access the runner’s records during the race so that you can add the times at which they pass various mile markers. There is no guarantee about when a given runner will pass a given mile marker, and thus there is no guarantee about the order in which the records will be accessed.
-
You need to keep track of students who register for hackathons that you organize on a regular basis. For a given hackathon, you start with an empty list and add students when they register. To ensure that you have performed the necessary processing of the registrants, you regularly display the list, starting with the most recently added registrant and working backwards towards the least recently added one. The number of registrants varies significantly from one hackathon to another.
Problem 2: Scaling a list of integers
10 points total; individual-only
The following method takes an integer factor
and an instance of our
ArrayList
class from lecture that we assume contains integers, and
it creates and returns an instance of our LLList
class in which each
integer from the original list has been multiplied by the specified
factor.
public static LLList scale(int factor, ArrayList vals) { LLList scaled = new LLList(); for (int i = 0; i < vals.length(); i++) { int val = (Integer)vals.getItem(i); scaled.addItem(val*factor, i); } return scaled; }
Note that the original list is not altered, and the scaled version of the value at position i in the original list is in position i of the new list.
-
What is the running time of this algorithm as a function of the length n of the original list? Don’t forget to take into account the time efficiency of the underlying list operations,
getItem()
andaddItem()
. Use big-O notation, and explain your answer briefly. -
Rewrite this method to improve its time efficiency by improving the ways in which the method manipulates one or both of the lists. Your new method should have the same results as the original one, and it should not modify the original list in any way. Make the new method as efficient as possible. You should assume that this method is client code that does not have direct access to the fields of either object.
Important: The revised method’s memory usage should be comparable to that of the original method. This means that it should not use an additional array or an additional instance of one of our collection classes. Instead, it should limit itself to the
ArrayList
that is passed in and theLLList
that is being created.Note: In the
ps7.zip
file that you will download for Part II, we have included a file calledProblem2.java
that contains the original version of thescale
method and amain
method with some preliminary test code. You are welcome to use that file to test the correctness of your new version of the method, although we encourage you to convince yourself of its correctness before you test it in VS Code. -
What is the running time of the improved algorithm? Use big-O notation, and explain your answer briefly.
Problem 3: Working with stacks and queues
8 points total; 4 points each part; individual-only
-
Write a method
remAllStack(Stack<Object> stack, Object item)
that takes a stack and an item, and that removes all occurrences of that item from the stack. After the removals, the remaining items should still be in the same order with respect to each other. For example, if you have a stack that contains (from top to bottom) {5, 2, 7, 2, 10}, if you use the method to remove all occurrences of 2, the resulting stack should contain (from top to bottom) {5, 7, 10}.Important guidelines:
-
Your method may use either another stack or a queue to assist it. It may not use an array, linked list, or other data structure. When choosing between a stack or a queue, choose the one that leads to the more efficient implementation.
-
You should assume that the method does not have access to the internals of the collection objects, and thus you can only interact with them using the methods in the interfaces that we discussed in lecture.
-
Although you aren’t writing this method as part of a class, you should use appropriate Java syntax. The methods should be static and should not return a value.
-
-
Write a method
remAllQueue(Queue<Object> queue, Object item)
that takes a queue and an item, and that removes all occurrences of that item from the queue. After the removals, the remaining items should still be in the same order with respect to each other. For example, if you have a queue that contains (from front to rear) {5, 2, 7, 2, 10} and you use the method to remove all occurrences of 2, the resulting queue should contain (from front to rear) {5, 7, 10}. The same guidelines that we specified forremAllStack()
also apply here.
Suggestion
We have not given you a Java file for this problem, but we
strongly recommend writing a program to test your methods before
you copy them into your ps7_partI
file. All of the necessary
classes can be found in the ps7.zip
file that you will download
for Part II.
Problem 4: Binary tree basics
11 points total; individual-only
We will complete the material needed for this problem during the week of April 11.
Consider the following binary tree, in which the nodes have the specified integers as keys:

-
What is the height of this tree?
-
How many leaf nodes does it have? How many interior nodes?
-
If a preorder traversal were used to print the keys, what would the output be?
-
What would be the output of a postorder traversal?
-
What would be the output of a level-order traversal?
-
Is this tree a search tree? Explain briefly why or why not.
-
Is this tree balanced? Explain briefly why or why not.
Problem 5: Tree traversal puzzles
6 points total; 3 points each part
We will complete the material needed for this problem during the week of April 11.
-
When a binary tree of characters (which is not a binary search tree) is listed in postorder, the result is IKHAFLMBCQ. Inorder traversal gives IAKHQCFLBM. Construct the tree by editing the diagram we have provided in section 5-1 of
ps7_partI
.-
Click on the diagram and then click the Edit link that appears below the diagram.
-
Edit the diagram. It includes the necessary nodes and edges, but you will need to move them into the appropriate positions and connect them.
-
When you have completed your edits, click the Save & Close button.
-
-
When a binary tree of characters (which is not a binary search tree) is listed in preorder, the result is BAFCIDGEHJ. Inorder traversal gives FCAIBGHEJD. Construct the tree by editing the diagram that have provided in section 5-2 of
ps7_partI
.
Problem 6: Binary search trees
6 points total; 3 points each part; individual-only
We will complete the material needed for this problem during the week of April 11.
Consider the following tree, which is a binary search tree.
-
Show the tree as it will appear if 33 is inserted, followed by 60 by editing the diagram that we have provided in section 6-1 of
ps7_partI
. -
Suppose we have the original tree and that 42 is deleted and then 26 is deleted, using the algorithm from the lecture notes. Show the resulting tree by editing the diagram that we have provided in section 6-2 of
ps7_partI
.
Submitting your work for Part I
Note: There are separate instructions at the end of Part II that you should use when submitting your work for that part of the assignment.
Submit your ps7_partI.pdf
file using these steps:
-
If you still need to create a PDF file, open your
ps7_partI
file on Google Drive, choose File->Download->PDF document, and save the PDF file in yourps7
folder. -
Login to Gradescope by clicking the link in the left-hand navigation bar, and click on the box for CS 112.
-
Click on the name of the assignment (
PS 7: Part I
) in the list of assignments on Gradescope. You should see a pop-up window labeled Submit Assignment. (If you don’t see it, click the Submit or Resubmit button at the bottom of the page.) -
Choose the Submit PDF option, and then click the Select PDF button and find the PDF file that you created. Then click the Upload PDF button.
-
You should see a question outline along with thumbnails of the pages from your uploaded PDF. For each question in the outline:
- Click the title of the question.
- Click the page(s) on which your work for that question can be found.
As you do so, click on the magnifying glass icon for each page and doublecheck that the pages that you see contain the work that you want us to grade.
-
Once you have assigned pages to all of the questions in the question outline, click the Submit button in the lower-right corner of the window. You should see a box saying that your submission was successful.
Important
-
It is your responsibility to ensure that the correct version of every file is on Gradescope before the final deadline. We will not accept any file after the submission window for a given assignment has closed, so please check your submissions carefully using the steps outlined above.
-
If you are unable to access Gradescope and there is enough time to do so, wait an hour or two and then try again. If you are unable to submit and it is close to the deadline, email your homework before the deadline to
cs112-staff@cs.bu.edu
Part II
50 points total
Preparing for Part II
Begin by downloading the following zip file: ps7.zip
Unzip this archive, and you should find a folder named ps7
, and
within it the files you will need for Part II.
Keep all of the files in the ps7
folder, and open that folder in VS
Code using the File->Open Folder or File->Open menu option.
Note: VS Code will show an “Unchecked cast” warning for the
lines in the ArrayStack
and ArrayQueue
constructors that create
the array for the items
field and cast it to be of type T[]
. You
can safely ignore these warnings.
Problem 7: Improving the efficiency of our LLList
class
14 points; individual-only
If you haven’t already done so, complete the steps above to prepare for this and the remaining problems in Part II.
In our LLList
class from lecture, the worst case for the addItem
method occurs when adding an item to the end of the list. addItem
is O(n) in that case because we need to walk down the entire linked
list before we can add the new item.
As discussed in lecture, we can optimize the case of adding an item to
the end of the list if we add an extra field to LLList
objects that
stores a reference to the last node in the linked list. Here is an
example of what this modified version of LLList
would look like:
Because the new field – which we have called last
in the diagram
above – holds a reference to the last node, it can used to avoid the
need to walk down the linked list to get to the last node, and thus
we can add an item to the end of the list in O(1) steps.
In the version of LLList
provided in the ps7
folder, we have
included a field called last
in the fields of the LList
class, but that field is not yet being used or fully maintained by the
methods of the class.
Your job is to add the code needed to maintain this field, and to use it to improve the efficiency of the class.
Here are the steps that you should take:
-
Revise the second
LLList
constructor – the one that takes an array of objects – so that thelast
field is assigned a reference to the last node in the newly constructed linked list. For full credit, the code that you add to assign a value to this field should only require O(1) steps.To facilitate testing, we have added a method called
getLastItem
that can be used to obtain the item in the node to which thelast
field refers. For example, after you make the necessary changes to the second constructor, the following code:String[] letters1 = {"a", "b", "c"}; LLList list1 = new LLList(letters1); System.out.println(list1); System.out.println(list1.getLastItem());
should print:
{a, b, c} c
-
Revise the
getNode
helper method so that it takes advantage of thelast
field in cases in which it needs to get the last node in the linked list. In such cases,getNode
should not need to walk down the linked list. -
Revise the
addItem
andremoveItem
methods so that each of them:-
updates the value of the
last
field when the last node in the linked list changes; for full credit, the code that you add to update the value of this field should only require O(1) steps -
takes advantage of the
last
field when doing so would improve the efficiency of the method.
After you have made the necessary changes:
String[] letters2 = {"a", "b", "c", "d", "e"}; LLList list2 = new LLList(letters2); // Add two items to the end of the list. list2.addItem("f", 5); list2.addItem("g", 6); System.out.println(list2.getLastItem()); // Remove three items from the end of the list. list2.removeItem(6); list2.removeItem(5); list2.removeItem(4); System.out.println(list2.getLastItem());
should print the following:
g d
-
Notes:
-
Take advantage of the fact that
addItem
andremoveItem
each callgetNode
. This should allow you to reduce the amount of code that you need to add.For example, consider the
getItem
method. Because this method usesgetNode
to do most of the necessary work, we don’t need to modifygetItem
at all in order to optimize the case of getting the last item in the list. Rather, that case should already be optimized by the changes that you make togetNode
. -
In an empty list, the
last
field should refer to the dummy head node. We have included code that initializes the field accordingly in the firstLLList
constructor. The code that you add should maintain this relationship betweenlast
and the dummy head node in any empty list.
Problem 8: Palindrome tester
15 points; pair-optional
This is the only problem of the assignment that you may complete with a partner. See the rules for working with a partner on pair-optional problems for details about how this type of collaboration must be structured.
Make sure to begin by following the instructions given above under Preparing for Part II.
-
(10 points) A palindrome is a string like
"radar"
,"racecar"
, and"abba"
that reads the same in either direction. To enable longer palindromes, we can ignore spaces, punctuation, and the cases of the letters. For example:"A man, a plan, a canal, Panama!"
is a palindrome, because if we ignore spaces and punctuation and convert everything to lowercase we get
"amanaplanacanalpanama"
which is a palindrome.
In the file
Palindrome.java
that we’ve included in theps7
folder, add a static method calledisPal()
that takes aString
object as a parameter and determines if it is a palindrome, returningtrue
if it is andfalse
if it is not.A string of length 1 and an empty string should both be considered palindromes. Throw an exception for
null
values.Although we can implement solutions to this problem using recursion or an appropriately constructed loop that manipulates the
String
object directly, your method must use an instance of one or more of the following collection classes from theps7
folder:ArrayStack
LLStack
ArrayQueue
LLQueue
You must not use any other data structure, including arrays or linked lists other than the ones that are “inside” instances of the above collections. Rather, you should put individual characters from the original string into an instance of one or more of the above collections, and use those collection object(s) to determine if the string is a palindrome.
For full credit, you should:
-
Write your method so that spaces, punctuation, and the cases of the letters don’t prevent a string from being a palindrome. To put it another way, make sure that your method only considers characters in the string that are letters of the alphabet and that it ignores the cases of the letters. See our example above.
-
Make your method as efficient as possible. In particular:
-
You should perform only one iteration over the original
String
object. After that one iteration, any additional manipulations of the characters should be done using the collection object(s) that you have chosen to use. -
Because we want to avoid unnecessary scanning, you may not use any of the built-in
String
methods exceptcharAt()
andlength()
.
-
Hints:
-
When constructing the collection object(s) that you decide to use, you will need to specify the appropriate data type for the items in the collection. Because
char
values are primitives, you should use the corresponding “wrapper” class, which is calledCharacter
. -
You may also find it helpful to use the
Character.toLowerCase()
orCharacter.toUpperCase()
method. -
Remember that
char
values are essentially integers, so you can compare them just as you would compare integers.
-
(5 points) For professional programmers, writing well-formatted units tests is an extremely important part of their work. In the
main()
method ofPalindrome.java
, we’ve given you an example of what such a unit test should look like.Add five additional unit tests for your
isPal()
method to themain()
method. Your unit tests must follow the same format as our example test. In particular, the output of each of your unit tests should include:- a header that specifies the test number and a description of what is being tested
- the actual return value that you get from that test
- the expected return value
- whether the actual return value matches the expected return value.
Put each test in the context of a
try-catch
block so that you can handle any exceptions that are thrown. Print a blank line between tests.
Problem 9: Adding methods to the LinkedTree
class
21 points; individual-only
We will complete the material needed for this problem during the week of April 11.
In the file LinkedTree.java
, add code that completes the following
tasks:
-
Write a non-static
sumKeysTo(int key)
method that takes takes an integer key as its only parameter and that uses uses iteration to determine and return the sum of the keys on the path from the root node to the node with the specified key, including the key itself. Your method should take advantage of the fact that the tree is a binary search tree, and it should avoid considering subtrees that couldn’t contain the specified key. It should return 0 if the specified key is not found in the tree.Note: There are two methods in the
LinkedTree
class that can facilitate your testing of this method and the other methods that you’ll write:-
The
insertKeys()
method takes an array of integer keys, and it processes the array from left to right, adding a node for each key to the tree using theinsert()
method. (The data associated with each key is a string based on the key, although our tests will focus on just the keys.) -
The
levelOrderPrint()
method performs a level-order traversal of the tree and prints the nodes as they are visited; each level is printed on a separate line. This method doesn’t show you the precise shape of the tree or the edges between nodes, but it gives you some sense of where the nodes are found.
For example, if we run the following test code:
LinkedTree tree = new LinkedTree(); System.out.println("for an empty tree: " + tree.sumKeysTo(13)); int[] keys = {37, 26, 42, 13, 35, 56, 30, 47, 70}; tree.insertKeys(keys); System.out.println("\nfor the tree from problem 6:"); System.out.println("sum to 13 = " + tree.sumKeysTo(13)); System.out.println("sum to 56 = " + tree.sumKeysTo(56)); System.out.println("sum to 37 = " + tree.sumKeysTo(37)); System.out.println("sum to 50 = " + tree.sumKeysTo(50));
we should see:
for an empty tree: 0 for the tree from problem 6: sum to 13 = 76 sum to 56 = 135 sum to 37 = 37 sum to 50 = 0
-
-
Write two methods that together allow a client to determine the number of leaf nodes in the tree:
-
a private static method called
numLeafNodesInTree()
that takes a reference to aNode
object as its only parameter; it should use recursion to find and return the number of leaf nodes in the binary search tree or subtree whose root node is specified by the parameter. Make sure that your method correctly handles empty trees/subtrees – i.e., cases in which the value of the parameterroot
isnull
. -
a public non-static method called
numLeafNodes()
that takes no parameters and that returns the number of leaf nodes in the entire tree. This method should serve as a “wrapper” method fornumLeafNodesInTree()
. It should make the initial call to that method – passing in the root of the tree as a whole – and it should return whatever value that method returns.
For example, if we run the following test code (which again includes building the tree from problem 6):
LinkedTree tree = new LinkedTree(); System.out.println(tree.numLeafNodes()); int[] keys = {37, 26, 42, 13, 35, 56, 30, 47, 70}; tree.insertKeys(keys); System.out.println(tree.numLeafNodes());
we should see:
0 4
-
-
Write a non-static method
deleteSmallest()
that takes no parameters and that uses iteration to find and delete the node containing the smallest key in the tree; it should also return the value of the key whose node was deleted. If the tree is empty when the method is called, the method should return -1.Important: Your
deleteSmallest()
method may not call any of the otherLinkedTree
methods (including thedelete()
method), and it may not use any helper methods. Rather, this method must take all of the necessary steps on its own – including correctly handling any child that the smallest node may have.For example, if we run the following test code (which again involves building the tree from problem 6):
LinkedTree tree = new LinkedTree(); System.out.println("empty tree: " + tree.deleteSmallest()); System.out.println(); int[] keys = {37, 26, 42, 13, 35, 56, 30, 47, 70}; tree.insertKeys(keys); tree.levelOrderPrint(); System.out.println(); System.out.println("first deletion: " + tree.deleteSmallest()); tree.levelOrderPrint(); System.out.println(); System.out.println("second deletion: " + tree.deleteSmallest()); tree.levelOrderPrint();
we should see:
empty tree: -1 37 26 42 13 35 56 30 47 70 first deletion: 13 37 26 42 35 56 30 47 70 second deletion: 26 37 35 42 30 56 47 70
Note that first we delete 13, because it is the smallest key in the original tree. Next we delete 26, because it is the smallest remaining key. As a result of these deletions, 35 and 30 move up a level in the tree.
-
In the
main()
method, add at least two unit tests for each of your new methods.-
Use the same unit-test format that we specified in Problem 8.
-
We have given you the beginnings of a test for part 1 that you should complete.
-
For part 2 (
numLeafNodesInTree()
/numLeafNodes()
), your unit tests only need to callnumLeafNodes()
, since doing so will also callnumLeafNodesInTree()
.
-
Submitting your work for Part II
Note: There are separate instructions at the end of Part I that you should use when submitting your work for that part of the assignment.
You should submit only the following files:
LLList.java
Palindrome.java
LinkedTree.java
Make sure that you do not try to submit a .class
file or a file
with a ~
character at the end of its name.
Here are the steps:
-
Login to Gradescope as needed by clicking the link in the left-hand navigation bar, and then click on the box for CS 112.
-
Click on the name of the assignment (
PS 7: Part II
) in the list of assignments. You should see a pop-up window with a box labeled DRAG & DROP. (If you don’t see it, click the Submit or Resubmit button at the bottom of the page.) -
Add your files to the box labeled DRAG & DROP. You can either drag and drop the files from their folder into the box, or you can click on the box itself and browse for the files.
-
Click the Upload button.
-
You should see a box saying that your submission was successful. Click the
(x)
button to close that box. -
The Autograder will perform some tests on your files. Once it is done, check the results to ensure that the tests were passed. If one or more of the tests did not pass, the name of that test will be in red, and there should be a message describing the failure. Based on those messages, make any necessary changes. Feel free to ask a staff member for help.
Note: You will not see a complete Autograder score when you submit. That is because additional tests for at least some of the problems will be run later, after the final deadline for the submission has passed. For such problems, it is important to realize that passing all of the initial tests does not necessarily mean that you will ultimately get full credit on the problem. You should always run your own tests to convince yourself that the logic of your solutions is correct.
-
If needed, use the Resubmit button at the bottom of the page to resubmit your work. Important: Every time that you make a submission, you should submit all of the files for that Gradescope assignment, even if some of them have not changed since your last submission.
-
Near the top of the page, click on the box labeled Code. Then click on the name of each file to view its contents. Check to make sure that the files contain the code that you want us to grade.
Important
-
It is your responsibility to ensure that the correct version of every file is on Gradescope before the final deadline. We will not accept any file after the submission window for a given assignment has closed, so please check your submissions carefully using the steps outlined above.
-
If you are unable to access Gradescope and there is enough time to do so, wait an hour or two and then try again. If you are unable to submit and it is close to the deadline, email your homework before the deadline to
cs112-staff@cs.bu.edu