Every week there will be exercises you can answer. They are completely optional, but are meant to help you master the course. You can ask for help with them at the lab sessions or consultation times.
Week 1
Introduction
-
Implement dynamic arrays in your favourite programming language.
-
Implement a "guess-the-number" game using binary search: I think of a number between 1 and 10000, your program guesses a number and I reply whether my number is higher or lower. Your program makes a new guess and so on until it guesses the right number. Then extend your program so that I can pick any positive number, not just ones up to 10000.
-
Write a method that reverses an array.
-
Write a method that interleaves two strings: it should take one character from the first string, then one from the second string, another from the first string and so on. Once one string has no characters left it should carry on with the other string. For example, interleaving the strings "anna" and "patrik" should give the result "apnantarik" (or "paantnraik"). Use s.length() to find the length of a string and s.charAt(i) to find the character at index i.
Complexity
-
Arrange the following functions in order of complexity: n4, log n, n log n, 4n, 3n3, 5n2 + n.
-
Assume that you have an algorithm that takes 2n steps and each step takes a microsecond. What is the largest problem size you can solve before the death of the Sun? Assume that the Sun will die in 5 billion years.
-
Here are the growth rates of several algorithms. Which will run in a reasonable amount of time when n = 50?
-
10 × 2n
-
10000 × log2 n
-
100000 × n
-
10 × n!
-
-
Write the following functions in big-O notation:
-
34x + x2
-
1x + 2x + 3x + 4x + 5x + 6x + 7x
-
104000x + 0.005 x2 + 103/x3
-
10 log2 x + 2 log10 x
-
2x + 10x
-
(x–2) (x–1) x
-
-
What is the complexity of adding a single element to an ArrayList? What is the complexity of adding n elements?
-
Suppose we want to be able to delete elements from a dynamic array. If we want to delete an arbitrary element, what is the complexity? What if deletion is allowed to change the order of the elements in the array?
-
Suppose you have an array of n elements, then you can check if it has duplicate elements in O(n2) time, by looping over every pair of elements. But if the array is sorted, you can do it in O(n) time. How?
Week 2
Sorting
-
Check out the following visualisations of sorting algorithms:
-
Sorting out sorting, an 80s-tastic video comparing different sorting algorithms
-
What complexities do the following operations have?
-
Finding an element in an unsorted array of n elements
-
Finding an element in a sorted array of n elements
-
Adding an element to an unsorted array of n elements
-
Adding an element to a sorted array of n elements (the resulting array should also be sorted)
The moral is that all data structures make tradeoffs—choosing a different representation will make some operations faster but others slower.
-
-
Sort the sequence 4 6 8 2 9 5 1 7 3 by hand with:
-
Insertion sort
-
Quicksort (picking the first element as the pivot)
-
Quicksort (using the median-of-three pivot)
-
Mergesort
-
-
A sorting algorithm is stable if equal elements always remain in the same relative order before and after sorting. Which of our sorting algorithms are stable? Why?
-
Suppose you have an array of booleans of size n. Give an O(n) algorithm that sorts the array so that all false elements come before all true elements. Your algorithm should use O(1) extra memory.
-
Write a method or function that removes all duplicate elements from a Java array or Haskell list in O(n log n) time. Hint: sort the array first (you may use the sorting function from the standard library).
-
The implementation of mergesort that we saw in the lecture traverses the array three times to split in half (once to call length, once to call take and once to call drop). Can you define a function splitInHalf :: [a] → ([a], [a]) that splits a list in two equal pieces after traversing it only once? Hint: your function will probably split the list into the elements with odd indexes and the elements with even indexes (e.g. splitInHalf [1,4,6,7,2] = ([1,6,2], [4,7])).
Complexity of recursive functions
-
Write a method with the following signature
void printSubsets(int[] array)
that prints out all subsets of the input array. Use recursion. Alternatively, write a Haskell function
subsets :: [a] -> [[a]]
that returns all subsets of a list. What is its complexity? Start by writing a recurrence relation. Assume that printing out a single subset is O(1).
Week 3
Stacks and queues
-
Give an algorithm that removes all comments from a program. Assume that comments are written as (* ... \*) and can be nested (i.e. (* a comment with (* another comment *) inside *)). Write your algorithm in natural language or pseudocode if you like. What is the complexity of your algorithm?
-
Write a program that reads a postfix expression and evaluates it.
-
Implement a bounded queue class using circular arrays as in the lecture. Then build an unbounded queue class using the size-doubling trick. The implementation of your unbounded queue should use a bounded queue.
-
You might think that we can easily adapt the Haskell queue implementation to a deque. But there is a trap. Imagine that we have a queue of four elements:
Queue{front = [], rear = [4,3,2,1]}
Removing an element from the front, we move rear to front and get:
Queue{front = [2,3,4], rear = []}
If we now remove an element from the back, we will move front to rear again and get:
Queue{front = [], rear = [3,2]}
By alternating removing elements from the front and the back, we get a sort of "ping-pong" effect where every time we remove an element we reverse the list of elements. Removal will then have O(n) complexity instead of O(1).
There is a standard trick to fix this. When removing an element from the front of the queue and front is empty, instead of taking the whole of the rear list and reversing it, we only take half of it. We reverse that half and move it to front, while the other half stays in rear. In our example above, after removing the first element from the queue, we would get:
Queue{front = [2], rear = [4,3]}
We use the same trick when removing an element from the back of the queue and rear is empty. Using this trick, implement a deque in Haskell where all operations have O(1) complexity.
-
In the queue implementation in the lecture we record the size of the queue in a field. You might think that we don’t need to do this: the size is simply the number of elements between front and rear, i.e., (rear - front + 1) % capacity. Instead of storing the size, we could compute it using this formula whenever we need to know it. But this doesn’t work. Why?
-
Given a circular buffer (i.e. the array, and variables front and rear), write down an expression giving the sequence represented by the buffer.
Week 4
Binary heaps
-
A max heap is a heap which operates on the maximum element instead of the minimum. That is, it has operations findMax and deleteMax instead of findMin and deleteMin.
-
Suppose you have an implementation of a min heap. How would you change it into a max heap?
-
Java provides a class PriorityQueue<E> that implements a min heap. Define a class that uses PriorityQueue<E> to implement a max heap. Hint: you will need to use a custom Comparator when you initialise the priority queue.
-
-
Insert the following elements in sequence into an empty heap: 6, 8, 4, 7, 2, 3, 9, 1, 5. Draw both the tree and array representations of the heap. Then illustrate building a heap from the same data.
-
(Harder) Remember from the lectures that a binary tree is complete if all its levels are completely full, except for the bottom level which is filled from left to right. The following diagram illustrates a complete binary tree.
Write a O(n) function that checks if a binary tree is complete or not.
Hint: you will need a helper function that calculates these three pieces of information at once:
-
Whether the tree is complete or not.
-
The height of the tree.
-
Whether the bottom level of the tree is full.
-
-
Suppose you want a data structure with only the operations insert and findMin. What is the most efficient way to implement it? Be careful with your answer!