Greedy Technique

Make the best or greedy choice at any given step.

Solves an optimization problem piece-by-piece by making choices that are:

  1. Feasible
  2. Locally Optimal: Make choices based on less expensive, short-term criteria
  3. Irrevocable: Choices cannot be undone!

Why? — Approximations can be wildly faster than precise ones.

Applies to Problems with:

Overview

Optimal Solutions:

Approximations:

Minimum Spanning Tree

Spanning Tree: A spanning tree of G is a connected acyclic subgraph that has all of G’s vertices.

Minimum Spanning Tree: A weighted version of the above.

Example: Real-World Applications

Any efficient network design!

Prim’s

Grow a MST by repeatedly adding the least-cost edge that connects a vertex in a the existing tree to a vertex not in the existing subtree.

ALGORITHM Prim(G)
// Prim's algorithm for constructing a MST
// Input: A weighted connected graph G = <V, E>
// Output: E_T, the set of edges composing a minimum spanning tree of G
    V_T \leftarrow {v_0} // Start with an arbitrary vertex
    E_T \leftarrow empty set
    for i \leftarrow 1 to |V| \text{$-$} 1 do
        find minimum-weight edge e* = (v*, u*) among all edges (v, u)
        such that v is in V_T and u is in V \text{$-$} V_T
        V_T \leftarrow V_T union {u*}
        E_T \leftarrow E_T union {e*}
    return E_T

Analysis — Time complexity is O(|E| \log |V|) if implemented with a min-heap priority queue.

Kruskal’s

Grow a MST by repeatedly adding the last-cost edge that doesn’t introduce a cycle among the edge included so far.

Note — Key Question: How do you determine if its acyclic still?

If the two nodes being connected:

Aside — Union-Find Problem:

This is a requirement for Kruskal’s to find cycles efficiently. We use a disjoint-set data structure that supports MakeSet, Find, and Union.

Example: Union-Find Execution

Let S = \{ 1,2,3,4,5,6 \}

Applying MakeSet() six times, we initialize the structure of a selection of 6 singletons. When we add an edge connecting 1 and 2, we Union(1,2). To check if an edge 2-3 creates a cycle, we check if Find(2) == Find(3). If not, we Union them.

ALGORITHM Kruskal(G)
// Kruskal's algorithm for constructing a MST
// Input: A weighted connected graph G = <V, E>
// Output: E_T, the set of edges composing a minimum spanning tree of G
    Sort E in nondecreasing order of edge weights
    E_T \leftarrow empty set
    Ecounter \leftarrow 0; k \leftarrow 0
    Initialize |V| disjoint subsets for each vertex
    while Ecounter \lt |V| \text{$-$} 1 do
        k \leftarrow k \text{$+$} 1
        e \leftarrow E[k] (which connects u and v)
        if Find(u) \ne Find(v)
            E_T \leftarrow E_T union {e}
            Union(u, v)
            Ecounter \leftarrow Ecounter \text{$+$} 1
    return E_T

Analysis — Sorting is the main thing O(|E| \log |E|). MakeSet O(|V|), Find and Union operations with path compression are nearly constant time. Thus, total time is O(|E| \log |E|).

Single Source Shortest Path Problem

Note — Remember: Do not mismatch this problem with a problem in the last chapter (Floyd’s algortihm). This problem is about one node’s relationship to everyr other node! Floyd’s was about every node!

Problem: Given a weighted connected graph G, find shortest paths for soruce vertex s to each of the other vertices.

Dijkstra’s Algorithm

Actually pretty similar to Prim’s MST.

Among vertices not already in the tree, finds vertex u with the smallest sum.

d_v + w(v,u)

where:

ALGORITHM Dijkstra(G, s)
// Dijkstra's algorithm for single-source shortest paths
// Input: A weighted connected graph G and starting vertex s
// Output: Shortest distance from s to every other vertex
    Initialize priority queue Q with all vertices
    for each v in V do d[v] \leftarrow infinity
    d[s] \leftarrow 0
    while Q is not empty do
        u \leftarrow ExtractMin(Q)
        for each neighbor v of u do
            if d[u] \text{$+$} w(u, v) \lt d[v]
                d[v] \leftarrow d[u] \text{$+$} w(u, v)
                DecreaseKey(Q, v, d[v])

Analysis — Efficiency: O(|V|^2) for graphs represented by weight matrix and array implementation of priority queue. O(|E| \log |V|) for graphs represented by adj. lists and min-heap. Space complexity is O(|V|).

Aside — A* is an extended version of this.

Example: Dijkstra’s Algorithm Trace
Tree Vertices Remaining Vertices
a(-,0) b(a,3), c(-,infinity), d(a,7), e(-,infinity)
b(a,3) c(b,3+4), d(b,3+2), e(-,infinity)
d(b,5) c(b,7), e(d, 5+4)
c(b,7) e(d, 9)
Example: Dijkstra’s with Airports
Tree Vertices Remaining Vertices
BWI(-,0) JFK(BWI, 184), MIA(BWI, 946), ORD(BWI, 621)
JFK(BWI, 184) ORD(JFK, 740 + 184), BOS(JFK, 187 + 184), PVD(JFK, 144 + 184), MIA(JFK, 946 + 184)
ORD(BWI, 621) DFW(ORD, 1542), MIA(BWI, 946), SFO(ORD, 2462)
MIA(BWI, 946) LAX(MIA, 3289), LAX(DFW, 2777), LAX(SFO, 2799)

Basically we find “what is the shortest path to this node?” and then build the shortest path by gradually extending and building paths out of these optimal little legos.

Note — Aside Notes:

Coding Problem

Coding: Assignment of bit strings to alphabet characters

Codewords: Bit strings assigned for characters of alphebet.

Two Types of Codes:

Problem: If the frequencies of the character occurence are know (e.g., Standard American English), what’s the best binary prefix-free code33. No codeword is a prefix of another codeword. This ensures that the encoded string can be uniquely and instantaneously decoded from left to right.?

Huffman Codes

It’s an optimal prefix code generated by creating a binary tree using the frequency of the character as a weight. We repeatedly merge the two lowest-frequency nodes into a new parent node whose frequency is their sum, continuing until we have a single root. Left edges get ‘0’, right edges get ‘1’.

ALGORITHM Huffman(C)
// Builds a Huffman tree
// Input: A set C of n characters and their frequencies
// Output: A Huffman tree for the characters
    Initialize a priority queue Q with all characters ordered by frequency
    for i \leftarrow 1 to n \text{$-$} 1 do
        Allocate a new node z
        z.left \leftarrow x \leftarrow ExtractMin(Q)
        z.right \leftarrow y \leftarrow ExtractMin(Q)
        z.freq \leftarrow x.freq \text{$+$} y.freq
        Insert(Q, z)
    return ExtractMin(Q) // root of the tree

Analysis — Time complexity is O(n \log n) due to priority queue operations. Space complexity is O(n) to store the tree.

Example: Building tree

Eerie eyes seen near lake

Char Freq
E 8
R 2
I 1
Y 1
S 2
N 2
A 1
L 1
K 1
. 1

Okay so the encoded file is pretty cool. We get like 25—30% savings.


  1. The optimal solution to the problem contains within it optimal solutions to subproblems.↩︎

  2. like greedy algorithms.↩︎

  3. No codeword is a prefix of another codeword. This ensures that the encoded string can be uniquely and instantaneously decoded from left to right.↩︎