dsa_visualizer 0.1.1 ā 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/dsa_visualizer/algorithms/graph_algorithms.rb +322 -6
- data/lib/dsa_visualizer/algorithms/sorting.rb +106 -2
- data/lib/dsa_visualizer/data_structures/bst.rb +303 -2
- data/lib/dsa_visualizer/data_structures/deque.rb +208 -2
- data/lib/dsa_visualizer/data_structures/doubly_linked_list.rb +255 -2
- data/lib/dsa_visualizer/data_structures/graph.rb +191 -2
- data/lib/dsa_visualizer/data_structures/heap.rb +327 -4
- data/lib/dsa_visualizer/data_structures/priority_queue.rb +214 -2
- data/lib/dsa_visualizer/data_structures/trie.rb +261 -2
- data/lib/dsa_visualizer/data_structures/union_find.rb +207 -2
- metadata +5 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 813e5d18b85fdd51dd758e48c77df855602225932446e34909a544c90e8609b4
|
|
4
|
+
data.tar.gz: ba55ef09dba874a9b77ee6dba7fc653da234f93c507c38bf8cf99991af476684
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 19f0790fe49b8c8166ed254e8fdfdaefee18a1c343f4d950597b3396d332950d730d7d073a493c46ebc7d0d8a9f90a19818d1b5db037e20e7fea1205784bcd31
|
|
7
|
+
data.tar.gz: d9621677e2ba21185eb87b7d3081504971ada5b413f0a3243f0a2744064ce219ec97732d0f4b279fb360fead131693273aa3f2f0943f12744cbdf07903372850
|
|
@@ -3,20 +3,336 @@ module DSAVisualizer
|
|
|
3
3
|
class GraphAlgorithms
|
|
4
4
|
def self.learn_bfs
|
|
5
5
|
Visualizer.print_header("BREADTH-FIRST SEARCH (BFS)")
|
|
6
|
-
|
|
7
|
-
puts "
|
|
6
|
+
|
|
7
|
+
puts "\nš CONCEPT:"
|
|
8
|
+
puts "BFS explores graph level by level using a queue:"
|
|
9
|
+
puts "⢠Start from source vertex"
|
|
10
|
+
puts "⢠Visit all neighbors before going deeper"
|
|
11
|
+
puts "⢠Uses queue (FIFO) for traversal"
|
|
12
|
+
puts "⢠Finds shortest path in unweighted graphs"
|
|
13
|
+
|
|
14
|
+
puts "\nā±ļø TIME COMPLEXITY: O(V + E)"
|
|
15
|
+
puts "š¾ SPACE COMPLEXITY: O(V) - for queue and visited set"
|
|
16
|
+
|
|
17
|
+
demonstrate_bfs
|
|
18
|
+
show_ruby_vs_cpp_bfs
|
|
8
19
|
end
|
|
9
20
|
|
|
10
21
|
def self.learn_dfs
|
|
11
22
|
Visualizer.print_header("DEPTH-FIRST SEARCH (DFS)")
|
|
12
|
-
|
|
13
|
-
puts "
|
|
23
|
+
|
|
24
|
+
puts "\nš CONCEPT:"
|
|
25
|
+
puts "DFS explores as deep as possible before backtracking:"
|
|
26
|
+
puts "⢠Start from source vertex"
|
|
27
|
+
puts "⢠Go deep into one path before exploring others"
|
|
28
|
+
puts "⢠Uses stack (recursion or explicit)"
|
|
29
|
+
puts "⢠Used for cycle detection, topological sort"
|
|
30
|
+
|
|
31
|
+
puts "\nā±ļø TIME COMPLEXITY: O(V + E)"
|
|
32
|
+
puts "š¾ SPACE COMPLEXITY: O(V) - for recursion stack"
|
|
33
|
+
|
|
34
|
+
demonstrate_dfs
|
|
35
|
+
show_ruby_vs_cpp_dfs
|
|
14
36
|
end
|
|
15
37
|
|
|
16
38
|
def self.learn_dijkstra
|
|
17
39
|
Visualizer.print_header("DIJKSTRA'S ALGORITHM")
|
|
18
|
-
|
|
19
|
-
puts "
|
|
40
|
+
|
|
41
|
+
puts "\nš CONCEPT:"
|
|
42
|
+
puts "Dijkstra finds shortest path in weighted graphs:"
|
|
43
|
+
puts "⢠Greedy algorithm"
|
|
44
|
+
puts "⢠Uses priority queue (min heap)"
|
|
45
|
+
puts "⢠Works only with non-negative weights"
|
|
46
|
+
puts "⢠Finds shortest path from source to all vertices"
|
|
47
|
+
|
|
48
|
+
puts "\nā±ļø TIME COMPLEXITY: O((V + E) log V) with min heap"
|
|
49
|
+
puts "š¾ SPACE COMPLEXITY: O(V)"
|
|
50
|
+
|
|
51
|
+
demonstrate_dijkstra
|
|
52
|
+
show_ruby_vs_cpp_dijkstra
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def self.demonstrate_bfs
|
|
56
|
+
puts "\n" + "="*60
|
|
57
|
+
puts "BFS DEMONSTRATION"
|
|
58
|
+
puts "="*60
|
|
59
|
+
|
|
60
|
+
graph = {
|
|
61
|
+
'A' => ['B', 'C'],
|
|
62
|
+
'B' => ['D', 'E'],
|
|
63
|
+
'C' => ['F'],
|
|
64
|
+
'D' => [],
|
|
65
|
+
'E' => ['F'],
|
|
66
|
+
'F' => []
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
puts "\nGraph:"
|
|
70
|
+
puts " A"
|
|
71
|
+
puts " / \\"
|
|
72
|
+
puts " B C"
|
|
73
|
+
puts " / \\ \\"
|
|
74
|
+
puts "D E F"
|
|
75
|
+
|
|
76
|
+
puts "\nBFS Traversal from A:"
|
|
77
|
+
result = bfs(graph, 'A')
|
|
78
|
+
puts "Order: #{result.join(' ā ')}"
|
|
79
|
+
|
|
80
|
+
puts "\nLevel-by-level:"
|
|
81
|
+
puts "Level 0: A"
|
|
82
|
+
puts "Level 1: B, C"
|
|
83
|
+
puts "Level 2: D, E, F"
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def self.demonstrate_dfs
|
|
87
|
+
puts "\n" + "="*60
|
|
88
|
+
puts "DFS DEMONSTRATION"
|
|
89
|
+
puts "="*60
|
|
90
|
+
|
|
91
|
+
graph = {
|
|
92
|
+
'A' => ['B', 'C'],
|
|
93
|
+
'B' => ['D', 'E'],
|
|
94
|
+
'C' => ['F'],
|
|
95
|
+
'D' => [],
|
|
96
|
+
'E' => ['F'],
|
|
97
|
+
'F' => []
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
puts "\nGraph:"
|
|
101
|
+
puts " A"
|
|
102
|
+
puts " / \\"
|
|
103
|
+
puts " B C"
|
|
104
|
+
puts " / \\ \\"
|
|
105
|
+
puts "D E F"
|
|
106
|
+
|
|
107
|
+
puts "\nDFS Traversal from A:"
|
|
108
|
+
result = dfs(graph, 'A')
|
|
109
|
+
puts "Order: #{result.join(' ā ')}"
|
|
110
|
+
|
|
111
|
+
puts "\nPath exploration:"
|
|
112
|
+
puts "A ā B ā D (backtrack)"
|
|
113
|
+
puts "B ā E ā F (backtrack)"
|
|
114
|
+
puts "A ā C ā F (already visited)"
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def self.demonstrate_dijkstra
|
|
118
|
+
puts "\n" + "="*60
|
|
119
|
+
puts "DIJKSTRA'S ALGORITHM DEMONSTRATION"
|
|
120
|
+
puts "="*60
|
|
121
|
+
|
|
122
|
+
graph = {
|
|
123
|
+
'A' => [['B', 4], ['C', 2]],
|
|
124
|
+
'B' => [['D', 5]],
|
|
125
|
+
'C' => [['B', 1], ['D', 8]],
|
|
126
|
+
'D' => []
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
puts "\nWeighted Graph:"
|
|
130
|
+
puts " A"
|
|
131
|
+
puts " /4\\2"
|
|
132
|
+
puts " B C"
|
|
133
|
+
puts " |5 /8"
|
|
134
|
+
puts " D"
|
|
135
|
+
|
|
136
|
+
puts "\nFinding shortest paths from A:"
|
|
137
|
+
distances = dijkstra(graph, 'A')
|
|
138
|
+
distances.each do |vertex, dist|
|
|
139
|
+
puts "A ā #{vertex}: #{dist}"
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def self.bfs(graph, start)
|
|
144
|
+
visited = Set.new
|
|
145
|
+
queue = [start]
|
|
146
|
+
result = []
|
|
147
|
+
|
|
148
|
+
until queue.empty?
|
|
149
|
+
vertex = queue.shift
|
|
150
|
+
next if visited.include?(vertex)
|
|
151
|
+
|
|
152
|
+
visited.add(vertex)
|
|
153
|
+
result << vertex
|
|
154
|
+
|
|
155
|
+
graph[vertex].each do |neighbor|
|
|
156
|
+
queue << neighbor unless visited.include?(neighbor)
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
result
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def self.dfs(graph, start, visited = Set.new, result = [])
|
|
164
|
+
return result if visited.include?(start)
|
|
165
|
+
|
|
166
|
+
visited.add(start)
|
|
167
|
+
result << start
|
|
168
|
+
|
|
169
|
+
graph[start].each do |neighbor|
|
|
170
|
+
dfs(graph, neighbor, visited, result)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
result
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def self.dijkstra(graph, start)
|
|
177
|
+
distances = Hash.new(Float::INFINITY)
|
|
178
|
+
distances[start] = 0
|
|
179
|
+
pq = [[0, start]]
|
|
180
|
+
visited = Set.new
|
|
181
|
+
|
|
182
|
+
until pq.empty?
|
|
183
|
+
dist, vertex = pq.min_by { |d, _| d }
|
|
184
|
+
pq.delete([dist, vertex])
|
|
185
|
+
|
|
186
|
+
next if visited.include?(vertex)
|
|
187
|
+
visited.add(vertex)
|
|
188
|
+
|
|
189
|
+
graph[vertex].each do |neighbor, weight|
|
|
190
|
+
new_dist = distances[vertex] + weight
|
|
191
|
+
if new_dist < distances[neighbor]
|
|
192
|
+
distances[neighbor] = new_dist
|
|
193
|
+
pq << [new_dist, neighbor]
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
distances
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def self.show_ruby_vs_cpp_bfs
|
|
202
|
+
puts "\n" + "="*60
|
|
203
|
+
puts "RUBY vs C++ - BFS"
|
|
204
|
+
puts "="*60
|
|
205
|
+
|
|
206
|
+
puts "\nš“ RUBY:"
|
|
207
|
+
puts <<~RUBY
|
|
208
|
+
def bfs(graph, start)
|
|
209
|
+
visited = Set.new
|
|
210
|
+
queue = [start]
|
|
211
|
+
|
|
212
|
+
until queue.empty?
|
|
213
|
+
vertex = queue.shift
|
|
214
|
+
next if visited.include?(vertex)
|
|
215
|
+
|
|
216
|
+
visited.add(vertex)
|
|
217
|
+
puts vertex
|
|
218
|
+
|
|
219
|
+
graph[vertex].each do |neighbor|
|
|
220
|
+
queue << neighbor unless visited.include?(neighbor)
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
RUBY
|
|
225
|
+
|
|
226
|
+
puts "\nšµ C++:"
|
|
227
|
+
puts <<~CPP
|
|
228
|
+
void bfs(unordered_map<int, vector<int>>& graph, int start) {
|
|
229
|
+
unordered_set<int> visited;
|
|
230
|
+
queue<int> q;
|
|
231
|
+
q.push(start);
|
|
232
|
+
|
|
233
|
+
while (!q.empty()) {
|
|
234
|
+
int vertex = q.front();
|
|
235
|
+
q.pop();
|
|
236
|
+
|
|
237
|
+
if (visited.count(vertex)) continue;
|
|
238
|
+
visited.insert(vertex);
|
|
239
|
+
cout << vertex << endl;
|
|
240
|
+
|
|
241
|
+
for (int neighbor : graph[vertex]) {
|
|
242
|
+
if (!visited.count(neighbor))
|
|
243
|
+
q.push(neighbor);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
CPP
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def self.show_ruby_vs_cpp_dfs
|
|
251
|
+
puts "\n" + "="*60
|
|
252
|
+
puts "RUBY vs C++ - DFS"
|
|
253
|
+
puts "="*60
|
|
254
|
+
|
|
255
|
+
puts "\nš“ RUBY:"
|
|
256
|
+
puts <<~RUBY
|
|
257
|
+
def dfs(graph, vertex, visited = Set.new)
|
|
258
|
+
return if visited.include?(vertex)
|
|
259
|
+
|
|
260
|
+
visited.add(vertex)
|
|
261
|
+
puts vertex
|
|
262
|
+
|
|
263
|
+
graph[vertex].each do |neighbor|
|
|
264
|
+
dfs(graph, neighbor, visited)
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
RUBY
|
|
268
|
+
|
|
269
|
+
puts "\nšµ C++:"
|
|
270
|
+
puts <<~CPP
|
|
271
|
+
void dfs(unordered_map<int, vector<int>>& graph,
|
|
272
|
+
int vertex, unordered_set<int>& visited) {
|
|
273
|
+
if (visited.count(vertex)) return;
|
|
274
|
+
|
|
275
|
+
visited.insert(vertex);
|
|
276
|
+
cout << vertex << endl;
|
|
277
|
+
|
|
278
|
+
for (int neighbor : graph[vertex]) {
|
|
279
|
+
dfs(graph, neighbor, visited);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
CPP
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
def self.show_ruby_vs_cpp_dijkstra
|
|
286
|
+
puts "\n" + "="*60
|
|
287
|
+
puts "RUBY vs C++ - DIJKSTRA"
|
|
288
|
+
puts "="*60
|
|
289
|
+
|
|
290
|
+
puts "\nš“ RUBY:"
|
|
291
|
+
puts <<~RUBY
|
|
292
|
+
def dijkstra(graph, start)
|
|
293
|
+
distances = Hash.new(Float::INFINITY)
|
|
294
|
+
distances[start] = 0
|
|
295
|
+
pq = [[0, start]]
|
|
296
|
+
|
|
297
|
+
until pq.empty?
|
|
298
|
+
dist, vertex = pq.min_by { |d, _| d }
|
|
299
|
+
pq.delete([dist, vertex])
|
|
300
|
+
|
|
301
|
+
graph[vertex].each do |neighbor, weight|
|
|
302
|
+
new_dist = distances[vertex] + weight
|
|
303
|
+
if new_dist < distances[neighbor]
|
|
304
|
+
distances[neighbor] = new_dist
|
|
305
|
+
pq << [new_dist, neighbor]
|
|
306
|
+
end
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
distances
|
|
310
|
+
end
|
|
311
|
+
RUBY
|
|
312
|
+
|
|
313
|
+
puts "\nšµ C++:"
|
|
314
|
+
puts <<~CPP
|
|
315
|
+
map<int, int> dijkstra(map<int, vector<pair<int,int>>>& graph, int start) {
|
|
316
|
+
map<int, int> distances;
|
|
317
|
+
priority_queue<pair<int,int>, vector<pair<int,int>>, greater<>> pq;
|
|
318
|
+
pq.push({0, start});
|
|
319
|
+
distances[start] = 0;
|
|
320
|
+
|
|
321
|
+
while (!pq.empty()) {
|
|
322
|
+
auto [dist, vertex] = pq.top();
|
|
323
|
+
pq.pop();
|
|
324
|
+
|
|
325
|
+
for (auto [neighbor, weight] : graph[vertex]) {
|
|
326
|
+
int newDist = distances[vertex] + weight;
|
|
327
|
+
if (newDist < distances[neighbor]) {
|
|
328
|
+
distances[neighbor] = newDist;
|
|
329
|
+
pq.push({newDist, neighbor});
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return distances;
|
|
334
|
+
}
|
|
335
|
+
CPP
|
|
20
336
|
end
|
|
21
337
|
end
|
|
22
338
|
end
|
|
@@ -6,11 +6,115 @@ module DSAVisualizer
|
|
|
6
6
|
end
|
|
7
7
|
|
|
8
8
|
def self.learn_merge
|
|
9
|
-
|
|
9
|
+
Visualizer.print_header("MERGE SORT - Divide and Conquer")
|
|
10
|
+
|
|
11
|
+
puts "\nš CONCEPT:"
|
|
12
|
+
puts "Merge Sort divides array into halves, sorts them, and merges:"
|
|
13
|
+
puts "⢠Divide: Split array into two halves"
|
|
14
|
+
puts "⢠Conquer: Recursively sort both halves"
|
|
15
|
+
puts "⢠Combine: Merge sorted halves"
|
|
16
|
+
puts "⢠Stable sort, predictable performance"
|
|
17
|
+
|
|
18
|
+
puts "\nā±ļø TIME COMPLEXITY: O(n log n) - all cases"
|
|
19
|
+
puts "š¾ SPACE COMPLEXITY: O(n) - needs auxiliary array"
|
|
20
|
+
|
|
21
|
+
demonstrate_merge_sort
|
|
10
22
|
end
|
|
11
23
|
|
|
12
24
|
def self.learn_quick
|
|
13
|
-
|
|
25
|
+
Visualizer.print_header("QUICK SORT - Partition-based")
|
|
26
|
+
|
|
27
|
+
puts "\nš CONCEPT:"
|
|
28
|
+
puts "Quick Sort picks a pivot and partitions around it:"
|
|
29
|
+
puts "⢠Choose pivot element"
|
|
30
|
+
puts "⢠Partition: elements < pivot on left, > pivot on right"
|
|
31
|
+
puts "⢠Recursively sort left and right partitions"
|
|
32
|
+
puts "⢠In-place sorting, cache-friendly"
|
|
33
|
+
|
|
34
|
+
puts "\nā±ļø TIME COMPLEXITY:"
|
|
35
|
+
puts "⢠Average: O(n log n)"
|
|
36
|
+
puts "⢠Worst: O(n²) - when pivot is always min/max"
|
|
37
|
+
puts "š¾ SPACE COMPLEXITY: O(log n) - recursion stack"
|
|
38
|
+
|
|
39
|
+
demonstrate_quick_sort
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def self.demonstrate_merge_sort
|
|
43
|
+
puts "\n" + "="*60
|
|
44
|
+
puts "MERGE SORT DEMONSTRATION"
|
|
45
|
+
puts "="*60
|
|
46
|
+
|
|
47
|
+
arr = [38, 27, 43, 3, 9, 82, 10]
|
|
48
|
+
puts "\nOriginal: #{arr.inspect}"
|
|
49
|
+
|
|
50
|
+
puts "\nDivide phase:"
|
|
51
|
+
puts "[38, 27, 43, 3, 9, 82, 10]"
|
|
52
|
+
puts "[38, 27, 43, 3] | [9, 82, 10]"
|
|
53
|
+
puts "[38, 27] [43, 3] | [9, 82] [10]"
|
|
54
|
+
puts "[38] [27] [43] [3] | [9] [82] [10]"
|
|
55
|
+
|
|
56
|
+
puts "\nMerge phase:"
|
|
57
|
+
puts "[27, 38] [3, 43] | [9, 82] [10]"
|
|
58
|
+
puts "[3, 27, 38, 43] | [9, 10, 82]"
|
|
59
|
+
puts "[3, 9, 10, 27, 38, 43, 82]"
|
|
60
|
+
|
|
61
|
+
sorted = merge_sort(arr.dup)
|
|
62
|
+
puts "\nSorted: #{sorted.inspect}"
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def self.demonstrate_quick_sort
|
|
66
|
+
puts "\n" + "="*60
|
|
67
|
+
puts "QUICK SORT DEMONSTRATION"
|
|
68
|
+
puts "="*60
|
|
69
|
+
|
|
70
|
+
arr = [10, 7, 8, 9, 1, 5]
|
|
71
|
+
puts "\nOriginal: #{arr.inspect}"
|
|
72
|
+
puts "Pivot: #{arr.last} (choosing last element)"
|
|
73
|
+
|
|
74
|
+
puts "\nPartitioning:"
|
|
75
|
+
puts "Elements < 5: [1]"
|
|
76
|
+
puts "Pivot: [5]"
|
|
77
|
+
puts "Elements > 5: [10, 7, 8, 9]"
|
|
78
|
+
|
|
79
|
+
sorted = quick_sort(arr.dup)
|
|
80
|
+
puts "\nSorted: #{sorted.inspect}"
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def self.merge_sort(arr)
|
|
84
|
+
return arr if arr.length <= 1
|
|
85
|
+
|
|
86
|
+
mid = arr.length / 2
|
|
87
|
+
left = merge_sort(arr[0...mid])
|
|
88
|
+
right = merge_sort(arr[mid..-1])
|
|
89
|
+
|
|
90
|
+
merge(left, right)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def self.merge(left, right)
|
|
94
|
+
result = []
|
|
95
|
+
i = j = 0
|
|
96
|
+
|
|
97
|
+
while i < left.length && j < right.length
|
|
98
|
+
if left[i] <= right[j]
|
|
99
|
+
result << left[i]
|
|
100
|
+
i += 1
|
|
101
|
+
else
|
|
102
|
+
result << right[j]
|
|
103
|
+
j += 1
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
result + left[i..-1] + right[j..-1]
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def self.quick_sort(arr)
|
|
111
|
+
return arr if arr.length <= 1
|
|
112
|
+
|
|
113
|
+
pivot = arr.pop
|
|
114
|
+
left = arr.select { |x| x <= pivot }
|
|
115
|
+
right = arr.select { |x| x > pivot }
|
|
116
|
+
|
|
117
|
+
quick_sort(left) + [pivot] + quick_sort(right)
|
|
14
118
|
end
|
|
15
119
|
|
|
16
120
|
def self.demo
|