[Instructions] [Search] [Current] [Changes] [Syllabus] [Handouts] [Outlines] [Labs] [Assignments] [Examples] [Bailey Docs] [SamR Docs] [Tutorial] [API]

*The exam was reformulated to be out of eighty (80) points, making
one of the first three questions optional.*

*
Each of the following problems has an assigned point value. While
these values are partially influenced by the expected complexity
or time requirements of each problem, there is no guarantee that
higher points means harder or longer.
*

**1. Improving the Merge in Merge Sort** [20 points]

*
Herman and Helga Hacker recently learned about the legendary merge
sort algorithm and have decided that the traditional
merge subalgorithm is inefficient because it uses
additional space. They decide to replace the traditional merge with
the following algorithm (which appears as part of an
ExtendedVector class). Critique their solution.
*

/** * Merge two contiguous ordered subvectors. * pre: 0 <= start <= mid < end < size * pre: the first subvector (start ... mid) is sorted. That is, * for all i, start <= i < mid, elementAt(i) <= elementAt(i+1) * pre: the second subvector (mid+1 ... end) is sorted. That is, * for all i, mid+1 <= i < end, elementAt(i) <= elementAt(i+1) * post: the whole subvector (start ... end) is sorted and contains * the same elements as before (just in a different order). * running time: O(end-start) */ public void merge(int start, int mid, ind end) { int left=start; // The "cursor" for the left subvector int right=mid+1; // The "cursor" for the right subvector while ((left <= mid) && (right <= end)) { // Make sure the smaller of the two current elements is // now in the left subvector. if (elementAt(right) < elementAt(left)) { swap(left,right); } // Increment the counters left = left + 1; right = right + 1; } // while } // merge

There seems to be no clear rationale to their solution. While it makes sense to swap elements that are out of order, there is no guarantee that after the swapping the element swapped into the right subvector is in the right position. Consider the vector

3 4 1 5 L R

with subvectors [3 4] (sorted) and [1 5] (also sorted).
If `start`

is 0, `mid`

is 1, and `end`

is 3, then when we swap the `3`

and the `1`

, we are
left with

1 4 3 5 L R

Now, the element at `left`

is less than the element at
`right`

, so we're done (more or less). However, the
resultant list is not sorted.

There are certainly other problems with the algorithm. For example, even if the strategy were generally correct, the Hackers make no attempt to deal with different size sublists (which will happen occasionally).

Can we improve their algorithm? Possibly. We might try moving the the
`left`

cursor and not the `right`

cursor. This
will have a problem with running time because we'll, in effect, insert
the first element in the right list into the left list and then still be
left with two lists that need to be merged. We could insert the
elements from the second list one-by-one into the first list, but that's
O(n^2) time. As far as I know, no one has figured out how to do
in-place merging in O(n) time.

Note that some of you weren't satisfied with giving a non-working example and felt you had to say more. This was fine, until you went so far as to say something incorrect, in which case I felt it necessary to take off some points.

**2. Biased Notation** [20 points]

*
You may recall that the IEEE standard representation for single precision
floating point numbers uses a biased notation for the exponent. In a
biased notation, to represent the signed value N, you instead represent
the unsigned value bias+N. For the following problems, you should use
eight bits and a bias of 128.
*

*What is the appropriate bias 128 representation of -17?*

(-17) + 128 = 111 = 64 + 32 + 8 + 4 + 2 + 1

**01101111**

*What is the appropriate bias 128 representation of 123?*

123 + 128 = 251 = 128 + 64 + 32 + 16 + 8 + 2 + 1

**11111011**

*What is the two's complement representation of -17?*

Recall that in two's complement notation to negate a number you flip the bits and add 1.

17 = 16 + 1, which is represented as 00001001. Flip the bits,
giving 11101110. Add 1, giving **11101111**.

Hmmm ... that's the same as the bias-128 representation except that the leftmost bit is flipped.

*What is the two's complement representation of 123?*

123 = 64 + 32 + 16 + 8 + 2 + 1, which is represented as
**01111011**.

Hmmm ... once again it's the bias-128 representation with the leftmost bit flipped. Could I prove that?

*What does 11010001 represent in bias 128 notation?*

- 128 + 64 + 16 + 1 = 209.
- 209 - 128 =
**81**.

*What does 01101101 represent in bias 128 notation?*

- 64 + 32 + 8 + 4 + 1 = 109.
- 109 - 128 =
**-19**

*What do you get if you add -5 and 7 (in bias 128 notation)
using the standard addition strategy for unsigned binary numbers?*

Represent -5: (-5) + 128 = 123 = 64 + 32 + 16 + 8 + 2 + 1, so 01111011.

Represent 5: 5 + 128 = 133 = 128 +4 + 1, so 10000101.

Represent 7: 7 + 128 = 135 = 128 + 4 + 2 + 1, so 10000111.

Represent -7: (-7) + 128 = 121 = 64 + 32 + 16 + 8 + 1, so 01111001.

1111111-5: 01111011 7: +10000111 ---------100000010

We drop the leftmost 1 (since we're limited to eight bits),
giving **00000010**.

Hmmm ... that's 2 in standard binary notation. However, we were working in excess-128, so we need to subtract 128, giving -126. Not very encouraging. However, we are off by only 128.

*What do you get if you add 5 and 7?*

1115: 10000101 7: +10000111 ---------100001100

We drop the leftmost bit, giving **00001100**.
Hmmm ... that's 12, but we need to subtract 128, giving -116. Not
good. Perhaps we need to add 128 to the result?

*What do you get if you add 5 and -7?*

15: 10000101 -7: +01111001 ---------11111110

Yay! No overflow bit. However, the result is 254. When we subtract 128 we still get 126.

*What do you get if you add -5 and -7?*

1111 11-5: 01111011 -7: +01111001 ---------11110100

Okay, that's 244. Subtract 128 we get 116.

*What does this suggest?*

That simply adding isn't enough. But wait! Every answer was off by 128 (-126+128=2; -116+128=12, 126-128=-2; 116-128=-12). Why is that? Basically, we were adding (X+128) + (Y+128) which gives (X+Y+128)+128, so we wouldn't expect to get the right number. Instead, we need to subtract 128 to get the right answer. However, because of the way the numbers are designed, adding 128 and subtracting 128 end up doing the same thing (flipping the left bit).

So, to add two numbers represented in excess-128, add them as you would any unsigned integers and then flip the leftmost bit.

**3. Tree Traversal** [20 points]

*
Our friends the Hackers are back again. This time, they've written
a "generic" tree traversal algorithm. They claim "it can do both
breadth-first and depth-first traversal of binary trees; all you have to
do is select the right argument". With some prodding, they admit that
their method only does preorder left-to-right traversals, but stick to
their claim about "both breadth-first and depth-first". For once they may
be right. Explain their claim.
*

/** * Print all the elements of a tree, using a linear structure * to help. */ public void printNodes ( SimpleOutput out, BinaryNode root, Linear collection) { // Just in case the collection has elements, reset it collection.reset(); // Add the root. collection.add(root); // As long as the collection is not empty, remove an // element, print it out, then add its children. while (!collection.empty()) { Node next = collection.remove(); out.println(next.toString() + " "); if (next.getLeft() != null) { collection.add(next.getLeft()); } if (next.getRight() != null) { collection.add(next.getRight()); } } // while the collection is not empty. } // printNodes

*
*

This should seem very similar to our puzzle solving algorithm. If we use a stack as the linear structure, then we do a depth first search. If we use a queue as the linear structure, then we do a breadth-first search. Why? Using a stack, we complete the subtree for one child before we get to the next child (since we'll be pushing all the children of the first child before its sibling). Using a queue, we put the children of a node after its sibling.

Consider the following tree

A B C D E F G

If we pass in a queue, we get:

- collection: A
- Remove
**A**, add its children (B and C)

- Remove
- collection: B C
- Remove
**B**, add its children (D and E)

- Remove
- collection: C D E
- Remove
**C**, add its children (F and G)

- Remove
- collection: D E F G
- Remove
**D**, it has no children

- Remove
- ...

Observe that this is breadth-first, left-to-right, preorder.

If we pass in a stack, we get

- collection: A
- Remove
**A**, add its children (B and C)

- Remove
- collection: C B A
- Remove
**C**, add its children (D and E)

- Remove
- collection: E D B A
- Remove
**E**, it has no children

- Remove
- collection: D B A
- Remove
**D**, it has no children

- Remove

Hmmm ... not quite left-to-right, but certainly depth-first and preorder. So, the hackers aren't quite as talented as I suggested.

A number of you made assumptions about the addition and removal policy for the Linear structure and forgot that both stacks and queues are linear structures. This made it difficult to get this question right.

**4. List Deletion** [40 points]

*
Believe it or not, our friends the Hackers have written a relatively
elegant implementation of doubly linked lists. Unfortunately, they've
forgotten to include a remove method and have asked you
to write one. Your goal is to write and document a routine that removes
all copies of an element from a doubly-linked list.
*

*
They've told you
that they've used
rebelsky.util.BinaryNode
for their nodes and that the attributes of their class are as follows:
*

public class TheGreatestDoublyLinkedListEver { /** * The first element in the list. */ protected BinaryNode first; /** * The last element in the list. */ protected BinaryNode last; /** * The current element in the list. */ protected BinaryNode current; /** * The size of the list. */ protected int size; ... } // TheGreatestDoublyLinkedListEver

*
*

*
Write and document the remove method that removes all
copies of an element from the list. Make sure your documentation is
at least as thorough as my typical documentation. Also make sure to
indicate the running time of your algorithm.
*

/** * Remove all copies of an element, elt, from the current list. * Precondition: (none) * Postcondition: the list no longer contains any values * equal to of elt. * Postcondition: if the cursor was equal to elt, then * the cursor refers to the start of the list (unless the * list is now empty). * Postcondition: any elements not equal to elt are still in * the list and still in the same order. * * @return The number of elements deleted. */ public int remove(Object elt) { // The number of elements we've removed int removed = 0; // The element we're currently looking at BinaryNode counter = front; // The next and previous elements (not strictly necessary, // but they do make it a little easier to read). BinaryNode prev, next; // Step through each element until we reach the end. If we // identify an equal element, then we delete the element // by changing the previous reference of the next node and the // next reference of the previous node. while (counter != null) { // Should we delete the element? if (counter.getContents().equal(elt)) { next = counter.getNext(); prev = counter.getPrev(); // Is there a previous element? If so, update it. if (prev != null) { prev.setNext(next); } // Is there a subsequent element? If so, update it. if (next != null) { next.setPrev(prev); } // Have we affected the front, back, or current // element of the list? Note that we use "==" because // we actually want "points to the same node". if (counter == front) { front = next; } if (counter == back) { back = prev; } if (counter == current) { current = front; } // Update the size and number of elements we've removed removed = removed + 1; size = size - 1; } // if we should delete the element // Move on to the next element counter = counter.getNext(); } // while // Done. Return the number of elements we've removed. return removed; } // Remove(Object)

Does this work with an empty list? Certainly. The body of the while loop is never executed. Should we restrict the method to nonempty lists? Probably not, but I didn't penalize those of you who did (unless you then failed to handle empty lists appropriately).

Does this work if it clears out the list? Well, it suffices to check
the single element list (since longer lists will be reduced to the
single elelment list). At that point, `current`

,
`counter`

, `front`

, and `back`

will
all point to the element that's equal and `prev`

and
`next`

. will be null. So, when we update all of them,
everything becomes null, which is how we represent the empty list.

What is the running time? It's an O(n) algorithm because we need to step through each element of the list. Can we do better? Probably not.

What were some common mistakes? [Listed so that those of you who didn't make them don't make them next time, and so those of you who made some see that others made similar and different errors.]

- No statement of preconditions.
- No postcondition specifying the meaning of removal.
- No postcondition specifying the position of the cursor after removal (and many of you moved the cursor).
- No exceptions in a design that called for exceptions (mine didn't, since "nothing can go wrong".
- No check that it's safe to call
`prev.setNext(...)`

(requires that`prev`

be nonnull). [Similar for setting the previous reference of the next node.] - No check for special case of deleting first, last, or current elemnt.
- Forgot to specify running time.

*Each of the following questions is worth two points if answered well
(and zero if answered poorly).*

*How does Bob Cadmus's lecture on electronic imaging technology relate
to what we've learned in CS152?*

Good answers included:

- Use of pixels, which we discussed in our "machine representation section".
- Discussion of architecture of the thing, which related to our quick overview of the structure of computers from the historical classes.
- Use of arrays.
- Motivating use of algorithms (while we haven't covered any of the algorithms, it reminds us that algorithms are important).

*List three things that Vannevar Bush should be famous for.*

- Developed analog computation device in the early 1930's. Used for many important military operations.
- Wrote "As We May Think" which inspired modern computer-based hypertext systems.
- Convinced the government to fund basic research.
- Cool first name.

[Instructions] [Search] [Current] [Changes] [Syllabus] [Handouts] [Outlines] [Labs] [Assignments] [Examples] [Bailey Docs] [SamR Docs] [Tutorial] [API]

**Disclaimer** Often, these pages were created "on the fly" with little, if any, proofreading. Any or all of the information on the pages may be incorrect. Please contact me if you notice errors.

Source text last modified Tue Dec 29 09:13:41 1998.

This page generated on Tue Jan 12 11:47:39 1999 by SiteWeaver.

Contact our webmaster at rebelsky@math.grin.edu