✍️ Note
Some codes and contents are sourced from Udemy. This post is for personal notes where I summarize the original contents to grasp the key concepts (🎨 some images I draw it)
Graph
A graph is used to represent relationships between entities
Graphs are used to represent information in many many real world applications
Graphs are also favorite interview questions – They can start from simple concepts and get arbitrarily complex
Graph theory is a complex field of study by itself – There are many algorithms to optimize different problems represented using graphs
First principle of graph can solve most of the graph problems
Vertex
- The entities can be anything – Graphs find applications in variety of ways in the real word
Edge
- These relationships can be arbitrarily complicated and of a variety of different types
Real word Example
https://www.udemy.com/course/break-away-coding-interviews-1/learn/lecture/8474120#content
- LinkedIn (Professional Graph)
- Entities: People
- Edge: Professional relationships – people work together
- Facebook (Social Graph)
- Entities: People
- Edge: Personal relationships – People are friends
- GoogleMaps (Locations)
- Entities: Locations
- Edge: A way to get from one location to another – Roads, Rail, Air
- Each of these can be further thought of in terms of specific means of transport
- Bus, Car, Taxi, Individual trains, Airlines
- at&t (Phone network)
- Entities: Old fashioned phones – landlines
- Edge: A network to carry voice from one instrument to another
- cisco / google and more (Internet)
- Entities: Computers across the world
- Edge: A way to send information or data from one computer to another
- This can information can be routed wirelessly or over wires
What is a graph?
A graph is a set of vertices and edges (V, E)
https://www.udemy.com/course/break-away-coding-interviews-1/learn/lecture/8484198#content
- A very simple graph is one vertex
- Two vertices and a single edge is also a valid graph
Undirected Graph

Undirected Graph


Unconnected Graph

Directed Graph

Directed Acyclic Graph

Graph Representation
A way to model a vertex which may hold some information
A way to model directed or undirected edges
There are 3 standard ways that graphs can be represented
https://www.udemy.com/course/break-away-coding-interviews-1/learn/lecture/8474130#content
- Adjacency Matrix
- Adjacency List
- Adjacency Set
Graph Interface
enum GraphType {
case directed
case undirected
}
protocol Graph {
func addEdge(src: Int, dst: Int)
//Helper to get the adjacent vertices for any vertex - A method which is required for all algorithms involving graphs
func getAdjacentVertices(vertext: Int) -> [Int]?
}
Adjacency Matrix
Use A Matrix with Rows and Columns – It just a Table
The row labels and the column labels represent the vertices
Each cell represents the relationship between the vertices. I.E. The edges
https://www.udemy.com/course/break-away-coding-interviews-1/learn/lecture/8474136#content


enum GraphType {
case directed
case undirected
}
protocol Graph {
func addEdge(src: Int, dst: Int)
func getAdjacentVertices(vertext: Int) -> [Int]?
}
class AdjacencyMatrix: Graph {
private var matrix: [[Int]]
private let type: GraphType
init(vertices: [Int], graphType: GraphType) {
matrix = Array(
repeating: Array(
repeating: 0,
count: vertices.count
), count: vertices.count
)
type = graphType
}
func addEdge(src: Int, dst: Int) {
let range = 0..<matrix[0].count
guard range ~= src, range ~= dst else {
return
}
matrix[src][dst] = 1
if type == .undirected {
matrix[dst][src] = 1
}
}
func getAdjacentVertices(vertext: Int) -> [Int]? {
let range = 0..<matrix[0].count
guard range ~= vertext else {
return nil
}
var result = [Int]()
for i in 0..<matrix[0].count {
//If 1 is present in the cell it means that the vertext V is directly connected to another vertex
if matrix[vertext][i] == 1 {
result.append(i)
}
}
//Always return the vertices in ascending order - to compare with other values's adjacents
return result.sorted(by: { $0 < $1 })
}
}
Adjacency List
Each vertex is a node
Each vertex has a pointer to a linked list
This linked list contains all the other nodes this vertex connects to directly
If a vertex V has an edge leading to another vertex U
The U is present in V’s linked list
https://www.udemy.com/course/break-away-coding-interviews-1/learn/lecture/8474138#content
Directed Graph – Adjacency List
I’ll skip implementation of adjacency list. Use 2D Matrix or Set, It’s the simple and easy to handle logics.


