Week 06


Graphs and Graph Algorithms


Graphs2/64

Many applications require

Examples: Collection types we've seen so far Graphs are more general ... allow arbitrary connections.


... Graphs3/64

A graph G = (V,E)

Example:

[Diagram:Pics/graphs/graph1-small.png]


... Graphs4/64

A real example: Australian road distances

Distance Adelaide Brisbane Canberra Darwin Melbourne Perth Sydney
Adelaide -20551390305173227161605
Brisbane 2055-1291342916714771982
Canberra 13901291-44416584106309
Darwin 305134294441-378340494411
Melbourne 73216716583783-3448873
Perth 27164771410640493448-3972
Sydney 160598230944118733972-

Notes: vertices are cities, edges are distance between cities, symmetric


... Graphs5/64

Alternative representation of above:

[Diagram:Pics/graphs/ausroads-small.png]


... Graphs6/64

Questions we might ask about a graph:

Graph algorithms are generally more complex than tree/list ones:


Properties of Graphs7/64

Terminology: |V | and |E | normally written just as V and E.

A graph with V vertices has at most V(V-1)/2 edges.

The ratio V:E can vary considerably.

Knowing whether a graph is sparse or dense is important


Describing Graphs8/64

Defining graphs:

E.g. four representations of the same graph:

[Diagram:Pics/graphs/graph-reps-small.png]


Graph Terminology9/64

For an edge e, that connects vertices v and w

Degree of a vertex v Synonyms: A node may contain other info apart from key (identifier).

An edge may have additional info (e.g. weight, direction)


... Graph Terminology10/64

Path: a sequence of vertices where

Cycle: a path where

[Diagram:Pics/graphs/pc-graphs-small.png]


... Graph Terminology11/64

Connected graph

Complete graph

[Diagram:Pics/graphs/complete-small.png]


... Graph Terminology12/64

Tree: connected (sub)graph with no cycles

Spanning tree: tree containing all vertices

Clique: complete subgraph

Consider the following single graph:

[Diagram:Pics/graphs/graph2-small.png]

This graph has 26 vertices, 32 edges, and 4 connected components


... Graph Terminology13/64

Other types of graphs ...

Directed graph (di-graph)

Weighted graph Multi-graph


Graph ADT14/64

Data:

Operations: Notes:


... Graph ADT15/64

Graph ADT interface:

// graph representation is hidden
typedef struct GraphRep *Graph;
// vertices denoted by integers 0..N-1
typedef int Vertex;
int   validV(Graph,Vertex); //validity check
// edges are pairs of vertices (end-points)
typedef struct { Vertex v; Vertex w; } Edge;
Edge mkEdge(Graph, Vertex, Vertex);
// operations on graphs
Graph newGraph(int nV);  // #vertices
void  insertE(Graph, Edge);
void  removeE(Graph, Edge);
Graph copy(Graph);
void  destroy(Graph);
void  show(Graph);

Introduce other functions as we go ...


... Graph ADT16/64

Graph representation:

// GraphRep has to include V and E
typedef struct GraphRep {
   int  nV;  // # vertices
   int  nE;  // # edges
   ...
   // Representation for vertices
   // e.g. array [0..nV-1] of Items
   ...
   // Representation for edges
   // some possibilities to be discussed shortly
} GraphRep;


... Graph ADT17/64

Implementation of foundation operations:

// make an edge for a given Grpah
Edge mkEdge(Graph g, Vertex v, Vertex w)
{
   assert(validV(g,v) && validV(g,w));
   Edge e = {v,w}; // struct assignment
   // or  Edge e; e.v = v; e.w = w;
   return e;
}

// is a vertex valid in a given Graph?
static int validV(Graph g, Vertex v)
{
   return (g != NULL && v >= 0 && v < g->nV);
}


Exercise 1: Canonical Edge Representation18/64

Above mkEdge() simply puts vertices in edge in param order

Implement an edge equality check under such a scheme:

bool eqE(Edge a, Edge b) { ... }

Returns True if a == (v,w) && b == (v,w) OR a == (v,w) && b == (w,v)


Also ...

How would mkEdge() and eqE() change if


Array-of-edges Representation19/64

Implementation of GraphRep

typedef struct GraphRep {
   int   nV;    // #vertices
   int   nE;    // #edges
   int   n;     // size of edge array
   Edge *edges; // array of edges
} GraphRep;

[Diagram:Pics/graphs/graph-array-edges-rep-small.png]


... Array-of-edges Representation20/64

Implementation of graph initialisation:

Graph newGraph(int nV)
{
   assert(nV >= 0);
   int n = BigEnough;
   Edge *e = malloc(n*sizeof(Edge));
   assert(e != NULL);
   Graph g = malloc(sizeof(GraphRep));
   assert(g != NULL);
   g->nV = nV;  g->nE = 0;
   g->n = n;  g->edges = e;
   return g;
}

