Skip to content

Commit 160522b

Browse files
committed
Fix error in depth-first search
Results were added in the wrong order inside the DFS too. Unit tests now use a function to check whether the result is a valid topological sort.
1 parent cf27c00 commit 160522b

File tree

4 files changed

+38
-42
lines changed

4 files changed

+38
-42
lines changed

Topological Sort/README.markdown

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,15 @@ If we were to represent these objectives in the form of a graph it would look as
2828

2929
![Example](Images/Algorithms.png)
3030

31-
If we consider each algorithm to be a vertex in the graph you can clearly see the dependencies between them. To learn something you might have to know something else first. This is exactly what topological sort is used for -- it will sort things out such that you know what to do first.
31+
If we consider each algorithm to be a vertex in the graph you can clearly see the dependencies between them. To learn something you might have to know something else first. This is exactly what topological sort is used for -- it will sort things out so that you know what to do first.
3232

3333
## How does it work?
3434

3535
**Step 1: Find all vertices that have in-degree of 0**
3636

3737
The *in-degree* of a vertex is the number of edges pointing at that vertex. Vertices with no incoming edges have an in-degree of 0. These vertices are the starting points for the topological sort.
3838

39-
In the context of the previous example, these vertices represent algorithms that don't have any prerequisites; you don't need to learn anything else first, hence the sort starts with them.
39+
In the context of the previous example, these starting vertices represent algorithms and data structures that don't have any prerequisites; you don't need to learn anything else first, hence the sort starts with them.
4040

4141
**Step 2: Traverse the graph with depth-first search**
4242

@@ -63,20 +63,20 @@ Consider the following graph:
6363
**Step 2:** Perform depth-first search for each starting vertex, without remembering vertices that have already been visited:
6464

6565
```
66-
Vertex 3: 3, 8, 9, 10
66+
Vertex 3: 3, 10, 8, 9
6767
Vertex 7: 7, 11, 2, 8, 9
6868
Vertex 5: 5, 11, 2, 9, 10
6969
```
7070

7171
**Step 3:** Filter out the vertices already visited in each previous search:
7272

7373
```
74-
Vertex 3: 3, 8, 9, 10
74+
Vertex 3: 3, 10, 8, 9
7575
Vertex 7: 7, 11, 2
7676
Vertex 5: 5
7777
```
7878

79-
**Step 4:** Combine the results of these three depth-first searches. The final sorted order is **5, 7, 11, 2, 3, 8, 9, 10**. (Important: we need to add the results of each subsequent search to the *front* of the list.)
79+
**Step 4:** Combine the results of these three depth-first searches. The final sorted order is **5, 7, 11, 2, 3, 10, 8, 9**. (Important: we need to add the results of each subsequent search to the *front* of the sorted list.)
8080

8181
The result of the topological sort looks like this:
8282

@@ -122,11 +122,13 @@ Some remarks:
122122

123123
2. The `visited` array keeps track of whether we've already seen a vertex during the depth-first search. Initially, we set all elements to `false`.
124124

125-
3. For each of the vertices in the `startNodes` array, perform a depth-first search. This returns an array of `Node` objects. We prepend that array to our own `result` array.
125+
3. For each of the vertices in the `startNodes` array, perform a depth-first search. This returns an array of sorted `Node` objects. We prepend that array to our own `result` array.
126126

127127
4. The `result` array contains all the vertices in topologically sorted order.
128128

129-
## Alternative method (Kahn's algorithm)
129+
> **Note:** For a slightly different implementation of topological sort using depth-first search, see [TopologicalSort3.swift](TopologicalSort3.swift). This uses a stack and does not require you to find all vertices with in-degree 0 first.
130+
131+
## Kahn's algorithm
130132

131133
Even though depth-first search is the typical way to perform a topological sort, there is another algorithm that also does the job.
132134

Topological Sort/Tests/TopologicalSortTests.swift

