- s.element(x)
- s.add(x)
- s.delete(x)
- s.size()
- iterate
- iterate in increasing order (for Comparable objects)
- s1.subset(s2)
- s1.union(s2) (Destructive or nondestructive)
- s1.intersect(s2)
- s1.setdiff(s2)

- Unordered array, with count.
- Unordered linked list with count.
- Ordered array, with count
- Ordered linked list, with count.
- Hashtable with count.
- Bit vector
- Composite data structures of various kinds.

The vector [1,0,1,1,0,0,0,0,0,0,0,0] represents the set { January, March, April }.

The vector [0,0,1,0,0,0,1,1,0,0,0,1] represents the set { March, July, August, December }.

- Space requirement |U|/8 bytes.
- Element. Constant. Return A[I].
- Add: Constant. A[I]=1.
- Delete: Constant. A[I]=0.
- Iterate in order: Proportional to |U|.
- Union: Proportional to |U|. Bitwise OR, so takes about |U|/8 instructions.
- Intersection: Proportional to |U|. Bitwise AND
- Set difference. Proportional to |U|. Bitwise s1 AND NOT s2.
- Subset. Proportional to |U|. Not a standard bitwise operation.

List intersect(a,b) { c = new List(); // null list for (x in a) for (y in b) if (y.value == x.value) { c.addLast(x.value); break; // exit inner loop } return c; }Running time: Each iteration of the inner loop takes time O(|b|) in the worst case, and there are |a| iterations of the outer loop. Worst case running time is therefore O(|a|*|b|) (quadratic).

Running time: As we will see, fast sorting algorithms run in time O(n log(n)) and the time for the set operation on ordered lists is linear. Therefore this runs in time O(n log n), where n = max(|a|,|b|).

List intersect(a,b) { c = new List(); // null list h = new Hashtable(); for (x in a) if (x is not in h) add x to h; for (y in b) if (y is in h) add y to c; return c;Expected running time: O(|a| + |b|)

The "two finger" method for computing the intersection of sets A and B represented as ordered lists.

List intersect(a,b) { x = a.first(); y = b.first(); c = new List(); while (x != null and y != null) { if (x.value == y.value) { c.addLast(x.value); x=a.next(); y=b.next(); } else if (x.value < y.value) x=a.next(); else // x.value > y.value y=b.next() } }

**Prefix notation:** Write the operator, then, recursively, each of the
arguments in prefix notation.

Examples:

The expression (2+x)*((1+y)/(x+y)) becomes

* + 2 x / + 1 y + x yIf function f(x) has one argument, g(x,y) has two, and h(x,y,z) has three, then the expression g(h(f(x),2,3),f(g(x,2))) becomes

g h f x 2 3 f g x 2Same as the original expression but with no parentheses or commas.

**Postfix notation** Write the two arguments, recursively, in postfix
then the operator.

The two examples become

2 x + 1 y + x y + / * x f 2 3 h x 2 g f g

evaluatePostfix(expression E) { S = emptystack; for (symbol Y:E) if (Y is a number or variable) S.push(value(Y)) else { // Y is an operator k=Y.numberOfArguments(); for (i=k-1; i >= 0; i--) X[i] = S.pop(); V = apply Y to X[0] ... X[k-1] S.push(V) } return S.pop();To evaluate prefix, do the same thing but scan E from right to left.

This reads the first complete expression off string S starting at K and returns (a) the root of a tree corresponding to that expression; (b) the starting index of the next expression in S. (Returning multiple values is a useful feature which is strangely rarely incorporated in programming languages.)

< Tree, int > prefixToExpTree(String S, int K) { N = new Node(); N.value = S[K]; K++; if (S[K-1] is an operator) for (i=0; i < S[K-1].numArguments; i++) { < T, K> = prefixToExpTree(S,K); make T a child of N; } return < N,K > }