How large should n be? ... No more than (V2-V)/2


... Array-of-edges Representation21/64

Implementation of edge insertion/removal:

void insertE(Graph g, Edge e)
{
   assert(g != NULL && g->nE < g->n);
   int i, nE = g->nE;
   for (i = 0; i < nE; i++)
      if (eqE(e,g->edges[i])) break;
   if (i == nE)
      g->edges[g->nE++] = e;
}
void removeE(Graph g, Edge e)
{
   assert(g != NULL);
   int i, nE = g->nE;
   for (i = 0; i < nE; i++)
      if (eqE(e,g->edges[i])) break;
   if (i < nE) {
      g->edges[i] = g->edges[nE-1];
      g->nE--;
   }
}


Exercise 2: Two points to consider22/64

  1. Implement an edge comparison function

    cmpE(a,b) < 0 if a < b
    cmpE(a,b) > 0 if a > b
    cmpE(a,b) == 0 if a == b
    int cmpE(Edge,Edge);  
    

    which compares first on v, then on w

  2. The fixed array size is a limitation.

    Suggest two possible ways to deal with this issue.


    Exercise 3: Sorted Array of Edges23/64

    To improve performance, we wish to maintain edges in sorted order.

    Rewrite the two functions below to achieve this:

    void insertE(Graph g, Edge e) { ... }
    void removeE(Graph g, Edge e) { ... }
    

    Assume the existence of a binary search function:

    // returns potential location for e in edges
    int bSearch(Edge edges[], int lo, int hi, Edge e)
    

    Assume that edges are defined as (x,y) where x<y


    Exercise 4: Checking Neighbours (i)24/64

    Assuming an array-of-edges representation ...

    Write a function to check whether two vertices are directly connected by an edge

    Use the function interface:

    int connected(Graph g, Vertex x, Vertex y) { ... }
    

    Assume that

    Don't assume that x < y.


    Exercise 5: Getting Neighbours (i)25/64

    Assuming an array-of-edges representation ...

    Write a function to give a list of all vertices connected to a given vertex

    Vertex *neighbours(Graph g, Vertex v, int *nv);
    

    which

    Usage:

    Graph g;  Vertex v;  int n;
    ...
    Vertex *ns = neighbours(g, v, &n);
    


    ... Array-of-edges Representation26/64

    Cost of operations:

    If array is full on insert If we maintain edges in order


    Adjacency Matrix Representation27/64

    Edges represented by a V × V matrix

    [Diagram:Pics/graphs/adjmatrix-small.png]


    ... Adjacency Matrix Representation28/64

    Implementation of GraphRep

    typedef struct GraphRep {
       int   nV;    // #vertices
       int   nE;    // #edges
       int **edges; // matrix of booleans
    } GraphRep;
    

    [Diagram:Pics/graphs/graphRep-small.png]


    ... Adjacency Matrix Representation29/64

    Implementation of graph initialisation:

    Graph newGraph(int nV)
    {
       assert(nV >= 0);
       int i, j;
       int **e = malloc(nV * sizeof(int *));
       assert(e != NULL);
       for (i = 0; i < nV; i++) {
          e[i] = malloc(nV * sizeof(int));
          assert(e[i] != NULL);
          for (j = 0; j < nV; j++)
             e[i][j] = 0;
       }
       Graph g = malloc(sizeof(GraphRep));
       assert(g != NULL);
       g->nV = nV;  g->nE = 0;  g->edges = e;
       return g;
    }
    


    Validity Checking30/64

    For asserts on various kinds of objects:

    int validG(Graph g) {
    	return (g != NULL);   // other checks?
    }
    int validV(Graph g, Vertex v) {
    	return (validG(g) && 0 <= v && v < g->nV);
    }
    int validE(Graph g, Edge e) {
    	return (validV(g,e.v) && validV(g,e.w));
    }
    

    Could also be done as C pre-processor macros, e.g.

    #define validG(g)  ((g) != NULL)
    


    Adjacency Matrix Representation31/64

    Implementation of GraphRep

    typedef struct GraphRep {
       int   nV;    // #vertices
       int   nE;    // #edges
       int **edges; // matrix of booleans
    } GraphRep;
    

    [Diagram:Pics/graphs/graphRep-small.png]


    ... Adjacency Matrix Representation32/64

    Implementation of edge insertion/removal:

    void insertE(Graph g, Edge e)
    {
       assert(validG(g) && validE(g,e));
       if (g->edges[e.v][e.w]) return;
       g->edges[e.v][e.w] = 1;
       g->edges[e.w][e.v] = 1;
       g->nE++;
    }
    void removeE(Graph g, Edge e)
    {
       assert(validG(g) && validE(g,e));
       if (!g->edges[e.v][e.w]) return;
       g->edges[e.v][e.w] = 0;
       g->edges[e.w][e.v] = 0;
       g->nE--;
    }
    


    Exercise 6: Checking Neighbours (ii)33/64

    Assuming an adjacency matrix representation ...

    Write a function to check whether two vertices are directly connected by an edge

    Use the function interface:

    int connected(Graph g, Vertex x, Vertex y);
    


    Exercise 7: Getting Neighbours (ii)34/64

    Assuming an adjacency matrix representation ...

    Write a function to give a list of all vertices connected to a given vertex

    Vertex *neighbours(Graph g, Vertex v, int *nv);
    

    which

    Usage:

    Graph g;  Vertex v;  int n;
    ...
    Vertex *ns = neighbours(g, v, &n);
    


    ... Adjacency Matrix Representation35/64

    Storage cost: V int ptrs + V2 ints

    Storage optimisation: store only top-right part of matrix.

    Cost of operations:


    Adjacency List Representation36/64

    For each vertex, store linked list of adjacent vertices:

    [Diagram:Pics/graphs/adjlists-small.png]


    ... Adjacency List Representation37/64

    Implementation of GraphRep

    typedef struct vNode *VList;
    struct vNode { Vertex v; vList next; };
    typedef struct graphRep GraphRep;
    struct graphRep {
       int   nV;     // #vertices
       int   nE;     // #edges
       VList *edges; // array of lists
    };
    

    [Diagram:Pics/graphs/graphRep3-small.png]


    ... Adjacency List Representation38/64

    Implementation of core operations:

    Graph newGraph(int nV)
    {
       int i, j;
       VList *e = malloc(nV * sizeof(VList));
       assert(e != NULL);
       for (i = 0; i < nV; i++) e[i] = newVList();
       Graph g = malloc(sizeof(GraphRep));
       assert(g != NULL);
       g->nV = nV;  g->nE = 0;  g->edges = e;
       return g;
    }
    


    ... Adjacency List Representation39/64

    Implementation of edge insertion/removal (assuming VList ADT):

    void insertE(Graph g, Edge e)
    {
       assert(validG(g) && validE(g,e));
       int orig = length(g->edges[e.v]);
       g->edges[e.v] = insert(g->edges[e.v], e.w);
       g->edges[e.w] = insert(g->edges[e.w], e.v);
       if (length(g->edges[e.v]) > orig) g->nE++;
    }
    void removeE(Graph g, Edge e)
    {
       assert(validG(g) && validE(g,e));
       int orig = length(g->edges[e.v]);
       g->edges[e.v] = delete(g->edges[e.v], e.w);
       g->edges[e.w] = delete(g->edges[e.w], e.v);
       if (length(g->edges[e.v]) < orig) g->nE--;
    }
    


    Exercise 8: Checking Neighbours (iii)40/64

    Assuming an adjacency list representation ...

    Write a function to check whether two vertices are directly connected by an edge

    Use the function interface:

    int connected(Graph g, Vertex x, Vertex y);
    

    Do we care about edge representation   (e.g. v < w) ?


    Exercise 9: Getting Neighbours (iii)41/64

    Assuming an adjacency list representation ...

    Write a function to give a list of all vertices connected to a given vertex

    Vertex *neighbours(Graph g, Vertex v, int *nv);
    

    which

    Usage:

    Graph g;  Vertex v;  int n;
    ...
    Vertex *ns = neighbours(g, v, &n);
    


    Exercise 10: Adjacency List Storage42/64

    In the above representation, each edge (x,y) ...

    which means ... How could we reduce the amount of storage needed for lists?

    How would the insertE() and removeE() functions change?


    ... Adjacency List Representation43/64

    Storage cost: V list ptrs + 2*E nodes ≅ O(V+E)

    Cost of operations   (if VLists unsorted):


    Comparison of Graph Representations44/64

     array
    of edges
    adjacency
    matrix
    adjacency
    list
    space usageEV2V+E
    initialise1V2V
    copyEV2V+E
    destroy1VV+E
    insert edgeE1V
    remove edgeE1V
    connectedE1V
    neighboursEVV


    Graph Algorithms


    Problems on Graphs46/64

    Above ADT represents and manipulates graphs at low level.

    What kind of problems do we want to solve on/via graphs?


    An Aside: Complexity Classes47/64

    Many of the above problems are solved, but some are not.

    For the solved problems ...

    Classes of problems:


    ... An Aside: Complexity Classes48/64

    The P and NP classes suggest "level of difficulty":

    Another characterisation of "difficulty":


    Graph Algorithms49/64

    In this part of the course, we examine algorithms for

    and look at generic methods for traversing graphs.

    We begin with one of the simplest graph traversals ...


    Finding a Path50/64

    Problem: is there a path from vertex v to vertex w ?

    As a function:   bool isPath(Vertex v, Vertex w)

    Approach to solving problem:

    Two different approaches to order of searching:


    ... Finding a Path51/64

    Comparison of BFS/DFS search for isPath(a,h) ...

    [Diagram:Pics/graphs/graph-search-bfs-dfs-small.png]

    Both approaches ignore some edges by remembering previously visited vertices.


    ... Finding a Path52/64

    Overall strategy for path-finding (function isPath(V,W)):

    // is there a path from V to W?
    ToVisit = {V}   // vertices to be checked
    Visited = {}    // previously checked vertices
    while (still some vertices in ToVisit) {
        X = remove a vertex from ToVisit
        Visited += X
        foreach (Y in neighbours(X)) {
            if (Y == W) return TRUE
            if (Y not in Visited) ToVisit += Y
        }
    }
    return FALSE
    


    Exercise 11: BFS isPath() Function53/64

    As presented above, isPath() produces correct results.

    However, it has a potential inefficiency.

    What is the problem, and how could we fix it?

    Hint: consider the following graph and isPath(a,e) ...

    [Diagram:Pics/graphs/ispath-doubling-small.png]


    ... Finding a Path54/64

    BFS algorithm:

    int isPath(Graph g, Vertex v, Vertex w)
    {
       int *visited = calloc(g->nV,sizeof(int));
       Queue q = newQueue();
       QueueJoin(q, v);
       while (!QueueEmpty(q)) {
          Vertex y, x = QueueLeave(q);
          foreach (y in neighbours(x)) {
             if (y == w) return TRUE;
             if (!visited[y]) {
                QueueJoin(q, y);
                visited[y] = 1;
             }
          }
       }
       return FALSE;
    }
    


    Exercise 12: Breadth-first Traversal55/64

    Show the BFS order we visit to determine isPath(a,k)

    [Diagram:Pics/graphs/isPath-traversal-small.png]

    Assume neighbours are chosen in alphabetical order


    ... Finding a Path56/64

    DFS algorithm:

    int isPath(Graph g, Vertex v, Vertex w)
    {
       int *visited = calloc(g->nV,sizeof(int));
       Stack s = newStack();
       StackPush(s, v);
       while (!StackEmpty(s)) {
          Vertex y, x = StackPop(s);
          foreach (y in neighbours(x)) {
             if (y == w) return TRUE;
             if (!visited[y]) {
                StackPush(s, y);
                visited[y] = 1;
             }
          }
       }
       return FALSE;
    }
    


    Exercise 13: Depth-first Traversal57/64

    Show the DFS order we visit to determine isPath(a,k)

    [Diagram:Pics/graphs/isPath-traversal-small.png]

    Assume neighbours are chosen in alphabetical order


    Exercise 14: Finding Neighbours58/64

    How would we implement the pseudo-code:

    foreach (y in neighbours(x))
    

    for a graph represented as


    Exercise 15: Implement GraphLab59/64

    Implement a system with a user interface to

    Can be used as a basis for demo'ing later algorithms.


    Graph Traversal


    Graph Traversal61/64

    A common class of graph algorithms involves

    isPath() is a simple instance of a graph traversal

    A more useful traversal would be findPath()

    Two approaches to graph traversal:   depth-first,   breadth-first.


    Depth-first Traversal62/64

    Basic approach to depth-first traversal:

    Notes: In this context, traversal is also called search   (hence DFS)


    ... Depth-first Traversal63/64

    An aside on nondeterminism ...

    "for each neighbour" doesn't specify a visiting order

    As long as we visit all neighbours, the traversal algorithms work

    Could choose a random order each time

    Such algorithms are called nondeterministic

    Cf. "regular" (deterministic) algorithms ...


    ... Depth-first Traversal64/64

    Depth-first search (DFS) algorithm:

    int order;     // visiting order
    int *visited;  // array of visiting orders
                   // indexed by vertex 0..V-1
    void dfs(Graph g)
    {
       int i;
       visited = malloc(g->nV*sizeof(int));
       for (i = 0; i < g->nV; i++) visited[i] = -1;
       order = 0;
       dfsR(g, 0);
    }
    void dfsR(Graph g, Vertex v)
    {
       visited[v] = order++;
       Vertex w;
       for (w = 0; w < g->nV; w++) {
          if (!g->edges[v][w]) continue;
          if (visited[w] == -1) dfsR(g, w);
       }
    }
    


    Produced: 21 Aug 2017