===========================================================================
CSC 373H                Lecture Summary for Week  1             Winter 2006
===========================================================================
(portions adapted from Francois Pitt's notes)

--------------------------
Administrative information
--------------------------

Course Information Sheet.
Office hours: MW 11:30-12:30

Motivation:
  - Abstraction is good.  Seen in ADTs: implement once, reuse often.
  - Same for algorithms: certain "types" of solutions come up often; useful
    to identify them and know about their general properties.
  - Overall goal: solve problems efficiently.

Background (from CSC263 and its prerequisites):
  - Asymptotic notation (big-Oh, Omega, Theta), analysis of runtimes for
    iterative and recursive algorithms.  [Chapter 2]
  - Data structures: queues, stacks, hashing, balanced search trees,
    priority queues, heaps, union-find/disjoint sets.
  - Graphs: definitions, properties, traversal algos (BFS, DFS).  [Ch 3]
  - Induction and other proof techniques, proving correctness of iterative
    and recursive algorithms.

-----------------
Greedy Algorithms  [Chapter 4]
-----------------

"At each step, make the choice that seems best at the time; never change
your mind."

Interval Scheduling (or Activity Scheduling)
    Input: Activities A_1, A_2, ..., A_n.  Each activity A_i consists of
        start time s(i) and finish time f(i) (s(i) < f(i)).
    Output: Subset of activities S such that all activities are
        "compatible" (no two of them overlap in time) and |S| is maximal.

 A. Brute force: consider each subset of activities.
    Correctness?  Trivial.
    Runtime?  Omega(2^n), not practical.

 B. Greedy by start time:
        sort activities s.t. s(1) <= s(2) <= ... <= s(n)
        S := {} // partial schedule
        f := 0 // last finish time of activities in S
        for i := 1 to n:
            if f <= s(i): // A_i is compatible with S
                S := S U {A_i}
                f := f(i)
        return S
    Runtime?  Sorting is Theta(n log n), main loop is Theta(n).
        Total is Theta(n log n).
    Correctness?  Doesn't work.  Counter-example:
        |-----------------------------|
          |---| |---| |---| ... |---|

 C. Greedy by duration:
        similar to above except sort by nondecreasing duration, i.e.,
        f(1)-s(1) <= f(2)-s(2) <= ... <= f(n)-s(n)
    Correctness?  Counter-example:
        |-----| |-----|  |-----| |-----| ... |-----| |-----|
             |---|            |---|               |---|

 D. Greedy by overlap count:
        similar to above except sort from fewest conflicts to most
        conflicts ("conflict" = overlap with some other activity)
    Correctness?  Counter-example:
        |---| |---| |---| |---| ... |---| |---| |---| |---|
           |---| |---| |---|           |---| |---| |---|
           |---|       |---|           |---|       |---|
           |---|       |---|           |---|       |---|

 E. Greedy by finish time:
        similar to above except sort by nondecreasing finish time, i.e.,
        f(1) <= f(2) <= ... <= f(n)
    Let S = {i_1, ..., i_k} be the schedule the algorithm obtains.

    Correctness?  3 things to prove:

    1. S is a compatible set of requests.
        - directly from algorithmi: we never select overlapping activities

    Notation: Let Opt = {j_1, ..., j_m} be an optimal solution.

    2. For all indices r <= k (recall k=|S|), f(i_r) <= f(j_r).
        Prove by induction on i, # requests considered.

	Base case: r = 1. True.

	Induction Step: Let r > 1. Suppose our 
	Induction Hypothesis: true for r - 1.

	Consider the following "bad" case:

	   |---i_{r-1}---|       |------i_r-------|
	       |---j_{r-1}--|      |---j_r----|

	Can assume f(i_{r-1}) <= f(j_{r-1}).  (Why?)

	Can S "fall behind" Opt as in picture?
	No, since greedy could choose j_r (and would).

	So, a formal proof would say
	    f(j_{r-1}) <= s(j_r) since Opt is a compatible set
	    f(i_{r-1}) <= f(j_{r-1}) from our inductive hypothesis
	    Therefore, f(i_{r-1}) <= s(j_r), and so j_r is available
	    when greedy selects i_r.
	    But from the algorithm, f(i_r) <= f(j_r) must be true.

    3. Greedy returns an optimal set S.
        Proof by contradiction:
	    Suppose not. Then |Opt| > |S|.
	    By 2. for r = k, f(i_k) <= f(j_k).
	    But j_{k+1} is in Opt. So j_{k+1} is available to the greedy
	    algorithm, but our algorithm stopped, a contradiction.