Cons
- Order of the vertices in the adjacency lists matter
- The same graph can have multiple representations
Certain operations become tricky. E.G. Deleting a node involves looking through all the adjacency lists to remove the node from all lists
https://www.udemy.com/course/break-away-coding-interviews-1/learn/lecture/8474138#content
Adjacency Set
This is very similar to an adjacency list
Instead of a linked list to maintain the adjacent vertices It use a Set
https://www.udemy.com/course/break-away-coding-interviews-1/learn/lecture/8474138#content

enum GraphType {
case directed
case undirected
}
protocol Graph {
func addEdge(src: Int, dst: Int)
func getAdjacentVertices(vertext: Int) -> [Int]?
}
class Vertex {
let value: Int
var adjacencySet: Set<Int>
init(value: Int, adjacencySet: Set<Int>) {
self.value = value
self.adjacencySet = adjacencySet
}
func addEdge(vertext: Int) {
adjacencySet.insert(vertext)
}
func getAdjacentVertices() -> [Int] {
let array = Array(adjacencySet)
return array.sorted(by: { $0 < $1 })
}
}
class GraphAdjacencySet: Graph {
var vertices: [Vertex]
let graphType: GraphType
var numberOfVertice = 0
init(numberOfVertices: Int, graphType: GraphType) {
self.vertices = [Vertex]()
for i in 0..<numberOfVertices {
vertices.append(Vertex(value: i, adjacencySet: []))
}
self.numberOfVertice = numberOfVertices
self.graphType = graphType
}
func addEdge(src: Int, dst: Int) {
let range = 0..<numberOfVertice
guard range ~= src, range ~= dst else {
return
}
vertices[src].addEdge(vertext: dst)
if graphType == .undirected {
vertices[dst].addEdge(vertext: src)
}
}
func getAdjacentVertices(vertext: Int) -> [Int]? {
let range = 0..<numberOfVertice
guard range ~= vertext else {
return nil
}
return vertices[vertext].getAdjacentVertices()
}
}
Comparison of Graph Representations
Adjacency Matrix
- This works well when the graph is well connected I.E. Many nodes are connected with many other nodes
- The overhead of V^2 space is worth it when the number of connections are large.
Adjacency List/Set
- A Sparse graph with few connections between nodes might be more efficiently represented using adjacency list or set
E = Number of Edges
V = Number of Vertices
Space
- Adjacency Matrix -> O(V^2)
- Adjacency List -> O(E + V)
- Adjacency Set -> O(E + V)
Is Edge Present
- Adjacency Matrix -> O(1), look up in 2D Array
- Adjacency List -> Degree of V
- Adjacency Set -> O(LogDegree of V) <- like a binary search…
Iterate over edges on a vertex
https://www.udemy.com/course/break-away-coding-interviews-1/learn/lecture/8474144#content
- Adjacency Matrix -> O(V)
- Adjacency List -> Degree of V
- Adjacency Set -> Degree of V
Graph Traversal
This is very similar to tree traversal
Depth-First
Breadth-First
One additional wrinkle
- In A Tree there is only one path from the root to a specific node
- In A Graph multiple paths can lead from one node to another
- A Graph can also have cycles, so the same node can be visited multiple times
In order to avoid infinite looping in a graph we need to keep track of the nodes previously visited
https://www.udemy.com/course/break-away-coding-interviews-1/learn/lecture/8474146#content

Depth First Traversal

