Discussion 7: Linked Lists
Solutions
split()
Method
|
|
|
|
split()
method on the following example list:
For how many iterations will the while
loop run? To which Node
s will fast
and slow
point at the end of each of these iterations?
while
loop will run for 2 iterations.
- When we enter the loop,
fast
will point to the node containing 4 andslow
will point to the node containing 1. - At the end of the first iteration,
fast
will point to the node containing 5 andslow
will point to the node containing 4. - At the end of the second iteration,
fast
will point to the "empty" tail node andslow
will point to the node containing 2.
Explain why this method works. How is it locating the midpoint of the list?
fast
pointer advances, the slow
pointer advances only one step. Therefore, when the fast
pointer reaches the end of the list, this will coincide with the slow
pointer reaching the midpoint.
merge()
Method
What local variables will we need to instantiate in order to merge head1
and head2
into a single list?
Node
, merged
, from which we will build our merged list; merged.next
will be the head of the merged list. We also instantiate current
, which tracks the tail of the merged list so that we can attach nodes from head1
and head2
.
In the given example, should head1
or head2
become the head of the merged list? How do we determine this in general? After we choose the head, how do we decide which node to add next from the two lists as we continue building the merged list?
head1
should become the head, since head1.data <= head2.data
. We'll always add the smaller head element to the merged lists (and using head1
in the case of equality to ensure stability of the sort). We update current.next
so that it references head1
, and then advance head1 = head1.next
and current = current.next
. We repeat this process with each head1
and head2
until
we have completely traversed at least one of the lists.
In the given example, does head1
or head2
run out of nodes first? How can we attach all of the remaining nodes to the merged list?
head1
runs out of nodes first, since its largest element 4 is smaller than the largest element 5 of head2
. After our while
loop terminates, we check whether head1
or head2
is null
. There will be at least one more node in the other list (in this case, head2
) that is not yet merged, and we can attach these nodes to the merged list by setting
current.next = head2
.
What should we return at the end of the merge()
method?
merged.next
, the head of the merged list.
merge()
method in IntLinkedList.java
from the starter code. Use the provided tests to check that your implementation is correct.
|
|
|
|
(Bonus Challenge): Implement the merge operation recursively by completing the mergeRecursive()
method. What are the base and recursive cases?
head1
is empty, we return head2
, and if head2
is empty,
we return head1
. Like the iterative implementation, we first compare head1.data
with
head2.data
. If head1.data
is smaller, we recurse by calling mergeRecursive(head1.next, head2)
, then return head1
. Otherwise, we recurse with mergeRecursive(head1, head2.next)
and return head2
.
|
|
|
|
mergeSort()
Method
|
|
|
|
What are the base cases, and what should the method return?
head.data == null
or head.next.data == null
), then it is trivially sorted, so we simply return head
.
How can you use the split()
and merge()
helper methods to define the recursive step?
split()
to split the list in half and find its midpoint node. Next, we can
recursively call mergeSort()
on the left and right halves. Finally, we call
merge()
to combine the two sorted halves into a single sorted list and return the head of
the merged list.
mergeSort()
method and use the provided tests to check that your implementation is correct.
|
|
|
|
What is the space complexity of merge sort on linked lists, and why is it different from the array-based implementation?
(Hint: What dominates the space complexity in the latter?)
merge()
.