C++ review: classes, inheritance, polymorphism. Vectors.

Class syntax:

class whatever {
  public:
    ...
  private:
    ...
};

The important parts of the vector class:

Range-based for-loops:

for(int i : data)
    cout << i << endl;

I won’t expect you to write templates, but you need to know what

template<typename T>
void foo(T a);

means. It means that foo is a generic function, that can take any type of argument; when it is called, the type of the argument will replace T.

UML diagrams: Class diagrams and sequence diagrams (sequence diagram for the process of getting my car repaired)

Class diagrams:

Pointer dereferenceing: *a and a->thing.

Review of summation notation

I might give you a simple summation problem. E.g., solve

$$\sum_{i=1}^n 2i$$

This can be solved by factoring out the 2, and then we have the familiar “triangle sum”. (Demo how to solve)

Complexity Analysis

Big-O analysis. Detail analysis of number of operations.

We looked at loops in two ways, in detail and as big-O.

In detail:

int smallest(vector<int> v) {
    int m = v.at(0);
    for(int i = 1; i < v.size(); i = i + 1) {
        m = v.at(i) < m ? v.at(i) : m;
    }
}

Number of assignments:

Number of vector lookups:

Number of comparisons:

Number of calls to v.size()

(Note that I might ask for different “counters”; e.g., number of increments).

Broadly speaking, Big-O analysis says that the number of times this loop will run is roughly proportional to \(n\), the size of the vector. The operations inside the loop do not depend on the size of the vector, and thus take constant time. So overall, this takes time proportional to \(n\), or we say it is of order \(O(n)\).

If we had a doubly-nested loop, both depending on \(n\), then it would be \(O(n^2)\). We’ve also seen \(O(n^3)\) and \(O(\log n)\).

You’ll need to know the Big-O “simplification” rules. This just means that if I tell you some loop takes \(2n + 3n^2\) time to run, you know that you can drop the smaller term and say that it is of order \(O(n^2)\). Note that this applies to the full “hierarchy” of Big-O classes:

Complexity class Name
\(O(1)\) Constant
\(O(\log n)\) Logarithmic
\(O(n)\) Linear
\(O(n^2)\) Quadratic
\(O(n^3)\) Cubic
\(O(n^p)\) Polynomial
\(O(2^n)\) Exponential
\(O(n!)\) Factorial

E.g., if we had \(O(2^n + \log n)\) then that simplifies to just \(O(2^n)\), because the exponential term will dominate the sum as n becomes large.

You don’t need to know the maximal contiguous subsequence sum problem, that was just an example.

The extensible array

We worked through the analysis of an extensible array that resizes itself by a constant amount each time, and found that adding new elements was still \(O(n)\) on average, over many operations. The only way to make it be \(O(1)\) was to double the size of the array each time, so that as copies become more expensive, they become correspondingly less frequent.

In order to build an extensible array, we have to keep track of both how big the array is (what is its actual allocated size), and how many elements are currently in it (how much of that space is used). When we try to add a new element to an array that has all its available space used up, then we allocate a new array, copy everything over, and delete the old array.

Lists

Singly- and doubly-linked.

Sketch list class, draw list diagram. Demonstrate some list operations. Note that I might throw you a curve ball by naming the methods/members of the list class something different. I might leave off the tail pointer, and ask you to implement push_back.

List operations:

Operation Complexity class
a.head(),tail() \(O(1)\) (tail() is \(O(n)\) if we don’t save a tail pointer)
a.insert(n,e) \(O(1)\) (single element)
a.at(i) \(O(i)\)
a.erase(n) \(O(1)\)
a.clear() \(O(n)\) (deletes everything)
a.push_back() \(O(1)\) if we have a tail pointer
a.pop_back() \(O(n)\) for singly-linked
a.push_front() \(O(1)\)
a.pop_front() \(O(1)\)
a.size() \(O(n)\)

Explain to me why this list class is basically useless:

class reverse_list {
  public:
    class node {
        int value;
        node* next;
    };

    // Other public methods...

  private:
    // No pointer to head...
    node* _tail;
};

Stacks and queues

Sketch the process of showing how the stack evolves through

push(1);
push(2);
push(3);
pop();
pop();
push(4);
pop();
pop();

Show the stack-based calculator again. I might ask you to evaluate an expression in this syntax, showing the state of the stack at each step!

Show the stack-based matching algorithm. Sketch a stack-based algorithm for matching properly nested double and single quotes.

List-based stack implementation (push and pop on the front of the list). I might ask you to implement the stack operations on a list. Array based stack implementation.

Sketch the process of running the queue on

enqueue(1);
enqueue(2);
enqueue(3);
dequeue();
dequeue();
enqueue(4);
dequeue();
dequeue();

List based queue implementation (dequeue from front, enqueue at back). I might ask you to implement a list-based queue. Array based queue implementation, with circular arrays.