//Vertex is 5
var visited = Array(repeating: 0, count: 5)
func dfsGraph(graph: Graph, visited: inout [Int], currentVertex: Int) {
if visited[currentVertex] == 1 {
return
}
visited[currentVertex] = 1
let list = graph.getAdjacentVertices(vertext: currentVertex) ?? []
for v in list {
dfsGraph(graph: graph, visited: &visited, currentVertex: v)
}
//This for loop ensures that all nodes are covered even for an unconnected graph
for i in 0..<visited.count {
dfsGraph(graph: graph, visited: &visited, currentVertex: i)
}
//Post order Traversal
print("\(currentVertex)", terminator: "->")
}
let graph = AdjacencyMatrix(vertices: [0, 1, 2, 3, 4], graphType: .undirected)
graph.addEdge(src: 0, dst: 1)
graph.addEdge(src: 0, dst: 2)
graph.addEdge(src: 1, dst: 4)
graph.addEdge(src: 1, dst: 3)
graph.addEdge(src: 1, dst: 0)
graph.addEdge(src: 2, dst: 0)
graph.addEdge(src: 2, dst: 3)
graph.addEdge(src: 3, dst: 2)
graph.addEdge(src: 3, dst: 1)
graph.addEdge(src: 3, dst: 4)
graph.addEdge(src: 4, dst: 1)
graph.addEdge(src: 4, dst: 3)
dfsGraph(graph: graph, visited: &visited, currentVertex: 0)

Breadth First Traversal
Go level wise from the first node
Add non-visited child nodes to a queue
Visited = true means the node has been seen before
Use a visited list to keep track of nodes already visited
Not using recursive call
https://www.udemy.com/course/break-away-coding-interviews-1/learn/lecture/8474146#content

func bfsGraph(graph: Graph, visited: inout [Int], currentVertext: Int) {
var queue = [Int]()
queue.append(currentVertext)
while !queue.isEmpty {
let vertex = queue.removeFirst()
if visited[vertex] == 1 {
continue
}
print("\(vertex)", terminator: "->")
//MARK Visited
visited[vertex] = 1
if let adjacentVertices = graph.getAdjacentVertices(vertext: vertex) {
for v in adjacentVertices {
if visited[v] != 1 {
queue.append(v)
}
}
}
}
}
let graph = AdjacencyMatrix(vertices: [0, 1, 2, 3, 4], graphType: .undirected)
graph.addEdge(src: 0, dst: 1)
graph.addEdge(src: 0, dst: 2)
graph.addEdge(src: 1, dst: 4)
graph.addEdge(src: 1, dst: 3)
graph.addEdge(src: 1, dst: 0)
graph.addEdge(src: 2, dst: 0)
graph.addEdge(src: 2, dst: 3)
graph.addEdge(src: 3, dst: 2)
graph.addEdge(src: 3, dst: 1)
graph.addEdge(src: 3, dst: 4)
graph.addEdge(src: 4, dst: 1)
graph.addEdge(src: 4, dst: 3)
bfsGraph(graph: graph, visited: &visited, currentVertext: 0)

Check Unconnected Graph – BFS
🔥 BFS is not using a recursive call. Don’t put below logic inside bfsGraph function!
//This for loop ensures that all nodes are covered even for an unconnected graph
for i in 0..<visited.count {
bfsGraph(graph: graph, visited: &visited, currentVertext: i)
}
Exercise 1. Topological Sort
It is an ordering of vertices in a directed acyclic graph in which each node comes before all the nodes to which
It has outgoing edges
A -> B
https://www.udemy.com/course/break-away-coding-interviews-1/learn/lecture/8474156#content
- A should come before B


Find first vertex
If there are vertices which has no incoming edge, you can start any of vertex among them.

Indegree
- Number of inward directed graph edges for a given graph vertex
- Indegree of vertex 0 is 0
If there were no vertices with 0 indegree, then there would have been no topological sort -> The graph has a cycle.




