Midterm review

Hash functions:

If we have two different hash values then we know that their keys must be different. If we have two identical hash values then we don’t know anything, because collisions (different keys that hash to the same value) are possible.

Handling collisions:

The load factor \(\alpha\) of a hash table is the ratio of elements stored \(n\) to \(m\), the size of the table. With open addresses, the maximum load factor is 1.0.

Hash functions should be


Expression trees

Data structure for encoding the (recursive) structure of various kinds of expressions. (Each kind of expression will have its own expression tree type.) We build expression trees using inheritance: we have a base class for “all expressions of this type” and then subclasses for particular types of subexpressions. E.g., in arithmetic expressions, we have

We definitely need a subclass for the first four, and maybe a fifth for parentheses, depending on what we are doing. (We might even choose to split the different infix operators into different subclasses.)

Example: A ring is a mathematical structure consisting of a set of values a, b, c, etc. together with two operations:

and two identified elements

(Parentheses are also allowed.)

An expression tree structure for general rings would need:

A sketch of this would be something like

struct ring {}; // Base class

struct ring_elem : public ring { string e; };
struct ring_zero : public ring { };
struct ring_one  : public ring { };
struct ring_plus : public ring { ring* left; ring* right; };
struct ring_mult : public ring { ring* left; ring* right; };

(If I ask you to do this on a test, you don’t need to worry about constructors or methods.)

Example: Suppose we have a sequence of tokens of the following types:

and the following grammar

bin -> LITERAL
bin -> '~' bin
bin -> bin OP bin
bin -> bin '~' OP bin
bin -> '(' bin ')'

where OP is any of the infix operators. Note that this grammar allows us to write things like 001 ~* 101 where ~* means NAND.

  1. Given this grammar, write a recognizer, using the helper functions is_bin() (is a binary literal), is_op (is an infix operator).

  2. Give a sketch of an expression tree type (just the classes/structs and their inheritance and data members) for this expression type.

  3. Suppose that the precedence of * is higher than that of + and ^, and the precedence of ~ is highest of all. (The precedence of the negated infix operators is the same as that of their unnegated forms.)

    a) What precedence levels should be assigned to the different tree node types?

    b) Rewrite the grammar so that it enforces these precedence levels.

Review of earlier material

Big-O complexity. E.g., binary search. Cutting the search space in half means logarithmic time.

Linked lists

class list {
  public:
    class node {
      int value;
      node* next;
      node* prev;
    };

  node* head;
  node* tail;
};

Implement various operations. E.g., how to find the length of a list:

int length(list l) {
  node* curr = l.head;
  int len = 0;
  while(curr) {
    len++;
    curr = curr->next;
  }

  return len;
}

Stacks: push, pop. Stack underflow: popping from an empty stack. Stack overflow: pushing onto a “full” stack (for stacks that have a maximum size). E.g., matching HTML tags: <html> with </html>. What happens if there are too many opening tags? Too many closing tags? Balanced tags?

Queues

Implementing stack on top of list.

Binary search: implementation, loop based, recursive

Sorting algorithms: bubble sort, insertion sort, selection sort.

Merge sort: merge operation

Quick sort: partition operation, choice of pivot, how it affects performance

Binary trees: tree rotations

BST operations: find, insert, delete. Three cases for delete.