|
| 1 | +# Graph |
| 2 | + |
| 3 | +A graph is a set of *vertices* paired with a set of *edges*. Each vertex has a set of associated edges, which connect to other vertices. |
| 4 | + |
| 5 | +For example, a graph can represent a social network. Each person is a vertex, and people that know each other are connected with edges. |
| 6 | + |
| 7 | +// TODO: Insert image here |
| 8 | + |
| 9 | +## Weighted and Directed Edges |
| 10 | +Edges can optionally be *weighted*, where an arbitrary weight is assigned to each edge. Consider an example of a graph representing airplane flights. Cities can be represented as vertices, and flights as edges. Then an edge weight could represent flight time. |
| 11 | + |
| 12 | +In addition, edges can optionally be *directed*. A directed edge from vertex A to vertex B connects *A to B*, but **not** *B to A*. Continuing from the flight path example, a directed edge from San Francisco, CA to Juneau, AK would indicate that there is a flight from San Francisco to Juneau, but not from Juneau to San Francisco. |
| 13 | + |
| 14 | +// TODO: Insert image here |
| 15 | + |
| 16 | +## Representing Graphs in Code |
| 17 | +There are two main strategies for representing graphs: |
| 18 | + |
| 19 | +1. Adjacency List |
| 20 | +2. Adjacency Matrix |
| 21 | + |
| 22 | +### Adjacency List |
| 23 | +In an adjacency list implementation, each vertex stores a list of edges that originate from the vertex. For example, if vertex A has an edge to vertices B, C, and D, then vertex A would have a list of 3 edges. |
| 24 | + |
| 25 | +### Adjacency Matrix |
| 26 | +In an adjacency matrix implementation, a matrix with rows and columns representing vertices stores a weight to indicate if vertices are connected, and by what weight. For example, if there is a directed edge of weight 5.6 from vertex A to vertex B, then the entry with row for vertex A, column for vertex B would have value 5.6: |
| 27 | +``` |
| 28 | + matrix[index(A)][index(B)] == 5.6 |
| 29 | +``` |
| 30 | + |
| 31 | +### Comparing Graph Representations |
| 32 | +Let *V* be the number of vertices in the graph, and *E* the number of edges. Then we have: |
| 33 | + |
| 34 | +| Operation | Adjacency List | Adjacency Matrix | |
| 35 | +|-----------------|----------------|------------------| |
| 36 | +| Storage Space | O(V + E) | O(V^2) | |
| 37 | +| Add Vertex | O(1) | O(V^2) | |
| 38 | +| Add Edge | O(1) | O(1) | |
| 39 | +| Check Adjacency | O(V) | O(1) | |
| 40 | + |
| 41 | +Note that the time to check adjacency for an adjacency list is **O(V)**, because at worst a vertex is connected to *every* other vertex. So, in the case of a *sparse* graph, in which each vertex is connected to a handfull of other vertices, an Adjacency List is preferred. If the graph is *dense*, that is, vertices are connected to most of the other vertices, then an Adjacency Matrix is preferred. |
| 42 | + |
| 43 | +## The Code |
| 44 | +Below we have implementations of both Adjacency List and Adjacency Matrix graph representations. |
| 45 | + |
| 46 | +In both cases, we also let the vertex store arbitrary data with a generic parameter. |
| 47 | +### Adjacency List |
| 48 | +First, we have the data structures for edges and vertices: |
| 49 | + |
| 50 | +```swift |
| 51 | +var uniqueIDCounter: Int = 0 |
| 52 | + |
| 53 | +public struct GraphEdge<T> { |
| 54 | + public let from: GraphVertex<T> |
| 55 | + public let to: GraphVertex<T> |
| 56 | + public let weight: Double |
| 57 | +} |
| 58 | + |
| 59 | +public struct GraphVertex<T> { |
| 60 | + public var data: T |
| 61 | + private var edges: [GraphEdge<T>] = [] |
| 62 | + |
| 63 | + private let uniqueID: Int |
| 64 | + |
| 65 | + public init(data: T) { |
| 66 | + self.data = data |
| 67 | + uniqueID = uniqueIDCounter |
| 68 | + uniqueIDCounter += 1 |
| 69 | + } |
| 70 | +} |
| 71 | +``` |
| 72 | +Note that each vertex has a unique identifier, which we use to compare equality later. |
| 73 | + |
| 74 | +Now, lets see how to connect vertices: |
| 75 | + |
| 76 | +```swift |
| 77 | +// Creates a directed edge self -----> dest |
| 78 | +public mutating func connectTo(destinationVertex: GraphVertex<T>, withWeight weight: Double = 1.0) { |
| 79 | + edges.append(GraphEdge(from: self, to: destinationVertex, weight: weight)) |
| 80 | +} |
| 81 | +``` |
| 82 | +And then we can check for an edge between vertices: |
| 83 | + |
| 84 | +```swift |
| 85 | +// Queries for an edge from self -----> otherVertex |
| 86 | +public func edgeTo(otherVertex: GraphVertex<T>) -> GraphEdge<T>? { |
| 87 | + for e in edges { |
| 88 | + if e.to.uniqueID == otherVertex.uniqueID { |
| 89 | + return e |
| 90 | + } |
| 91 | + } |
| 92 | + return nil |
| 93 | +} |
| 94 | +``` |
| 95 | + |
| 96 | +### Adjacency Matrix |
| 97 | +Fundamentally, we will store a `[[Double?]]` which is the adjacency matrix. An entry of `nil` indicates no edge, while any other value indicates an edge of the given weight. To index into the matrix using vertices, we give each `GraphVertex` a unique integer index: |
| 98 | + |
| 99 | +```swift |
| 100 | +public struct GraphVertex<T> { |
| 101 | + public var data: T |
| 102 | + private let index: Int |
| 103 | +} |
| 104 | +``` |
| 105 | + |
| 106 | +Then the `Graph` struct keeps track of the matrix. To create a new vertex, the graph must resize the matrix: |
| 107 | + |
| 108 | +```swift |
| 109 | +public struct Graph<T> { |
| 110 | + |
| 111 | + // nil entries are used to mark that two vertices are NOT connected. |
| 112 | + // If adjacencyMatrix[i][j] is not nil, then there is an edge from vertex i to vertex j. |
| 113 | + private var adjacencyMatrix: [[Double?]] = [] |
| 114 | + |
| 115 | + // O(n^2) because of the resizing of the matrix |
| 116 | + public mutating func createVertex(data: T) -> GraphVertex<T> { |
| 117 | + let vertex = GraphVertex(data: data, index: adjacencyMatrix.count) |
| 118 | + |
| 119 | + // Expand each existing row to the right one column |
| 120 | + for i in 0..<adjacencyMatrix.count { |
| 121 | + adjacencyMatrix[i].append(nil) |
| 122 | + } |
| 123 | + |
| 124 | + // Add one new row at the bottom |
| 125 | + let newRow = [Double?](count: adjacencyMatrix.count + 1, repeatedValue: nil) |
| 126 | + adjacencyMatrix.append(newRow) |
| 127 | + |
| 128 | + return vertex |
| 129 | + } |
| 130 | +``` |
| 131 | + |
| 132 | +Once we have the matrix properly configured, adding edges and querying for edges are simple indexing operations into the matrix: |
| 133 | + |
| 134 | +```swift |
| 135 | +// Creates a directed edge source -----> dest. Represented by M[source][dest] = weight |
| 136 | +public mutating func connect(sourceVertex: GraphVertex<T>, toDestinationVertex: GraphVertex<T>, withWeight weight: Double = 0) { |
| 137 | + adjacencyMatrix[sourceVertex.index][toDestinationVertex.index] = weight |
| 138 | +} |
| 139 | + |
| 140 | +public func weightFrom(sourceVertex: GraphVertex<T>, toDestinationVertex: GraphVertex<T>) -> Double? { |
| 141 | + return adjacencyMatrix[sourceVertex.index][toDestinationVertex.index] |
| 142 | +} |
| 143 | +``` |
| 144 | +*Written by Donald Pinckney* |
0 commit comments