Skip to content

Commit f5cc3cf

Browse files
Write content for README
1 parent 905dd1a commit f5cc3cf

File tree

3 files changed

+152
-20
lines changed

3 files changed

+152
-20
lines changed

Graph/Graph.playground/Pages/Adjacency List.xcplaygroundpage/Contents.swift

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ A graph implementation, using an adjacency list.
44

55
In an adjacency list implementation, each vertex stores an array of edges, indicating to which vertices it has an edge (note the directionality). The edge stores the source and destination vertices, as well as a weight.
66

7-
Connecting vertices in this implementation is O(1).
8-
97
*/
108

119
var uniqueIDCounter: Int = 0
@@ -19,7 +17,7 @@ public struct GraphEdge<T> {
1917

2018
public struct GraphVertex<T> {
2119
public var data: T
22-
public private(set) var edges: [GraphEdge<T>] = [] // This is an adjacency list, rather than matrix
20+
private var edges: [GraphEdge<T>] = [] // This is an adjacency list, rather than matrix
2321

2422
private let uniqueID: Int
2523

@@ -35,11 +33,12 @@ public struct GraphVertex<T> {
3533
}
3634

3735
// Creates an undirected edge by making 2 directed edges: self ----> other, and other ----> self
38-
public mutating func connectBetween(inout otherVertex: GraphVertex<T>, withWeight weight: Double = 1.0) {
39-
edges.append(GraphEdge(from: self, to: otherVertex, weight: weight))
40-
otherVertex.edges.append(GraphEdge(from: otherVertex, to: self, weight: weight))
36+
public mutating func connectBetween(inout otherVertex: GraphVertex<T>, withWeight weight: Double = 1.0) {
37+
connectTo(otherVertex, withWeight: weight)
38+
otherVertex.connectTo(self, withWeight: weight)
4139
}
4240

41+
// Queries for an edge from self -----> otherVertex
4342
public func edgeTo(otherVertex: GraphVertex<T>) -> GraphEdge<T>? {
4443
for e in edges {
4544
if e.to.uniqueID == otherVertex.uniqueID {

Graph/Graph.playground/Pages/Adjacency Matrix.xcplaygroundpage/Contents.swift

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,11 @@ A graph implementation, using an adjacency matrix.
55

66
In an adjacency matrix implementation, each vertex is associated with an index from 0..<V (V being the number of vertices). Then a matrix (a 2D array) is stored for the entire graph, with each entry indicating if the corresponding vertices are connected, and the weight. For example, if matrix[3][5] = 4.6, then vertex #3 is connected to vertex #5, with a weight of 4.6. Note that vertex #5 is not necessarily connected to vertex #3.
77

8-
Creating a new vertex must expand the adjacency matrix in this implementation, so creating a new vertex is O(V).
9-
Connecting vertices os O(1).
10-
118
*/
129

1310
public struct GraphVertex<T> {
1411
public var data: T
15-
private let uniqueID: Int
12+
private let index: Int
1613
}
1714

1815
public struct Graph<T> {
@@ -21,10 +18,9 @@ public struct Graph<T> {
2118
// If adjacencyMatrix[i][j] is not nil, then there is an edge from vertex i to vertex j.
2219
private var adjacencyMatrix: [[Double?]] = []
2320

24-
public init() { }
25-
21+
// Possibly O(n^2) because of the resizing of the matrix
2622
public mutating func createVertex(data: T) -> GraphVertex<T> {
27-
let vertex = GraphVertex(data: data, uniqueID: adjacencyMatrix.count)
23+
let vertex = GraphVertex(data: data, index: adjacencyMatrix.count)
2824

2925
// Expand each existing row to the right one column
3026
for i in 0..<adjacencyMatrix.count {
@@ -40,18 +36,17 @@ public struct Graph<T> {
4036

4137
// Creates a directed edge source -----> dest. Represented by M[source][dest] = weight
4238
public mutating func connect(sourceVertex: GraphVertex<T>, toDestinationVertex: GraphVertex<T>, withWeight weight: Double = 0) {
43-
adjacencyMatrix[sourceVertex.uniqueID][toDestinationVertex.uniqueID] = weight
39+
adjacencyMatrix[sourceVertex.index][toDestinationVertex.index] = weight
4440
}
4541

4642
// Creates an undirected edge by making 2 directed edges: some ----> other, and other ----> some
4743
public mutating func connect(someVertex: GraphVertex<T>, symmetricallyWithVertex withVertex: GraphVertex<T>, withWeight weight: Double = 0) {
48-
adjacencyMatrix[someVertex.uniqueID][withVertex.uniqueID] = weight
49-
adjacencyMatrix[withVertex.uniqueID][someVertex.uniqueID] = weight
50-
44+
adjacencyMatrix[someVertex.index][withVertex.index] = weight
45+
adjacencyMatrix[withVertex.index][someVertex.index] = weight
5146
}
5247

5348
public func weightFrom(sourceVertex: GraphVertex<T>, toDestinationVertex: GraphVertex<T>) -> Double? {
54-
return adjacencyMatrix[sourceVertex.uniqueID][toDestinationVertex.uniqueID]
49+
return adjacencyMatrix[sourceVertex.index][toDestinationVertex.index]
5550
}
5651

5752
}

Graph/README.markdown

Lines changed: 140 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,144 @@
11
# Graph
22

3-
YAY Graphs!
4-
TODO: Write stuff here.
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.
54

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+
```
6144
*Written by Donald Pinckney*

0 commit comments

Comments
 (0)