dsa_visualizer 0.1.0
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 +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +323 -0
- data/USAGE.md +359 -0
- data/bin/dsa_visualizer +5 -0
- data/lib/dsa_visualizer/algorithms/dynamic_programming.rb +23 -0
- data/lib/dsa_visualizer/algorithms/graph_algorithms.rb +23 -0
- data/lib/dsa_visualizer/algorithms/greedy.rb +11 -0
- data/lib/dsa_visualizer/algorithms/searching.rb +78 -0
- data/lib/dsa_visualizer/algorithms/sorting.rb +77 -0
- data/lib/dsa_visualizer/algorithms/string_algorithms.rb +11 -0
- data/lib/dsa_visualizer/cli.rb +281 -0
- data/lib/dsa_visualizer/comparator.rb +57 -0
- data/lib/dsa_visualizer/data_structures/array.rb +109 -0
- data/lib/dsa_visualizer/data_structures/binary_tree.rb +104 -0
- data/lib/dsa_visualizer/data_structures/bst.rb +11 -0
- data/lib/dsa_visualizer/data_structures/deque.rb +11 -0
- data/lib/dsa_visualizer/data_structures/doubly_linked_list.rb +11 -0
- data/lib/dsa_visualizer/data_structures/graph.rb +11 -0
- data/lib/dsa_visualizer/data_structures/hash_table.rb +61 -0
- data/lib/dsa_visualizer/data_structures/heap.rb +17 -0
- data/lib/dsa_visualizer/data_structures/linked_list.rb +197 -0
- data/lib/dsa_visualizer/data_structures/priority_queue.rb +11 -0
- data/lib/dsa_visualizer/data_structures/queue.rb +110 -0
- data/lib/dsa_visualizer/data_structures/stack.rb +207 -0
- data/lib/dsa_visualizer/data_structures/string.rb +11 -0
- data/lib/dsa_visualizer/data_structures/trie.rb +11 -0
- data/lib/dsa_visualizer/data_structures/union_find.rb +11 -0
- data/lib/dsa_visualizer/fundamentals/complexity.rb +264 -0
- data/lib/dsa_visualizer/fundamentals/memory.rb +285 -0
- data/lib/dsa_visualizer/fundamentals/pointers.rb +311 -0
- data/lib/dsa_visualizer/fundamentals/recursion.rb +63 -0
- data/lib/dsa_visualizer/memory_tracker.rb +49 -0
- data/lib/dsa_visualizer/notes_manager.rb +85 -0
- data/lib/dsa_visualizer/version.rb +3 -0
- data/lib/dsa_visualizer/visualizer.rb +58 -0
- data/lib/dsa_visualizer.rb +114 -0
- metadata +157 -0
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
module DSAVisualizer
|
|
2
|
+
module DataStructures
|
|
3
|
+
class Stack
|
|
4
|
+
def initialize
|
|
5
|
+
@items = []
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def push(item)
|
|
9
|
+
@items.push(item)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def pop
|
|
13
|
+
@items.pop
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def peek
|
|
17
|
+
@items.last
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def empty?
|
|
21
|
+
@items.empty?
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def size
|
|
25
|
+
@items.size
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def visualize
|
|
29
|
+
return "Empty stack" if @items.empty?
|
|
30
|
+
|
|
31
|
+
result = "\n"
|
|
32
|
+
@items.reverse.each_with_index do |item, idx|
|
|
33
|
+
marker = (idx == 0) ? " ← TOP" : ""
|
|
34
|
+
result += " │ #{item.to_s.center(10)} │#{marker}\n"
|
|
35
|
+
result += " └────────────┘\n" if idx == @items.size - 1
|
|
36
|
+
result += " ├────────────┤\n" unless idx == @items.size - 1
|
|
37
|
+
end
|
|
38
|
+
result
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def self.learn
|
|
42
|
+
demo
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def self.demo
|
|
46
|
+
Visualizer.print_header("STACK - Core Level Visualization")
|
|
47
|
+
|
|
48
|
+
# Concept
|
|
49
|
+
Visualizer.print_section("1. Stack Concept (LIFO)")
|
|
50
|
+
puts "\nLast In, First Out - like a stack of plates"
|
|
51
|
+
puts "Operations: push (add), pop (remove), peek (view top)"
|
|
52
|
+
|
|
53
|
+
# Implementation Comparison
|
|
54
|
+
Visualizer.print_section("2. Implementation Comparison")
|
|
55
|
+
|
|
56
|
+
ruby_code = <<~RUBY
|
|
57
|
+
class Stack
|
|
58
|
+
def initialize
|
|
59
|
+
@items = [] # Dynamic array
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def push(item)
|
|
63
|
+
@items.push(item) # O(1) amortized
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def pop
|
|
67
|
+
@items.pop # O(1)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
RUBY
|
|
71
|
+
|
|
72
|
+
cpp_code = <<~CPP
|
|
73
|
+
class Stack {
|
|
74
|
+
std::vector<int> items;
|
|
75
|
+
public:
|
|
76
|
+
void push(int item) {
|
|
77
|
+
items.push_back(item); // O(1) amortized
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
int pop() {
|
|
81
|
+
int top = items.back();
|
|
82
|
+
items.pop_back(); // O(1)
|
|
83
|
+
return top;
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
CPP
|
|
87
|
+
|
|
88
|
+
explanation = "Both use dynamic arrays internally. Ruby's Array and C++'s std::vector provide O(1) push/pop at end. Alternative: linked list implementation trades memory for guaranteed O(1) operations."
|
|
89
|
+
|
|
90
|
+
Visualizer.print_comparison(ruby_code, cpp_code, explanation)
|
|
91
|
+
|
|
92
|
+
# Demo Operations
|
|
93
|
+
Visualizer.print_section("3. Stack Operations")
|
|
94
|
+
tracker = MemoryTracker.new
|
|
95
|
+
stack = Stack.new
|
|
96
|
+
|
|
97
|
+
# Push operations
|
|
98
|
+
Visualizer.print_step(1, "Pushing elements: 10, 20, 30")
|
|
99
|
+
[10, 20, 30].each do |val|
|
|
100
|
+
stack.push(val)
|
|
101
|
+
tracker.track_operation("Push", "Pushed #{val}")
|
|
102
|
+
puts stack.visualize
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
puts "\n🔍 Push Internals (Ruby):"
|
|
106
|
+
puts " 1. Check array capacity"
|
|
107
|
+
puts " 2. If full: allocate new array (2× size)"
|
|
108
|
+
puts " 3. Copy existing elements (if resized)"
|
|
109
|
+
puts " 4. Add element at end"
|
|
110
|
+
puts " 5. Increment size counter"
|
|
111
|
+
puts " Amortized O(1) - occasional O(n) resize"
|
|
112
|
+
|
|
113
|
+
puts "\n🔍 Push Internals (C++):"
|
|
114
|
+
puts " 1. Check vector capacity"
|
|
115
|
+
puts " 2. If full: allocate new memory"
|
|
116
|
+
puts " 3. Move/copy elements (if resized)"
|
|
117
|
+
puts " 4. Place element at end"
|
|
118
|
+
puts " 5. Update size"
|
|
119
|
+
puts " Amortized O(1) - same as Ruby"
|
|
120
|
+
|
|
121
|
+
# Peek operation
|
|
122
|
+
Visualizer.print_step(2, "Peeking at top element")
|
|
123
|
+
top = stack.peek
|
|
124
|
+
puts "Top element: #{top}".colorize(:yellow)
|
|
125
|
+
puts stack.visualize
|
|
126
|
+
tracker.track_operation("Peek", "Viewed top: #{top}")
|
|
127
|
+
|
|
128
|
+
# Pop operations
|
|
129
|
+
Visualizer.print_step(3, "Popping elements")
|
|
130
|
+
2.times do
|
|
131
|
+
popped = stack.pop
|
|
132
|
+
tracker.track_operation("Pop", "Popped #{popped}")
|
|
133
|
+
puts "\nPopped: #{popped}".colorize(:red)
|
|
134
|
+
puts stack.visualize
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
puts "\n🔍 Pop Internals:"
|
|
138
|
+
puts " Ruby:"
|
|
139
|
+
puts " 1. Check if empty (raise error if true)"
|
|
140
|
+
puts " 2. Get last element"
|
|
141
|
+
puts " 3. Decrement size"
|
|
142
|
+
puts " 4. Return element"
|
|
143
|
+
puts " O(1) operation"
|
|
144
|
+
|
|
145
|
+
puts "\n C++:"
|
|
146
|
+
puts " 1. Access last element (no bounds check)"
|
|
147
|
+
puts " 2. Call destructor if needed"
|
|
148
|
+
puts " 3. Decrement size"
|
|
149
|
+
puts " 4. Return value"
|
|
150
|
+
puts " O(1) operation"
|
|
151
|
+
|
|
152
|
+
# Memory Layout
|
|
153
|
+
Visualizer.print_section("4. Memory Layout")
|
|
154
|
+
|
|
155
|
+
puts "\n📦 Array-based Stack Memory:"
|
|
156
|
+
puts " Ruby:"
|
|
157
|
+
puts " Stack object → Array object → [ref1, ref2, ref3, ...]"
|
|
158
|
+
puts " Each element is object reference"
|
|
159
|
+
puts " Overhead: Array header + references"
|
|
160
|
+
|
|
161
|
+
puts "\n C++:"
|
|
162
|
+
puts " Stack object → vector → [val1, val2, val3, ...]"
|
|
163
|
+
puts " Direct values in contiguous memory"
|
|
164
|
+
puts " Overhead: vector metadata only"
|
|
165
|
+
|
|
166
|
+
# Use Cases
|
|
167
|
+
Visualizer.print_section("5. Real-World Use Cases")
|
|
168
|
+
|
|
169
|
+
puts "\n📚 Common Applications:"
|
|
170
|
+
puts " 1. Function call stack (recursion)"
|
|
171
|
+
puts " 2. Undo/Redo operations"
|
|
172
|
+
puts " 3. Expression evaluation (postfix)"
|
|
173
|
+
puts " 4. Backtracking algorithms"
|
|
174
|
+
puts " 5. Browser history (back button)"
|
|
175
|
+
|
|
176
|
+
puts "\n💡 Example: Function Call Stack"
|
|
177
|
+
puts " def factorial(n)"
|
|
178
|
+
puts " return 1 if n <= 1"
|
|
179
|
+
puts " n * factorial(n - 1) # Each call pushed to stack"
|
|
180
|
+
puts " end"
|
|
181
|
+
puts "\n Call stack for factorial(3):"
|
|
182
|
+
puts " │ factorial(1) │ ← TOP (returns 1)"
|
|
183
|
+
puts " ├──────────────┤"
|
|
184
|
+
puts " │ factorial(2) │ (waits for result)"
|
|
185
|
+
puts " ├──────────────┤"
|
|
186
|
+
puts " │ factorial(3) │ (waits for result)"
|
|
187
|
+
puts " └──────────────┘"
|
|
188
|
+
|
|
189
|
+
tracker.print_summary
|
|
190
|
+
|
|
191
|
+
Comparator.compare_complexity(
|
|
192
|
+
"Stack Operations",
|
|
193
|
+
"Push/Pop: O(1) amortized, Peek: O(1)",
|
|
194
|
+
"Push/Pop: O(1) amortized, Peek: O(1)",
|
|
195
|
+
"Both implementations have same complexity. C++ is faster due to direct value storage vs Ruby's object references."
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
puts "\n\n🎯 Key Takeaways:".colorize(:green).bold
|
|
199
|
+
puts " 1. Stack is LIFO - Last In, First Out"
|
|
200
|
+
puts " 2. Array-based implementation provides O(1) operations"
|
|
201
|
+
puts " 3. Both Ruby and C++ use dynamic arrays internally"
|
|
202
|
+
puts " 4. Perfect for reversing, backtracking, and nested structures"
|
|
203
|
+
puts " 5. Function calls use stack internally (call stack)"
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
module DSAVisualizer
|
|
2
|
+
module DataStructures
|
|
3
|
+
class String
|
|
4
|
+
def self.learn
|
|
5
|
+
Visualizer.print_header("STRINGS - Character Arrays with Special Properties")
|
|
6
|
+
puts "\n📚 Coming soon: String manipulation, encoding, pattern matching"
|
|
7
|
+
puts "Topics: ASCII vs Unicode, string immutability, common operations"
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
module DSAVisualizer
|
|
2
|
+
module DataStructures
|
|
3
|
+
class Trie
|
|
4
|
+
def self.learn
|
|
5
|
+
Visualizer.print_header("TRIE - Prefix Tree")
|
|
6
|
+
puts "\n📚 Coming soon: String storage and search"
|
|
7
|
+
puts "Topics: Insert, search, prefix matching, autocomplete"
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
module DSAVisualizer
|
|
2
|
+
module DataStructures
|
|
3
|
+
class UnionFind
|
|
4
|
+
def self.learn
|
|
5
|
+
Visualizer.print_header("UNION-FIND (Disjoint Set)")
|
|
6
|
+
puts "\n📚 Coming soon: Connected components"
|
|
7
|
+
puts "Topics: Union by rank, path compression, applications in graphs"
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
module DSAVisualizer
|
|
2
|
+
module Fundamentals
|
|
3
|
+
class Complexity
|
|
4
|
+
def self.learn
|
|
5
|
+
Visualizer.print_header("TIME & SPACE COMPLEXITY - Foundation of Algorithm Analysis")
|
|
6
|
+
|
|
7
|
+
# Introduction
|
|
8
|
+
puts "\n📖 WHAT IS COMPLEXITY ANALYSIS?".colorize(:cyan).bold
|
|
9
|
+
puts "─" * 80
|
|
10
|
+
puts "Complexity analysis helps us understand how an algorithm's performance"
|
|
11
|
+
puts "scales with input size. We measure two things:"
|
|
12
|
+
puts " 1. Time Complexity - How execution time grows"
|
|
13
|
+
puts " 2. Space Complexity - How memory usage grows"
|
|
14
|
+
|
|
15
|
+
# Big O Notation
|
|
16
|
+
Visualizer.print_section("1. Big O Notation")
|
|
17
|
+
puts "\nBig O describes the upper bound (worst case) of algorithm growth."
|
|
18
|
+
puts "\nCommon Time Complexities (from best to worst):"
|
|
19
|
+
|
|
20
|
+
complexities = [
|
|
21
|
+
["O(1)", "Constant", "Array access, hash lookup"],
|
|
22
|
+
["O(log n)", "Logarithmic", "Binary search, balanced tree ops"],
|
|
23
|
+
["O(n)", "Linear", "Array traversal, linear search"],
|
|
24
|
+
["O(n log n)", "Linearithmic", "Merge sort, quick sort (avg)"],
|
|
25
|
+
["O(n²)", "Quadratic", "Bubble sort, nested loops"],
|
|
26
|
+
["O(n³)", "Cubic", "Triple nested loops"],
|
|
27
|
+
["O(2ⁿ)", "Exponential", "Recursive fibonacci, subsets"],
|
|
28
|
+
["O(n!)", "Factorial", "Permutations, traveling salesman"]
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
complexities.each do |notation, name, example|
|
|
32
|
+
puts " #{notation.ljust(12)} #{name.ljust(15)} → #{example}".colorize(:light_blue)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Visual Growth Comparison
|
|
36
|
+
Visualizer.print_section("2. Growth Rate Visualization")
|
|
37
|
+
puts "\nFor n = 100:"
|
|
38
|
+
puts " O(1) = 1 operation"
|
|
39
|
+
puts " O(log n) = 7 operations"
|
|
40
|
+
puts " O(n) = 100 operations"
|
|
41
|
+
puts " O(n log n)= 700 operations"
|
|
42
|
+
puts " O(n²) = 10,000 operations"
|
|
43
|
+
puts " O(2ⁿ) = 1,267,650,600,228,229,401,496,703,205,376 operations 💥"
|
|
44
|
+
|
|
45
|
+
# Example 1: O(1) - Constant Time
|
|
46
|
+
Visualizer.print_section("3. Example: O(1) - Constant Time")
|
|
47
|
+
|
|
48
|
+
ruby_code = <<~RUBY
|
|
49
|
+
def get_first_element(arr)
|
|
50
|
+
arr[0] # Always 1 operation
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Time: O(1)
|
|
54
|
+
# No matter if array has 10 or 10,000 elements
|
|
55
|
+
RUBY
|
|
56
|
+
|
|
57
|
+
cpp_code = <<~CPP
|
|
58
|
+
int getFirstElement(int arr[]) {
|
|
59
|
+
return arr[0]; // Direct memory access
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Time: O(1)
|
|
63
|
+
// Single memory read operation
|
|
64
|
+
CPP
|
|
65
|
+
|
|
66
|
+
Visualizer.print_comparison(ruby_code, cpp_code,
|
|
67
|
+
"Both are O(1). Array indexing is constant time in both languages.")
|
|
68
|
+
|
|
69
|
+
# Example 2: O(n) - Linear Time
|
|
70
|
+
Visualizer.print_section("4. Example: O(n) - Linear Time")
|
|
71
|
+
|
|
72
|
+
ruby_code = <<~RUBY
|
|
73
|
+
def find_max(arr)
|
|
74
|
+
max = arr[0]
|
|
75
|
+
arr.each do |num| # Loop runs n times
|
|
76
|
+
max = num if num > max
|
|
77
|
+
end
|
|
78
|
+
max
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Time: O(n) - must check every element
|
|
82
|
+
RUBY
|
|
83
|
+
|
|
84
|
+
cpp_code = <<~CPP
|
|
85
|
+
int findMax(int arr[], int n) {
|
|
86
|
+
int max = arr[0];
|
|
87
|
+
for(int i = 1; i < n; i++) { // n iterations
|
|
88
|
+
if(arr[i] > max)
|
|
89
|
+
max = arr[i];
|
|
90
|
+
}
|
|
91
|
+
return max;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Time: O(n) - linear scan
|
|
95
|
+
CPP
|
|
96
|
+
|
|
97
|
+
Visualizer.print_comparison(ruby_code, cpp_code,
|
|
98
|
+
"Both O(n). Must examine each element once. Time grows linearly with input size.")
|
|
99
|
+
|
|
100
|
+
# Example 3: O(n²) - Quadratic Time
|
|
101
|
+
Visualizer.print_section("5. Example: O(n²) - Quadratic Time")
|
|
102
|
+
|
|
103
|
+
ruby_code = <<~RUBY
|
|
104
|
+
def print_pairs(arr)
|
|
105
|
+
arr.each do |i| # n times
|
|
106
|
+
arr.each do |j| # n times for each i
|
|
107
|
+
puts "\#{i}, \#{j}"
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Time: O(n²) - nested loops
|
|
113
|
+
# Total operations: n × n = n²
|
|
114
|
+
RUBY
|
|
115
|
+
|
|
116
|
+
cpp_code = <<~CPP
|
|
117
|
+
void printPairs(int arr[], int n) {
|
|
118
|
+
for(int i = 0; i < n; i++) { // n times
|
|
119
|
+
for(int j = 0; j < n; j++) { // n times
|
|
120
|
+
cout << arr[i] << "," << arr[j];
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Time: O(n²) - quadratic growth
|
|
126
|
+
CPP
|
|
127
|
+
|
|
128
|
+
Visualizer.print_comparison(ruby_code, cpp_code,
|
|
129
|
+
"Both O(n²). Nested loops multiply. For n=100, runs 10,000 times!")
|
|
130
|
+
|
|
131
|
+
# Example 4: O(log n) - Logarithmic Time
|
|
132
|
+
Visualizer.print_section("6. Example: O(log n) - Logarithmic Time")
|
|
133
|
+
|
|
134
|
+
ruby_code = <<~RUBY
|
|
135
|
+
def binary_search(arr, target)
|
|
136
|
+
left, right = 0, arr.length - 1
|
|
137
|
+
|
|
138
|
+
while left <= right
|
|
139
|
+
mid = (left + right) / 2
|
|
140
|
+
return mid if arr[mid] == target
|
|
141
|
+
|
|
142
|
+
if arr[mid] < target
|
|
143
|
+
left = mid + 1 # Eliminate left half
|
|
144
|
+
else
|
|
145
|
+
right = mid - 1 # Eliminate right half
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
-1
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Time: O(log n) - halves search space each iteration
|
|
152
|
+
RUBY
|
|
153
|
+
|
|
154
|
+
cpp_code = <<~CPP
|
|
155
|
+
int binarySearch(int arr[], int n, int target) {
|
|
156
|
+
int left = 0, right = n - 1;
|
|
157
|
+
|
|
158
|
+
while(left <= right) {
|
|
159
|
+
int mid = left + (right - left) / 2;
|
|
160
|
+
if(arr[mid] == target) return mid;
|
|
161
|
+
|
|
162
|
+
if(arr[mid] < target)
|
|
163
|
+
left = mid + 1; // Discard left half
|
|
164
|
+
else
|
|
165
|
+
right = mid - 1; // Discard right half
|
|
166
|
+
}
|
|
167
|
+
return -1;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Time: O(log n) - logarithmic search
|
|
171
|
+
CPP
|
|
172
|
+
|
|
173
|
+
Visualizer.print_comparison(ruby_code, cpp_code,
|
|
174
|
+
"Both O(log n). Dividing problem in half each step. For n=1,000,000 only ~20 steps!")
|
|
175
|
+
|
|
176
|
+
# Space Complexity
|
|
177
|
+
Visualizer.print_section("7. Space Complexity")
|
|
178
|
+
puts "\nSpace complexity measures memory usage:"
|
|
179
|
+
puts " • Input space - memory for input (usually not counted)"
|
|
180
|
+
puts " • Auxiliary space - extra memory used by algorithm"
|
|
181
|
+
puts " • Total space = Input space + Auxiliary space"
|
|
182
|
+
|
|
183
|
+
puts "\nExample - Iterative vs Recursive Sum:"
|
|
184
|
+
|
|
185
|
+
ruby_code = <<~RUBY
|
|
186
|
+
# Iterative - O(1) space
|
|
187
|
+
def sum_iterative(n)
|
|
188
|
+
total = 0
|
|
189
|
+
(1..n).each { |i| total += i }
|
|
190
|
+
total
|
|
191
|
+
end
|
|
192
|
+
# Only uses 'total' variable
|
|
193
|
+
|
|
194
|
+
# Recursive - O(n) space
|
|
195
|
+
def sum_recursive(n)
|
|
196
|
+
return 0 if n == 0
|
|
197
|
+
n + sum_recursive(n - 1)
|
|
198
|
+
end
|
|
199
|
+
# Each call adds to call stack
|
|
200
|
+
# n recursive calls = O(n) space
|
|
201
|
+
RUBY
|
|
202
|
+
|
|
203
|
+
cpp_code = <<~CPP
|
|
204
|
+
// Iterative - O(1) space
|
|
205
|
+
int sumIterative(int n) {
|
|
206
|
+
int total = 0;
|
|
207
|
+
for(int i = 1; i <= n; i++)
|
|
208
|
+
total += i;
|
|
209
|
+
return total;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Recursive - O(n) space
|
|
213
|
+
int sumRecursive(int n) {
|
|
214
|
+
if(n == 0) return 0;
|
|
215
|
+
return n + sumRecursive(n - 1);
|
|
216
|
+
}
|
|
217
|
+
// Call stack grows to depth n
|
|
218
|
+
CPP
|
|
219
|
+
|
|
220
|
+
Visualizer.print_comparison(ruby_code, cpp_code,
|
|
221
|
+
"Recursion uses call stack memory. Each recursive call adds a stack frame.")
|
|
222
|
+
|
|
223
|
+
# Important Notes
|
|
224
|
+
notes = [
|
|
225
|
+
"Big O describes worst-case scenario (upper bound)",
|
|
226
|
+
"Drop constants: O(2n) → O(n), O(n/2) → O(n)",
|
|
227
|
+
"Drop non-dominant terms: O(n² + n) → O(n²)",
|
|
228
|
+
"Different inputs use different variables: O(a + b) not O(n)",
|
|
229
|
+
"Best case (Ω), Average case (Θ), Worst case (O)",
|
|
230
|
+
"Space complexity includes call stack for recursion",
|
|
231
|
+
"In-place algorithms use O(1) extra space",
|
|
232
|
+
"Amortized analysis considers average over sequence of operations"
|
|
233
|
+
]
|
|
234
|
+
NotesManager.print_notes("Complexity Analysis", notes)
|
|
235
|
+
|
|
236
|
+
# Key Points
|
|
237
|
+
key_points = [
|
|
238
|
+
"O(1) < O(log n) < O(n) < O(n log n) < O(n²) < O(2ⁿ) < O(n!)",
|
|
239
|
+
"Nested loops usually mean multiplication: O(n × m)",
|
|
240
|
+
"Consecutive loops mean addition: O(n + m)",
|
|
241
|
+
"Recursive algorithms often have O(n) space due to call stack",
|
|
242
|
+
"Binary search is O(log n) because it halves the problem",
|
|
243
|
+
"Sorting comparison-based algorithms can't be better than O(n log n)"
|
|
244
|
+
]
|
|
245
|
+
NotesManager.print_key_points(key_points)
|
|
246
|
+
|
|
247
|
+
# Practice Problems
|
|
248
|
+
problems = [
|
|
249
|
+
{ title: "Analyze nested loop complexity", difficulty: "Easy",
|
|
250
|
+
description: "Given code with 3 nested loops, determine time complexity" },
|
|
251
|
+
{ title: "Space complexity of merge sort", difficulty: "Medium",
|
|
252
|
+
description: "Calculate auxiliary space used by merge sort algorithm" },
|
|
253
|
+
{ title: "Optimize O(n²) to O(n)", difficulty: "Medium",
|
|
254
|
+
description: "Use hash table to improve two-sum problem from O(n²) to O(n)" }
|
|
255
|
+
]
|
|
256
|
+
NotesManager.print_practice_problems(problems)
|
|
257
|
+
|
|
258
|
+
puts "\n💡 PRO TIP:".colorize(:yellow).bold
|
|
259
|
+
puts "Always analyze both time AND space complexity."
|
|
260
|
+
puts "Sometimes you trade space for time (memoization) or vice versa."
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
end
|