Skip to content

Commit 11f5ebf

Browse files
committed
Add Graph
1 parent 903dd56 commit 11f5ebf

File tree

3 files changed

+315
-1
lines changed

3 files changed

+315
-1
lines changed

data_structures/linear/queue/queue.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ class Queue<T> {
1818

1919
bool get isEmpty => size == 0;
2020

21+
bool get isNotEmpty => !isEmpty;
22+
2123
/// Adding to the beginning of the Queue.
2224
/// En-queuing from the end (last) same as the linked list's push. (to achieve constant time when popping)
2325
///
Lines changed: 312 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,312 @@
1+
import 'package:test/test.dart';
2+
3+
import '../../linear/queue/queue.dart';
4+
5+
/// An undirected GRAPH using the adjacency list representation.
6+
class Graph<T> {
7+
/// Creates an undirected GRAPH using the adjacency list representation.
8+
Graph();
9+
10+
final Map<T, List<T>> _adjacencyList = {};
11+
12+
/// Pseudocode:
13+
/// - Write a method called addVertex, which accepts a vertex.
14+
/// - It should add a key to the adjacency list with the vertex
15+
/// and set its value to be an empty array (no neighbors).
16+
void addVertex(T vertex) {
17+
if (_adjacencyList[vertex] != null) {
18+
throw StateError('vertex already added!');
19+
}
20+
_adjacencyList[vertex] = [];
21+
}
22+
23+
/// Pseudocode:
24+
/// - The function should accept a vertex to remove.
25+
/// - The function should iterate over the neighbors list of that vertex as long as there
26+
/// is any vertices in the list.
27+
/// - Inside of the loop, call our removeEdge function with the vertex we are removing
28+
/// and the current neighbor.
29+
/// - Eventually, Delete the key for that vertex at the adjacency list.
30+
removeVertex(T vertex) {
31+
final neighbors = _adjacencyList[vertex];
32+
if (neighbors == null) throw StateError('vertex does not exist!');
33+
34+
while (neighbors.isNotEmpty) {
35+
removeEdge(vertex, neighbors.last);
36+
}
37+
_adjacencyList.remove(vertex);
38+
}
39+
40+
/// Pseudocode:
41+
/// - This function should accept two vertices, we can call them vertex1 and vertex2.
42+
/// - The function should add vertex1 to the neighbors list of vertex2.
43+
/// - The function should add vertex2 to the neighbors list of vertex1.
44+
addEdge(T vertex1, T vertex2) {
45+
final v1Neighbors = _adjacencyList[vertex1];
46+
final v2Neighbors = _adjacencyList[vertex2];
47+
if (v1Neighbors == null || v2Neighbors == null) {
48+
throw StateError('Either vertex1 or vertex2 does not exist!');
49+
}
50+
v1Neighbors.add(vertex2);
51+
v2Neighbors.add(vertex1);
52+
}
53+
54+
/// Pseudocode:
55+
/// - This function should accept two vertices, we'll call them vertex1 and vertex2.
56+
/// - The function should remove vertex2 from the neighbors list of vertex2.
57+
/// - The function should remove vertex1 from the neighbors list of vertex1.
58+
removeEdge(T vertex1, T vertex2) {
59+
final v1Neighbors = _adjacencyList[vertex1];
60+
final v2Neighbors = _adjacencyList[vertex2];
61+
if (v1Neighbors == null || v2Neighbors == null) {
62+
throw StateError('Either vertex1 or vertex2 does not exist!');
63+
}
64+
v1Neighbors.remove(vertex2);
65+
v2Neighbors.remove(vertex1);
66+
}
67+
68+
/// Depth-first Search (DFS) Pseudocode (Recursively):
69+
/// - The function should accept a starting vertex.
70+
/// - The function should return early if the startingVertex doesn't exist.
71+
/// - Create a list to store the end result, to be returned at the very end.
72+
/// - Create a set or map to store the visited vertices.
73+
/// - Create a helper function which accepts a vertex:
74+
/// - The helper function should place the vertex it accepts into the
75+
/// visited set and push that vertex into the result list.
76+
/// - Loop over all of the values in the neighbors list for that vertex (will end if it's empty).
77+
/// - If any of those values have not been visited, recursively invoke the helper function with that vertex.
78+
/// - Invoke the helper function with the starting vertex.
79+
/// - Return the result array.
80+
List<T> dfs(T startingVertex) {
81+
// startingVertex vertex doesn't exist.
82+
if (_adjacencyList[startingVertex] == null) return [];
83+
84+
final List<T> data = [];
85+
final Set<T> visited = {};
86+
87+
_dfsRecursive(data, visited, startingVertex);
88+
return data;
89+
}
90+
91+
void _dfsRecursive(List<T> data, Set<T> visited, T vertex) {
92+
visited.add(vertex);
93+
data.add(vertex);
94+
95+
for (final neighbor in _adjacencyList[vertex]!) {
96+
if (!visited.contains(neighbor)) _dfsRecursive(data, visited, neighbor);
97+
}
98+
}
99+
100+
/// Breadth-first Search (BFS) Pseudocode (Iteratively):
101+
/// - The function should accept a starting vertex.
102+
/// - The function should return early if the startingVertex doesn't exist.
103+
/// - Create a list to store the end result, to be returned at the very end.
104+
/// - Create a set or map to store the visited vertices.
105+
/// - Create a queue and place the starting vertex in it.
106+
/// - Mark the starting vertex as visited.
107+
/// - Loop as long as there is anything in the queue:
108+
/// - Remove the first vertex from the queue and push it into the result list.
109+
/// - Loop over all of the values in the neighbors list for that vertex (will end if it's empty).
110+
/// - If any of those values have not been visited, mark it as visited and enqueue that vertex.
111+
/// - Return the result array.
112+
List<T> bfs(T startingVertex) {
113+
// startingVertex vertex doesn't exist.
114+
if (_adjacencyList[startingVertex] == null) return [];
115+
116+
final List<T> data = [];
117+
final Set<T> visited = {};
118+
119+
final queue = Queue<T>()..enqueue(startingVertex);
120+
visited.add(startingVertex);
121+
122+
while (queue.isNotEmpty) {
123+
final vertex = queue.dequeue() as T;
124+
data.add(vertex);
125+
126+
for (final neighbor in _adjacencyList[vertex]!) {
127+
if (!visited.contains(neighbor)) {
128+
visited.add(neighbor);
129+
queue.enqueue(neighbor);
130+
}
131+
}
132+
}
133+
134+
return data;
135+
}
136+
}
137+
138+
void main() {
139+
group('addVertex', () {
140+
test(
141+
'should add vertex to the graph with no neighboring vertices (no edges)',
142+
() {
143+
final graph = Graph<String>();
144+
graph.addVertex('Cairo');
145+
expect(graph._adjacencyList, {'Cairo': []});
146+
});
147+
148+
test('should throw if the vertex already added', () {
149+
final graph = Graph<String>();
150+
graph.addVertex('Cairo');
151+
expect(
152+
() => graph.addVertex('Cairo'),
153+
throwsA(
154+
isA<StateError>()
155+
.having((e) => e.message, 'message', 'vertex already added!'),
156+
),
157+
);
158+
});
159+
});
160+
161+
group('removeVertex', () {
162+
test(
163+
'should remove the vertex and all the edges connected to it'
164+
'(remove the vertex from its neighboring vertices too)', () {
165+
final graph = Graph<String>();
166+
graph._adjacencyList.addAll({
167+
'Cairo': ['Alex', 'Giza'],
168+
'Alex': ['Cairo', 'Giza'],
169+
'Giza': ['Cairo', 'Alex']
170+
});
171+
172+
graph.removeVertex('Cairo');
173+
expect(graph._adjacencyList, {
174+
'Alex': ['Giza'],
175+
'Giza': ['Alex']
176+
});
177+
graph.removeVertex('Alex');
178+
expect(graph._adjacencyList, {'Giza': []});
179+
});
180+
181+
test('should throw if the vertex does not exist', () {
182+
final graph = Graph<String>();
183+
graph.addVertex('Cairo');
184+
expect(
185+
() => graph.removeVertex('Giza'),
186+
throwsA(
187+
isA<StateError>()
188+
.having((e) => e.message, 'message', 'vertex does not exist!'),
189+
),
190+
);
191+
});
192+
});
193+
194+
group('addEdge', () {
195+
test('should add an edge between two vertices (be neighbors to each other)',
196+
() {
197+
final graph = Graph<String>();
198+
199+
graph.addVertex('Cairo');
200+
graph.addVertex('Alex');
201+
graph.addVertex('Giza');
202+
expect(graph._adjacencyList, {'Cairo': [], 'Alex': [], 'Giza': []});
203+
204+
graph.addEdge('Cairo', 'Alex');
205+
expect(graph._adjacencyList, {
206+
'Cairo': ['Alex'],
207+
'Alex': ['Cairo'],
208+
'Giza': []
209+
});
210+
graph.addEdge('Cairo', 'Giza');
211+
expect(graph._adjacencyList, {
212+
'Cairo': ['Alex', 'Giza'],
213+
'Alex': ['Cairo'],
214+
'Giza': ['Cairo']
215+
});
216+
});
217+
218+
test('should throw if either of the vertices does not exist', () {
219+
final graph = Graph<String>();
220+
graph.addVertex('Cairo');
221+
222+
expect(
223+
() => graph.addEdge('Cairo', 'Alexandria'),
224+
throwsA(
225+
isA<StateError>().having((e) => e.message, 'message',
226+
'Either vertex1 or vertex2 does not exist!'),
227+
),
228+
);
229+
});
230+
});
231+
232+
group('removeEdge', () {
233+
test('should remove the edge between two vertices', () {
234+
final graph = Graph<String>();
235+
graph._adjacencyList.addAll({
236+
'Cairo': ['Alex', 'Giza'],
237+
'Alex': ['Cairo'],
238+
'Giza': ['Cairo']
239+
});
240+
241+
graph.removeEdge('Cairo', 'Alex');
242+
expect(graph._adjacencyList, {
243+
'Cairo': ['Giza'],
244+
'Alex': [],
245+
'Giza': ['Cairo']
246+
});
247+
graph.removeEdge('Cairo', 'Giza');
248+
expect(graph._adjacencyList, {'Cairo': [], 'Alex': [], 'Giza': []});
249+
});
250+
251+
test('should throw if either of the vertices does not exist', () {
252+
final graph = Graph<String>();
253+
graph.addVertex('Cairo');
254+
255+
expect(
256+
() => graph.removeEdge('Cairo', 'Alexandria'),
257+
throwsA(
258+
isA<StateError>().having((e) => e.message, 'message',
259+
'Either vertex1 or vertex2 does not exist!'),
260+
),
261+
);
262+
});
263+
});
264+
265+
group('Graph Traversal', () {
266+
test('dfs', () {
267+
final emptyGraph = Graph<String>();
268+
expect(emptyGraph.dfs(''), []);
269+
270+
/// A
271+
/// / \
272+
/// B C
273+
/// | |
274+
/// D ______ E
275+
/// \ /
276+
/// F
277+
final graph = Graph<String>();
278+
graph._adjacencyList.addAll({
279+
'A': ['B', 'C'],
280+
'B': ['A', 'D'],
281+
'C': ['A', 'E'],
282+
'D': ['B', 'E', 'F'],
283+
'E': ['C', 'D', 'F'],
284+
'F': ['D', 'E']
285+
});
286+
expect(graph.dfs('A'), ['A', 'B', 'D', 'E', 'C', 'F']);
287+
});
288+
289+
test('bfs', () {
290+
final emptyGraph = Graph<String>();
291+
expect(emptyGraph.bfs(''), []);
292+
293+
/// A
294+
/// / \
295+
/// B C
296+
/// | |
297+
/// D ______ E
298+
/// \ /
299+
/// F
300+
final graph = Graph<String>();
301+
graph._adjacencyList.addAll({
302+
'A': ['B', 'C'],
303+
'B': ['A', 'D'],
304+
'C': ['A', 'E'],
305+
'D': ['B', 'E', 'F'],
306+
'E': ['C', 'D', 'F'],
307+
'F': ['D', 'E']
308+
});
309+
expect(graph.bfs('A'), ['A', 'B', 'C', 'D', 'E', 'F']);
310+
});
311+
});
312+
}

data_structures/non_linear/trees/binary_trees/binary_tree_traversal.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ extension BinaryTreeTraversalX on BinarySearchTree {
1515
if (_root case final root?) queue.enqueue(root);
1616

1717
Node<num> dequeuedNode;
18-
while (!queue.isEmpty) {
18+
while (queue.isNotEmpty) {
1919
dequeuedNode = queue.dequeue()!;
2020
data.add(dequeuedNode.value);
2121
if (dequeuedNode.left != null) queue.enqueue(dequeuedNode.left!);

0 commit comments

Comments
 (0)