It is an ordering of vertices in a Directed Acyclic Graph in which each node comes before all the nodes to which. It has outgoing edges
Final code for topological sort. (I added 2 functions)
enum GraphType {
case directed
case undirected
}
protocol Graph {
func addEdge(src: Int, dst: Int)
func getAdjacentVertices(vertext: Int) -> [Int]?
//New functions
func getIndegree(vertext: Int) -> Int
var numberOfVertices: Int { get set }
}
class Vertex {
let value: Int
var adjacencySet: Set<Int>
init(value: Int, adjacencySet: Set<Int>) {
self.value = value
self.adjacencySet = adjacencySet
}
func addEdge(vertext: Int) {
adjacencySet.insert(vertext)
}
func getAdjacentVertices() -> [Int] {
let array = Array(adjacencySet)
return array.sorted(by: { $0 < $1 })
}
}
Using Matrix
class AdjacencyMatrix: Graph {
var numberOfVertices: Int
private var matrix: [[Int]]
private let type: GraphType
init(vertices: [Int], graphType: GraphType) {
matrix = Array(
repeating: Array(
repeating: 0,
count: vertices.count
), count: vertices.count
)
numberOfVertices = vertices.count
type = graphType
}
func addEdge(src: Int, dst: Int) {
let range = 0..<numberOfVertices
guard range ~= src, range ~= dst else {
return
}
matrix[src][dst] = 1
if type == .undirected {
matrix[dst][src] = 1
}
}
func getAdjacentVertices(vertext: Int) -> [Int]? {
let range = 0..<numberOfVertices
guard range ~= vertext else {
return nil
}
var result = [Int]()
for i in 0..<numberOfVertices {
//If 1 is present in the cell it means that the vertext V is directly connected to another vertex
if matrix[vertext][i] == 1 {
result.append(i)
}
}
//Always return the vertices in ascending order - to compare with other values's adjacents
return result.sorted(by: { $0 < $1 })
}
//New function, Get Indegree of vertex
func getIndegree(vertext: Int) -> Int {
let range = 0..<numberOfVertices
guard range ~= vertext else {
return 0
}
var indegree = 0
//Loop from 0 to all vertex
for i in 0..<numberOfVertices {
//src = i, dst = vertex
if matrix[i][vertext] != 0 {
indegree += 1
}
}
return indegree
}
}
Topological Sort
func topologicalSort(graph: Graph) -> [Int] {
var queue = [Int]()
var indgreeMap = [Int: Int]()
//Step 1. Add first node of sort
for i in 0..<graph.numberOfVertices {
let indegree = graph.getIndegree(vertext: i)
//Add indegree 0's vertex to queue
indgreeMap[i] = indegree
//Add all vertices with indegree = 0 to the queue of vertices to explore
if indegree == 0 {
queue.append(i)
}
}
print(indgreeMap)
var result = [Int]()
//BFS
while !queue.isEmpty {
let vertex = queue.removeLast()
result.append(vertex)
var adjacentVertices = graph.getAdjacentVertices(vertext: vertex) ?? []
print("v: \(vertex) -> neighbors: \(adjacentVertices)")
for v in adjacentVertices {
//Important! get indegree from dictionary!
//Get the adjacent vertices of the current one and decrement their indegrees by 1
let updatedIndegree = indgreeMap[v]! - 1
indgreeMap[v] = updatedIndegree
//For every vertex which now has indegree = 0, It's a potential next node for the topological sort
if updatedIndegree == 0 {
queue.append(v)
}
}
}
print(result)
//If the final sorted list is not same to the number of vertices in the graph there is a cycles
if result.count != graph.numberOfVertices {
print("There is cycle")
return []
}
return result
}
Topological sort using Matrix

let graph = AdjacencyMatrix(vertices: [0, 1, 2, 3, 4], graphType: .directed)
graph.addEdge(src: 0, dst: 1)
graph.addEdge(src: 0, dst: 2)
graph.addEdge(src: 1, dst: 4)
graph.addEdge(src: 2, dst: 3)
graph.addEdge(src: 3, dst: 1)
graph.addEdge(src: 3, dst: 4)
topologicalSort(graph: graph)

