Skip to content

Commit 9db70a6

Browse files
committed
Merge pull request kodecocodes#26 from donald-pinckney/master
Add graph
2 parents b311d34 + f5cc3cf commit 9db70a6

File tree

6 files changed

+331
-0
lines changed

6 files changed

+331
-0
lines changed
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
3+
A graph implementation, using an adjacency list.
4+
5+
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.
6+
7+
*/
8+
9+
var uniqueIDCounter: Int = 0
10+
11+
public struct GraphEdge<T> {
12+
public let from: GraphVertex<T>
13+
public let to: GraphVertex<T>
14+
public let weight: Double
15+
}
16+
17+
18+
public struct GraphVertex<T> {
19+
public var data: T
20+
private var edges: [GraphEdge<T>] = [] // This is an adjacency list, rather than matrix
21+
22+
private let uniqueID: Int
23+
24+
public init(data: T) {
25+
self.data = data
26+
uniqueID = uniqueIDCounter
27+
uniqueIDCounter += 1
28+
}
29+
30+
// Creates a directed edge self -----> dest
31+
public mutating func connectTo(destinationVertex: GraphVertex<T>, withWeight weight: Double = 1.0) {
32+
edges.append(GraphEdge(from: self, to: destinationVertex, weight: weight))
33+
}
34+
35+
// Creates an undirected edge by making 2 directed edges: self ----> other, and other ----> self
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)
39+
}
40+
41+
// Queries for an edge from self -----> otherVertex
42+
public func edgeTo(otherVertex: GraphVertex<T>) -> GraphEdge<T>? {
43+
for e in edges {
44+
if e.to.uniqueID == otherVertex.uniqueID {
45+
return e
46+
}
47+
}
48+
49+
return nil
50+
}
51+
}
52+
53+
54+
55+
// Create 4 separate vertices
56+
var v1 = GraphVertex(data: 1)
57+
var v2 = GraphVertex(data: 2)
58+
var v3 = GraphVertex(data: 3)
59+
var v4 = GraphVertex(data: 4)
60+
61+
// Setup a cycle like so:
62+
// v1 ---(1)---> v2 ---(1)---> v3 ---(4.5)---> v4
63+
// ^ |
64+
// | V
65+
// ---------<-----------<---------(2.8)----<----|
66+
67+
v1.connectTo(v2, withWeight: 1.0)
68+
v2.connectTo(v3, withWeight: 1.0)
69+
v3.connectTo(v4, withWeight: 4.5)
70+
v4.connectTo(v1, withWeight: 2.8)
71+
72+
// Returns the weight of the edge from v1 to v2 (1.0)
73+
v1.edgeTo(v2)?.weight
74+
75+
// Returns the weight of the edge from v1 to v3 (nil, since there is not an edge)
76+
v1.edgeTo(v3)?.weight
77+
78+
// Returns the weight of the edge from v3 to v4 (4.5)
79+
v3.edgeTo(v4)?.weight
80+
81+
// Returns the weight of the edge from v4 to v1 (2.8)
82+
v4.edgeTo(v1)?.weight
83+
//: [Next](@next)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<Timeline
3+
version = "3.0">
4+
<TimelineItems>
5+
</TimelineItems>
6+
</Timeline>
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
//: [Previous](@previous)
2+
/*
3+
4+
A graph implementation, using an adjacency matrix.
5+
6+
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.
7+
8+
*/
9+
10+
public struct GraphVertex<T> {
11+
public var data: T
12+
private let index: Int
13+
}
14+
15+
public struct Graph<T> {
16+
17+
// nil entries are used to mark that two vertices are NOT connected.
18+
// If adjacencyMatrix[i][j] is not nil, then there is an edge from vertex i to vertex j.
19+
private var adjacencyMatrix: [[Double?]] = []
20+
21+
// Possibly O(n^2) because of the resizing of the matrix
22+
public mutating func createVertex(data: T) -> GraphVertex<T> {
23+
let vertex = GraphVertex(data: data, index: adjacencyMatrix.count)
24+
25+
// Expand each existing row to the right one column
26+
for i in 0..<adjacencyMatrix.count {
27+
adjacencyMatrix[i].append(nil)
28+
}
29+
30+
// Add one new row at the bottom
31+
let newRow = [Double?](count: adjacencyMatrix.count + 1, repeatedValue: nil)
32+
adjacencyMatrix.append(newRow)
33+
34+
return vertex
35+
}
36+
37+
// Creates a directed edge source -----> dest. Represented by M[source][dest] = weight
38+
public mutating func connect(sourceVertex: GraphVertex<T>, toDestinationVertex: GraphVertex<T>, withWeight weight: Double = 0) {
39+
adjacencyMatrix[sourceVertex.index][toDestinationVertex.index] = weight
40+
}
41+
42+
// Creates an undirected edge by making 2 directed edges: some ----> other, and other ----> some
43+
public mutating func connect(someVertex: GraphVertex<T>, symmetricallyWithVertex withVertex: GraphVertex<T>, withWeight weight: Double = 0) {
44+
adjacencyMatrix[someVertex.index][withVertex.index] = weight
45+
adjacencyMatrix[withVertex.index][someVertex.index] = weight
46+
}
47+
48+
public func weightFrom(sourceVertex: GraphVertex<T>, toDestinationVertex: GraphVertex<T>) -> Double? {
49+
return adjacencyMatrix[sourceVertex.index][toDestinationVertex.index]
50+
}
51+
52+
}
53+
54+
55+
56+
// Create 4 separate vertices
57+
var graph = Graph<Int>()
58+
let v1 = graph.createVertex(1)
59+
let v2 = graph.createVertex(2)
60+
let v3 = graph.createVertex(3)
61+
let v4 = graph.createVertex(4)
62+
63+
// Setup a cycle like so:
64+
// v1 ---(1)---> v2 ---(1)---> v3 ---(4.5)---> v4
65+
// ^ |
66+
// | V
67+
// ---------<-----------<---------(2.8)----<----|
68+
69+
graph.connect(v1, toDestinationVertex: v2, withWeight: 1.0)
70+
graph.connect(v2, toDestinationVertex: v3, withWeight: 1.0)
71+
graph.connect(v3, toDestinationVertex: v4, withWeight: 4.5)
72+
graph.connect(v4, toDestinationVertex: v1, withWeight: 2.8)
73+
74+
// Returns the weight of the edge from v1 to v2 (1.0)
75+
graph.weightFrom(v1, toDestinationVertex: v2)
76+
77+
// Returns the weight of the edge from v1 to v3 (nil, since there is not an edge)
78+
graph.weightFrom(v1, toDestinationVertex: v3)
79+
80+
// Returns the weight of the edge from v3 to v4 (4.5)
81+
graph.weightFrom(v3, toDestinationVertex: v4)
82+
83+
// Returns the weight of the edge from v4 to v1 (2.8)
84+
graph.weightFrom(v4, toDestinationVertex: v1)
85+
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<Timeline
3+
version = "3.0">
4+
<TimelineItems>
5+
</TimelineItems>
6+
</Timeline>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2+
<playground version='6.0' target-platform='ios' display-mode='rendered'>
3+
<pages>
4+
<page name='Adjacency List'/>
5+
<page name='Adjacency Matrix'/>
6+
</pages>
7+
</playground>

Graph/README.markdown

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
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

Comments
 (0)