Lecture 06

1.0 - Graph Data Structures

We may need certain information about graphs:

Graphs will be used to explore different programming styles throughout the course.

1.1 - Types of Graphs

1.2 - Graph Representations

1.2.1 - Adjacency List (Undirected)

🌱 For every vertex, store a list of vertices that it’s adjacent to

Node Connections
A B, C, D
B A, D
C A, D
D A, B, C

For an undirected graph G=(V,E)G=(V, E) with V\bold{V} vertices and E\bold{E} vertices, what is the worst-case:

1.2.2 - Adjacency List (Directed)

Node Connections
A B, D
B D
C A
D C

For a directed graph G=(V,E)G=(V,E) with V\bold{V} vertices and E\bold{E} vertices, what is the worst-case:

1.2.3 - Adjacency Matrix (Undirected)

A B C D
A ❌ βœ… βœ… βœ…
B βœ… ❌ ❌ βœ…
C βœ… ❌ ❌ βœ…
D βœ… βœ… βœ… ❌

For an undirected graph G=(V,E)G=(V, E) with V\bold{V} vertices and E\bold{E} edges what is the worst case:

1.2.4 - Adjacency Matrix (Directed)

A B C D
A ❌ βœ… ❌ βœ…
B ❌ ❌ ❌ βœ…
C βœ… ❌ ❌ ❌
D ❌ ❌ βœ… ❌

For a directed graph G=(V,E)G=(V, E) with VV vertices and EE edges, what is the worst-case

1.3 - Comparison of Graph Representations

2.0 - Graph Traversal Algorithms

2.1 - Graphs and Shortest Path Algorithms

2.2.1 - Implementation of BFS

What do we know about BFS?

  1. Start vertex vv is at a distance 00 from itself.
  2. The vertices adjacent to vv, other than those at distance 00 are at distance 1
  3. The vertices adjacent to v’s adjacent vertices (other than those at distance 0 or 1
  4. etc…

We will use a queue data structure:

How do we keep track of which vertices have been reached?

🌱 To find the shortest path from A to B, we just need to keep track (update) of the parent of each node as we visit it

BFS(G, v) for u in G.V - {v} # For all but the starting vertex. u.distance = ∞; u.colour = white; u.parent = NULL; v.distance = 0; v.color = grey; v.parent = NULL Q.initialise() Q.enqueue(v) while !Q.isEmpty() current = Q.dequeue() for u in G.adjacent[current] if u.colour == white # For vertices with no shortest path yet, compute. u.distance == current.distance + 1 u.color = grey; u.parent = current Q.enqueue(u) current.color := black

2.2.2 - Performance of BFS

The worst-case time complexity of BFS is:

Θ(V)+Θ(1)+(βˆ‘v∈G.VΘ(1))+outDegree(v)Γ—Ξ˜(1))=Θ(V+E) \Theta(V)+\Theta(1)+(\sum_{v\in G.V} \Theta(1)) + \text{outDegree}(v)\times\Theta(1))=\Theta(V+E)

The worst-case space complexity of BFS is Θ(V)=Θ(V)+Θ(V)\Theta(V)=\Theta(V)+\Theta(V) (for space to store colours, and the queue).

Assuming enqueue and dequeue take Θ(1)\Theta(1) time, and we use an adjacency list representation of our graph problem - require determining adjacent vertices in Θ(outDegree(v))\Theta(\text{outDegree(v)}) time.

That is:

Θ(V)\Theta(V) for initial colouring

Θ(1)\Theta(1) for setup of initial vertex v, initialisation of queue and enqueueing v.

βˆ‘v∈G.VΘ(1)+outDegree(v)Γ—Ξ˜(1)\sum_{v\in G.V}\Theta(1)+\text{outDegree(v)}\times\Theta(1) as:

Each vertex can be added to the queue at most one time; Therefore, the while loop iterates at most Θ(V)\Theta(V) times.

We perform the inner loop outDegree(v)\text{outDegree(v)} times, in which we do a constant amount of work per iteration of the inner loop.

Since βˆ‘(outDegree(v))=Θ(E)\sum(\text{outDegree(v)})=\Theta(E)

2.3.1 - Implementation of DFS

The depth-first search algorithm can be used to classify the edges in a graph (directed) into four types: (in an undirected graph, we only have tree edges and back edges)

DFS Graph with back-edges (thinner lines)

DFS(G): # We use colour to indicate where in the traversal process we are for u in G.V u.color = white u.parent = NULL # For each vertex, we call dfs_visit on it for u in G.V if u.color == white then DFS_Visit(G, u) DFS_Visit(G, v) v.color = grey # Started traversal, but not finished for u in adjacent[v] if u.color == white: u.parent = v DFS_Visit(G, u) v.color = black # Finished traversal for v

2.3.2 - Performance of DFS

Θ(V)\Theta(V) for initial colouring

Θ(V)+x\Theta(V)+\color{pink}x Each vertex can only be visited once, as we only visit it when it is coloured white.

βˆ‘v∈G.V\color{pink}\sum_{v\in G.V} work for the for loop in DFS(G), calling DFS_Visit for each vertex.

Θ(1)\color{pink} \Theta(1) work for setting colours to grey and black at the start and end of the method

outDegree(v)\color{pink}\text{outDegree(v)} work for the for loop - a vertex has outDegree(v)=Θ(E)\text{outDegree(v)}=\Theta(E) adjacent vertices.

Therefore, the work done (time complexity) by DFS is given as:

Θ(V)+(βˆ‘v∈G.VΘ(1)+outDegree(v)Γ—Ξ˜(1))=Θ(V+E) \Theta(V)+(\sum_{v\in G.V}\Theta(1)+\text{outDegree(v)}\times\Theta(1))=\Theta(V+E)

We use Θ(V)\Theta(V) space to store the colours of each node, and Θ(V)\Theta(V) space for recursive calls

2.3.3 - DFS with Start and Finish Times

DFS(G): time = 0 # We use colour to indicate where in the traversal process we are for u in G.V u.color = white u.parent = NULL # For each vertex, we call dfs_visit on it for u in G.V if u.color == white then DFS_Visit(G, u) DFS_Visit(G, v) time += 1 u.discovered = time v.color = grey # Started traversal, but not finished for u in adjacent[v] if u.color == white: u.parent = v DFS_Visit(G, u) v.color = black # Finished traversal for v time += 1 u.finished = time

3.0 - Directed Acyclic Graphs

3.1 - Topological Sort

Topological_Sort(G): initialise an empty linked-list of vertices call DFS(G) as each vertex is finished in DFS, insert it onto the front of the linked list return the list of vertices.