Using Set
class GraphAdjacencySet: Graph {
var vertices: [Vertex]
let graphType: GraphType
var numberOfVertices: Int
init(numberOfVertices: Int, graphType: GraphType) {
self.vertices = [Vertex]()
for i in 0..<numberOfVertices {
vertices.append(Vertex(value: i, adjacencySet: []))
}
self.numberOfVertices = numberOfVertices
self.graphType = graphType
}
func addEdge(src: Int, dst: Int) {
let range = 0..<numberOfVertices
guard range ~= src, range ~= dst else {
return
}
vertices[src].addEdge(vertext: dst)
if graphType == .undirected {
vertices[dst].addEdge(vertext: src)
}
}
func getAdjacentVertices(vertext: Int) -> [Int]? {
let range = 0..<numberOfVertices
guard range ~= vertext else {
return nil
}
return vertices[vertext].getAdjacentVertices()
}
//New function, Get Indegree of vertex
func getIndegree(vertext: Int) -> Int {
let range = 0..<numberOfVertices
guard range ~= vertext else {
return 0
}
var indegree = 0
//Loop from 0 to all vertex
for i in 0..<numberOfVertices {
if getAdjacentVertices(vertext: i)?.contains(vertext) == true {
indegree += 1
}
}
return indegree
}
}
let graph = GraphAdjacencySet(numberOfVertices: 5, graphType: .directed)
graph.addEdge(src: 0, dst: 1)
graph.addEdge(src: 0, dst: 2)
graph.addEdge(src: 1, dst: 4)
graph.addEdge(src: 2, dst: 3)
graph.addEdge(src: 3, dst: 1)
graph.addEdge(src: 3, dst: 4)
topologicalSort(graph: graph)

As you can see the results are the same. You don’t need to change topological sort logic, Because both Set and Matrix implements Graph interface.
Exercise 2. Design A Course Schedule Considering Pre-reqs for Courses
Design a schedule for a student to complete her degree given the list of courses along with the prerequisites for each course
We have 2 lists
- List of courses
- List of pre-reqs for each course
This contains pairs (course A, course B) where course A, course B belong to courses list and course A should be taken before course B
We want to know a valid order in which the student can take her courses!
https://www.udemy.com/course/break-away-coding-interviews-1/learn/lecture/8474160#content
Each course can be a vertex


Any course that has pre-reqs should not come before its pre-reqs in the schedule!
It is an ordering of vertices in a directed acyclic graph in which each node comes before all the nodes to which it has outgoing edges
It’s a Topological Sort!
https://www.udemy.com/course/break-away-coding-interviews-1/learn/lecture/8474160#content

All nodes with indegree 0 are potential courses the student could start with
There are many schedules possible!
func courseOrder(courseList: [Int], prereqs: [Int: [Int]]) {
let graph = AdjacencyMatrix(vertices: courseList, graphType: .directed)
//You need this because Matrix used row and col start from 0 to number of vertices.
var courseId = [Int: Int]()
var matrixIndexToCourseId = [Int: Int]()
for (index, course) in courseList.enumerated() {
courseId[course] = index
matrixIndexToCourseId[index] = course
}
//Link Edges
for (preCourse, nextCourse) in prereqs {
for next in nextCourse {
print("add edge: \(preCourse) -> \(next)")
let preCourseId = courseId[preCourse]
let nextId = courseId[next]
graph.addEdge(src: preCourseId!, dst: nextId!)
}
}
var indegreeMap = [Int: Int]()
//Int is courseId not a Index
var queue = [Int]()
var sorted = [Int]()
for v in courseList {
//VertextId will be 0..<numberOfVertices
let vertexId = courseId[v]!
let indegree = graph.getIndegree(vertext: vertexId)
indegreeMap[v] = indegree
//Can take this course
if indegree == 0 {
queue.append(v)
}
}
print(indegreeMap)
while !queue.isEmpty {
let insertingCourse = queue.removeFirst()
sorted.append(insertingCourse)
let vertexId = courseId[insertingCourse]!
let nextCourses = graph.getAdjacentVertices(vertext: vertexId) ?? []
for nextCourse in nextCourses {
let courseId = matrixIndexToCourseId[nextCourse]!
let updatedIndegree = indegreeMap[courseId]! - 1
indegreeMap[courseId] = updatedIndegree
print("Next course: \(courseId) - Indegree: \(updatedIndegree)")
if updatedIndegree == 0 {
queue.append(courseId)
}
}
}
print(sorted)
}
courseOrder(
courseList: [101, 102, 103, 104, 105, 106, 107],
prereqs: [101: [102, 103, 105], 104: [105], 105: [107]]
)


Leave a comment