EEN218 Very Easy Test 18th April 2002, Sample Solutions

 

5.         “Tell me all about Quicksort”:

Explain how quicksort works.

 

Quicksort is naturally implemented as a recursive function, which is passed as parameters the array to be sorted and the bounds of the sort (i.e. sort this array between positions a and b). If the distance between a and b is only one, there is no work to do; otherwise quicksort does its job.

The first step is to partition the array. A 'pivot' is selected (an element of the array that we hope will turn out to be near the middle when the array is sorted, but there are no guarantees), and the elements of the array are rearranged one-by-one so that all elements less than the pivot are to the left of it, and all elements greater than the pivot are to the right of it.

This results in a slightly sorted array. Most of the elements are out of order, and clearly more sorting is required, but the important thing is that the pivot is guaranteed now to be in the right place. Even though most of the other elements are probably going to have to move, we know they won't have to move very far. Everything that is left of the pivot really belongs left of the pivot, and everything that is right of the pivot really belongs right of the pivot.

The sorting operation is completed by simply applying quicksort recursively to the two partitions of the array: everything to the left of the pivot is sorted, then everything to the right of the pivot is sorted, both operations performed as complete self-contained (but smaller) sorts.

 

What are the advantages and disadvantages of quicksort, compared to other sorting algorithms?

 

Quicksort is normally expected to be very fast, t=O(n×log2n), so that is a major advantage over all of the simpler algorithms (e.g. selection, insertion, and bubble sorts). Also, it requires no extra memory, which is an advantage over merge-sort. However, quicksort can, in unfortunate circumstances (when the original array is already sorted or nearly sorted) slow down to O(n2), which is a significant disadvantage when compared to mergesort.

 

How fast is quicksort?

 

t=O(n×log2n)


 

Why is quicksort that fast?

 

When sorting an array of n elements, the partitioning phase simply inspects each element once, and in a very simple operation moves it to the right place, so partitioning is O(n).

If the pivot did turn out to be near the middle of the partitioned array (and on average, it does) then the next step will to be to recursively sort two arrays each of about ½n elements, so there will then be two further partitioning operations each dealing with ½n items, for t=2×O(½n)=O(n).

After each of those ½n partitioning operations, there will be 4 sub-arrays, each of size approximately ¼n, requiring time time 4×¼n=n, and resulting in 8 sub-arrays each of size n, requiring time 8×n=n. This process continues in a similar fashion until the sizes of the sub-arrays are so small that there is no work to do.

How long does this take? At each stage, the expected size of the sub-arrays is halved, so how many times can you halve n before it is reduced to 1? (½)xn=1 has x=log2n as its solution. So the number of stages is log2n, and each stage is t=O(n), so the total time is O(n×log2n), if the pivots do indeed turn out to be as expected.

 

If it takes a particular implementation of quicksort 1mS (0.001 sec.) to sort an array of 1000 data items, how long would you expect it to take to sort an array of 1000000 data items? (show your calculations)

 

t=O(n×log2n) means that time taken is k×n×log2n for some unknown k. With knowledge of just one set of corresponding values for t and n, we can work out what k is: k=t¸(n×log2n).

So, given n=1000 (103) produces t=1mS (10-3), k = 10-3¸(103×10) = 10-7.

Now, when n=1000000 (106), t=k×n×log2n is a simple calculation: 10-7×106×20 = 2. We should expect it to take about 2 seconds to sort a million items.

 


 

6.         All about pointers:

There are two really important things to remember for this sort of question: Priorities of operators – [ ] denoting an array, and ( ) denoting a function, are high priority; * denoting a pointer is low priority. Use parentheses to help clarify. Secondly, you must remember that C and C++ can not tell the difference between a pointer to a single thing and a pointer to a whole array of things. * denotes both "pointer-to" and "pointer-to-array-of" equally.

Write complete and correct C++ declarations for the following things:

a)      an integer variable

            int a;

b)      an array of 300 strings

            string b[300];

c)      a pointer to a string

            string *c;

d)      a pointer to an array of 400 integers

            int *d;

Remember C/C++'s illogical system: "pointer-to" and "pointer-to-array-of" are the same thing, as far as it can tell. But you would have got partial credit for this wrong-but-logical answer:

          int (*d)[400];