Lines changed: 18 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,18 @@ extension Graph {
1818

1919
class TopologicalSort: XCTestCase {
2020

21+
// The topological sort is valid if a node does not have any of its
22+
// predecessors in its adjacency list.
23+
func checkIsValidTopologicalSort(graph: Graph, _ a: [Graph.Node]) {
24+
for i in (a.count - 1).stride(to: 0, by: -1) {
25+
if let neighbors = graph.adjacencyList(forNode: a[i]) {
26+
for j in (i - 1).stride(through: 0, by: -1) {
27+
XCTAssertFalse(neighbors.contains(a[j]), "\(a) is not a valid topological sort")
28+
}
29+
}
30+
}
31+
}
32+
2133
func testTopologicalSort() {
2234
let graph = Graph()
2335

@@ -40,51 +52,31 @@ class TopologicalSort: XCTestCase {
4052
graph.addEdge(fromNode: node11, toNode: node10)
4153
graph.addEdge(fromNode: node8, toNode: node9)
4254

43-
XCTAssertEqual(graph.topologicalSort(), ["5", "7", "11", "2", "3", "8", "9", "10"])
55+
XCTAssertEqual(graph.topologicalSort(), ["5", "7", "11", "2", "3", "10", "8", "9"])
4456
XCTAssertEqual(graph.topologicalSortKahn(), ["3", "7", "5", "8", "11", "2", "9", "10"])
4557
XCTAssertEqual(graph.topologicalSortAlternative(), ["5", "7", "3", "8", "11", "10", "9", "2"])
4658
}
4759

4860
func testTopologicalSortEdgeLists() {
4961
let p1 = ["A B", "A C", "B C", "B D", "C E", "C F", "E D", "F E", "G A", "G F"]
50-
let s1 = ["G", "A", "B", "C", "E", "D", "F"]
51-
let a1 = ["G", "A", "B", "C", "F", "E", "D"]
52-
let k1 = ["G", "A", "B", "C", "F", "E", "D"]
53-
5462
let p2 = ["B C", "C D", "C G", "B F", "D G", "G E", "F G", "F G"]
55-
let s2 = ["B", "C", "D", "G", "E", "F"]
56-
let a2 = ["B", "C", "F", "D", "G", "E"]
57-
let k2 = ["B", "F", "C", "D", "G", "E"]
58-
5963
let p3 = ["S V", "S W", "V T", "W T"]
60-
let s3 = ["S", "V", "W", "T"]
61-
let a3 = ["S", "V", "W", "T"]
62-
let k3 = ["S", "V", "W", "T"]
63-
6464
let p4 = ["5 11", "7 11", "7 8", "3 8", "3 10", "11 2", "11 9", "11 10", "8 9"]
65-
let s4 = ["5", "7", "11", "2", "3", "8", "9", "10"]
66-
let a4 = ["3", "7", "5", "8", "11", "2", "9", "10"]
67-
let k4 = ["5", "7", "3", "8", "11", "10", "9", "2"]
6865

69-
let data = [
70-
(p1, s1, a1, k1),
71-
(p2, s2, a2, k2),
72-
(p3, s3, a3, k3),
73-
(p4, s4, a4, k4),
74-
]
66+
let data = [ p1, p2, p3, p4 ]
7567

7668
for d in data {
7769
let graph = Graph()
78-
graph.loadEdgeList(d.0)
70+
graph.loadEdgeList(d)
7971

8072
let sorted1 = graph.topologicalSort()
81-
XCTAssertEqual(sorted1, d.1)
73+
checkIsValidTopologicalSort(graph, sorted1)
8274

8375
let sorted2 = graph.topologicalSortKahn()
84-
XCTAssertEqual(sorted2, d.2)
76+
checkIsValidTopologicalSort(graph, sorted2)
8577

8678
let sorted3 = graph.topologicalSortAlternative()
87-
XCTAssertEqual(sorted3, d.3)
79+
checkIsValidTopologicalSort(graph, sorted3)
8880
}
8981
}
9082
}

Topological Sort/Topological Sort.playground/Sources/TopologicalSort1.swift

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
11
extension Graph {
22
private func depthFirstSearch(source: Node, inout visited: [Node : Bool]) -> [Node] {
3-
var result = [source]
4-
visited[source] = true
3+
var result = [Node]()
54

65
if let adjacencyList = adjacencyList(forNode: source) {
76
for nodeInAdjacencyList in adjacencyList {
87
if let seen = visited[nodeInAdjacencyList] where !seen {
9-
result += depthFirstSearch(nodeInAdjacencyList, visited: &visited)
8+
result = depthFirstSearch(nodeInAdjacencyList, visited: &visited) + result
109
}
1110
}
1211
}
13-
return result
12+
13+
visited[source] = true
14+
return [source] + result
1415
}
15-
16+
1617
/* Topological sort using depth-first search. */
1718
public func topologicalSort() -> [Node] {
1819

Topological Sort/TopologicalSort1.swift

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
extension Graph {
22
private func depthFirstSearch(source: Node, inout visited: [Node : Bool]) -> [Node] {
3-
var result = [source]
4-
visited[source] = true
3+
var result = [Node]()
54

65
if let adjacencyList = adjacencyList(forNode: source) {
76
for nodeInAdjacencyList in adjacencyList {
87
if let seen = visited[nodeInAdjacencyList] where !seen {
9-
result += depthFirstSearch(nodeInAdjacencyList, visited: &visited)
8+
result = depthFirstSearch(nodeInAdjacencyList, visited: &visited) + result
109
}
1110
}
1211
}
13-
return result
12+
13+
visited[source] = true
14+
return [source] + result
1415
}
1516

1617
/* Topological sort using depth-first search. */

0 commit comments

Comments
 (0)