e)      an array of 500 pointers to strings

            string *e[500];

or equally correct:

          string *(e[500]);

f)        a pointer to an array of 600 pointers to integers

          int **f;

or equally correct:

          int *(*f);

for the same reason as with d, but for partial credit:

          int *(*f)[600];

or

          int *((*f)[600]);

 

Say in plain but precise English what the following declarations declare:

(assuming that a struct or class called “person” has already been declared)

g)      person *one[100];

Remembering that [ ] has higher priority than *, the name "one" is most closely associated with the [100] than with the *, so one is an array of 100 pointers to integers.

h)      person **two;

Remembering the dual meaning of *, there are four things this declaration could mean:

two is a pointer to a pointer to a person object, or

two is a pointer to an array of pointers to person objects, or

two is a pointer to a pointer to an array of person objects, or

two is a pointer to an array of pointers to arrays of person objects.

i)        person *three(int x);

Remembering the priorities again, the ( ) is more binding than the *, so three is a function, not a pointer. Three is a function that takes an integer parameter and returns as its result a pointer to a person object.

 

 

If a function is needed to create a person object using input from the user, and return a pointer to that object ready to be stored in an array for later use, one might write something like this:

person *make_a_person(void)

{ person p;

  string s;

  cout << “What is the person’s first name? “;

  cin >> s;

  p.fname = s;

  cout << “and what is their last name? “;

  cin >> s;

  p.lname = s;

  return &p; }

but something is seriously wrong with that design. What?

 

This function creates a temporary person object in the variable p. Like all temporary objects, it will cease to exist as soon as the function finishes. At the end of the function, a pointer to this temporary object is returned. So the function returns as its result a pointer to an object that no longer exists.

 

I need something that behaves like an array of persons, but its size can be changed while the program is running. I don’t know how many persons I am likely to need to store: it could be an enormous number, and it could be very few, but I don’t want to waste too much memory.

Show me how to declare such a thing in C++, and how to use it properly (how to store data in it, how to access that data, and how to change its size).

Note that I am only concerned with having a flexible size and being fairly efficient with memory use. I am not concerned with error detection.

 

For efficiency (as requested by the question), we should be dealing with pointers to person objects. As arrays can't really be resized, we need to be able to discard an old array that turns out to be the wrong size and replace it with a new one. So what we need is a pointer to an array of pointers to person objects. From the first part of this question, we know that the declaration should be:

person **data;

together with

int size;

so that we can remember how big the array is at the moment.


We would need to pico an initial size, let's say 10, but it could be anything and add assignments:

int size=10;

person **data = new (person *)[size];

 

How to store something in the array: first make sure its big enough, resize if not; then simply assign:

 

          // storing value v at position p: (v is a pointer-to-person, p is an int)

          if (p>size)

             resize(p+1);

          data[p]=v;

 

Retrieving a value from the array can still check that the index is valid, but probably should not resize if it isn't:

 

          // extracting value from position p and stroing it in variable x:

          if (p<size)

             x=data[p];

          else

             error...

 

And finally the resize function. This should first create a new array of the right size, then copy as much from the old array as will fit, then recycle the old array and start using the new one instead:

 

          void resize(int newsize)

          {  person **n = new (person *)[newsize];

             for (int i=0; i<size && i<newsize; i+=1)

                n[i] = data[i];

             delete[ ] data;

             data = n;

             size = newsize; }


 

7.      What on earth is this supposed to be?

 

                           

 

It is an ant wearing a hat, being disappointed by a ham sandwich.

 

What appears to be an extra leg at the back, and caused some people to think he must be a spider, is in fact a vestigial tail. It is quite rude to point out deformities in others, especially under examination conditions, so I am glad that so few of you mentioned it.

 

The ant is not being swept up by the wind; he is in that position because he is preparing to pounce on his prey. All the great hunting species pounce, even on prey that has disappointed them.

 

He is disappointed because the sandwich was made with mayonnaise instead of butter.

 

The hat is intended to protect him when stamped on by irate formicophobic humans. You may think that is a foolish form of protection, but he is only an ant; there is barely room for a single neuron inside that tiny little head, so I think he is doing quite well really.