Skip to content

Commit 3d1ade2

Browse files
authored
Update basic_concepts.md
1 parent fdb271e commit 3d1ade2

File tree

1 file changed

+126
-26
lines changed

1 file changed

+126
-26
lines changed

notes/basic_concepts.md

Lines changed: 126 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,73 @@ Data structures and algorithms are fundamental concepts in computer science that
1010

1111
A **data structure** organizes and stores data in a way that allows efficient access, modification, and processing. The choice of the appropriate data structure depends on the specific use case and can significantly impact the performance of an application. Here are some common data structures:
1212

13-
1. Imagine an **array** as a row of lockers, each labeled with a number and capable of holding one item of the same type. Technically, arrays are blocks of memory storing elements sequentially, allowing quick access using an index. However, arrays have a fixed size, which limits their flexibility when you need to add or remove items.
14-
2. Think of a **stack** like stacking plates: you always add new plates on top (push), and remove them from the top as well (pop). This structure follows the Last-In, First-Out (LIFO) approach, meaning the most recently added item is removed first. Stacks are particularly helpful in managing function calls (like in the call stack of a program) or enabling "undo" operations in applications.
15-
3. A **queue** is similar to a line at the grocery store checkout. People join at the end (enqueue) and leave from the front (dequeue), adhering to the First-In, First-Out (FIFO) principle. This ensures the first person (or item) that arrives is also the first to leave. Queues work great for handling tasks or events in the exact order they occur, like scheduling print jobs or processing messages.
16-
4. You can picture a **linked list** as a treasure hunt, where each clue leads you to the next one. Each clue, or node, holds data and a pointer directing you to the next node. Because nodes can be added or removed without shifting other elements around, linked lists offer dynamic and flexible management of data at any position.
17-
5. A **tree** resembles a family tree, starting from one ancestor (the root) and branching out into multiple descendants (nodes), each of which can have their own children. Formally, trees are hierarchical structures organized across various levels. They’re excellent for showing hierarchical relationships, such as organizing files on your computer or visualizing company structures.
18-
6. Consider a **graph** like a network of cities connected by roads. Each city represents a node, and the roads connecting them are edges, which can either be one-way (directed) or two-way (undirected). Graphs effectively illustrate complex relationships and networks, such as social media connections, website link structures, or even mapping transportation routes.
13+
**I. Array**
14+
15+
Imagine an **array** as a row of lockers, each labeled with a number and capable of holding one item of the same type. Technically, arrays are blocks of memory storing elements sequentially, allowing quick access using an index. However, arrays have a fixed size, which limits their flexibility when you need to add or remove items.
16+
17+
```
18+
Indices: 0 1 2 3
19+
Array: [A] [B] [C] [D]
20+
```
21+
22+
**II. Stack**
23+
24+
Think of a **stack** like stacking plates: you always add new plates on top (push), and remove them from the top as well (pop). This structure follows the Last-In, First-Out (LIFO) approach, meaning the most recently added item is removed first. Stacks are particularly helpful in managing function calls (like in the call stack of a program) or enabling "undo" operations in applications.
25+
26+
```
27+
Top
28+
┌───┐
29+
│ C │ ← most recent (pop/push here)
30+
├───┤
31+
│ B │
32+
├───┤
33+
│ A │
34+
└───┘
35+
Bottom
36+
```
37+
38+
**III. Queue**
39+
40+
A **queue** is similar to a line at the grocery store checkout. People join at the end (enqueue) and leave from the front (dequeue), adhering to the First-In, First-Out (FIFO) principle. This ensures the first person (or item) that arrives is also the first to leave. Queues work great for handling tasks or events in the exact order they occur, like scheduling print jobs or processing messages.
41+
42+
```
43+
Front → [A] → [B] → [C] → [D] ← Rear
44+
(dequeue) (enqueue)
45+
```
46+
47+
**IV. Linked List**
48+
49+
You can picture a **linked list** as a treasure hunt, where each clue leads you to the next one. Each clue, or node, holds data and a pointer directing you to the next node. Because nodes can be added or removed without shifting other elements around, linked lists offer dynamic and flexible management of data at any position.
50+
51+
```
52+
Head -> [A] -> [B] -> [C] -> NULL
53+
```
54+
55+
**V. Tree**
56+
57+
A **tree** resembles a family tree, starting from one ancestor (the root) and branching out into multiple descendants (nodes), each of which can have their own children. Formally, trees are hierarchical structures organized across various levels. They’re excellent for showing hierarchical relationships, such as organizing files on your computer or visualizing company structures.
58+
59+
```
60+
# Tree
61+
(Root)
62+
/ \
63+
(L) (R)
64+
/ \ \
65+
(LL) (LR) (RR)
66+
```
67+
68+
**VI. Graph**
69+
70+
Consider a **graph** like a network of cities connected by roads. Each city represents a node, and the roads connecting them are edges, which can either be one-way (directed) or two-way (undirected). Graphs effectively illustrate complex relationships and networks, such as social media connections, website link structures, or even mapping transportation routes.
71+
72+
```
73+
(A) ↔ (B)
74+
| \
75+
(C) ---> (D)
76+
```
77+
78+
(↔ undirected edge, ---> directed edge)
79+
1980

2081
![image](https://github.com/user-attachments/assets/f1962de7-aa28-4348-9933-07e49c737cd9)
2182

@@ -88,10 +149,7 @@ sum = num1 + num2
88149
print("The sum is", sum)
89150
```
90151

91-
To recap:
92-
93-
- Algorithms are abstract instructions designed to terminate after a finite number of steps.
94-
- Programs are concrete implementations, which may sometimes run indefinitely or until an external action stops them. For instance, an operating system is a program designed to run continuously until explicitly terminated.
152+
Programs may sometimes run indefinitely or until an external action stops them. For instance, an operating system is a program designed to run continuously until explicitly terminated.
95153

96154
#### Types of Algorithms
97155

@@ -101,13 +159,14 @@ I. **Sorting Algorithms** arrange data in a specific order, such as ascending or
101159

102160
Example: Bubble Sort
103161

104-
```
105-
Initial Array: [5, 3, 8, 4, 2]
162+
Initial Array: `[5, 3, 8, 4, 2]`
106163

107164
Steps:
165+
108166
1. Compare adjacent elements and swap if needed.
109167
2. Repeat for all elements.
110168

169+
```
111170
After 1st Pass: [3, 5, 4, 2, 8]
112171
After 2nd Pass: [3, 4, 2, 5, 8]
113172
After 3rd Pass: [3, 2, 4, 5, 8]
@@ -118,16 +177,17 @@ II. **Search Algorithms** are designed to find a specific item or value within a
118177

119178
Example: Binary Search
120179

121-
```
122-
Searching 33 in Sorted Array: [1, 3, 5, 7, 9, 11, 33, 45, 77, 89]
180+
Searching 33 in Sorted Array: `[1, 3, 5, 7, 9, 11, 33, 45, 77, 89]`
123181

124182
Steps:
183+
125184
1. Start with the middle element.
126185
2. If the middle element is the target, return it.
127186
3. If the target is greater, ignore the left half.
128187
4. If the target is smaller, ignore the right half.
129188
5. Repeat until the target is found or the subarray is empty.
130189

190+
```
131191
Mid element at start: 9
132192
33 > 9, so discard left half
133193
New mid element: 45
@@ -137,64 +197,98 @@ New mid element: 11
137197
The remaining element is 33, which is the target.
138198
```
139199

140-
**Graph Algorithms** address problems related to graphs, such as finding the shortest path between nodes or determining if a graph is connected. Examples include Dijkstra's algorithm and the Floyd-Warshall algorithm.
200+
III. **Graph Algorithms** address problems related to graphs, such as finding the shortest path between nodes or determining if a graph is connected. Examples include Dijkstra's algorithm and the Floyd-Warshall algorithm.
141201

142202
Example: Dijkstra's Algorithm
143203

144-
```
145204
Given a graph with weighted edges, find the shortest path from a starting node to all other nodes.
146205

147206
Steps:
207+
148208
1. Initialize the starting node with a distance of 0 and all other nodes with infinity.
149209
2. Visit the unvisited node with the smallest known distance.
150210
3. Update the distances of its neighboring nodes.
151211
4. Repeat until all nodes have been visited.
152212

153213
Example Graph:
214+
215+
```
154216
A -> B (1)
155217
A -> C (4)
156218
B -> C (2)
157219
B -> D (5)
158220
C -> D (1)
221+
```
222+
223+
Trace Table
224+
225+
| Iter | Extracted Node (u) | PQ before extraction | dist[A,B,C,D] | prev[A,B,C,D] | Visited | Comments / Updates |
226+
| ---- | ------------------ | ---------------------------------- | -------------- | -------------- | --------- | --------------------------------------------------------------------------------------- |
227+
| 0 | — (initial) | (0, A) | [0, ∞, ∞, ∞] | [-, -, -, -] | {} | Initialization: A=0, others ∞ |
228+
| 1 | A (0) | (0, A) | [0, 1, 4, ∞] | [-, A, A, -] | {A} | Relax A→B (1), A→C (4); push (1,B), (4,C) |
229+
| 2 | B (1) | (1, B), (4, C) | [0, 1, 3, 6] | [-, A, B, B] | {A, B} | Relax B→C: alt=3 <4 ⇒ update C; B→D: dist[D]=6; push (3,C), (6,D). (4,C) becomes stale |
230+
| 3 | C (3) | (3, C), (4, C) stale, (6, D) | [0, 1, 3, 4] | [-, A, B, C] | {A, B, C} | Relax C→D: alt=4 <6 ⇒ update D; push (4,D). (6,D) becomes stale |
231+
| 4 | D (4) | (4, D), (4, C) stale, (6, D) stale | [0, 1, 3, 4] | [-, A, B, C] | {A,B,C,D} | No outgoing improvements; done |
232+
233+
Legend:
234+
235+
* `dist[X]`: current best known distance from A to X
236+
* `prev[X]`: predecessor of X on that best path
237+
* PQ: min-heap of (tentative distance, node); stale entries (superseded by better distance) are shown in parentheses
238+
* Visited: nodes whose shortest distance is finalized
159239

160240
Starting from A:
241+
161242
- Shortest path to B: A -> B (1)
162243
- Shortest path to C: A -> B -> C (3)
163244
- Shortest path to D: A -> B -> C -> D (4)
164-
```
165245

166-
**String Algorithms** deal with problems related to strings, such as finding patterns or matching sequences. Examples include the Knuth-Morris-Pratt (KMP) algorithm and the Boyer-Moore algorithm.
246+
IV. **String Algorithms** deal with problems related to strings, such as finding patterns or matching sequences. Examples include the Knuth-Morris-Pratt (KMP) algorithm and the Boyer-Moore algorithm.
167247

168248
Example: Boyer-Moore Algorithm
169249

170250
```
171251
Text: "ABABDABACDABABCABAB"
172252
Pattern: "ABABCABAB"
253+
```
173254

174255
Steps:
256+
175257
1. Compare the pattern from right to left.
176258
2. If a mismatch occurs, use the bad character and good suffix heuristics to skip alignments.
177259
3. Repeat until the pattern is found or the text is exhausted.
178260

261+
| Iter | Start | Text window | Mismatch (pattern vs text) | Shift applied | Next Start | Result |
262+
| ---- | ----- | ----------- | ----------------------------------------- | -------------------------------------------------- | ---------- | --------------- |
263+
| 1 | 0 | `ABABDABAC` | pattern[8]=B vs text[8]=C | bad char C → last in pattern at idx4 ⇒ 8−4 = **4** | 4 | no match |
264+
| 2 | 4 | `DABACDABA` | pattern[8]=B vs text[12]=A | bad char A → last at idx7 ⇒ 8−7 = **1** | 5 | no match |
265+
| 3 | 5 | `ABACDABAB` | pattern[4]=C vs text[9]=D | D not in pattern ⇒ 4−(−1)= **5** | 10 | no match |
266+
| 4 | 10 | `ABABCABAB` | full right-to-left comparison → **match** ||| **found** at 10 |
267+
179268
Pattern matched starting at index 10 in the text.
180-
```
181269

182270
#### Important Algorithms for Software Engineers
183271

184-
- As a software engineer, it is not necessary to **master every algorithm**. Instead, knowing how to effectively use libraries and packages that implement widely-used algorithms is more practical.
272+
- As a software engineer, it is not necessary to **master every algorithm**. Instead, knowing how to use libraries and packages that implement widely-used algorithms is more practical.
185273
- The important skill is the ability to **select the right algorithm** for a task by considering factors such as its efficiency, the problem’s requirements, and any specific constraints.
186274
- Learning **algorithms** during the early stages of programming enhances problem-solving skills. It builds a solid foundation in logical thinking, introduces various problem-solving strategies, and helps in understanding how to approach complex issues.
187275
- Once the **fundamentals of algorithms** are understood, the focus often shifts to utilizing pre-built libraries and tools for solving real-world problems, as writing algorithms from scratch is rarely needed in practice.
188276

277+
Real Life Story:
278+
279+
```
280+
When Zara landed her first job at a logistics-tech startup, her assignment was to route delivery vans through a sprawling city in under a second—something she’d never tackled before. She remembered the semester she’d wrestled with graph theory and Dijkstra’s algorithm purely for practice, so instead of hand-coding the logic she opened the company’s Python stack and pulled in NetworkX, benchmarking its built-in shortest-path routines against the map’s size and the firm’s latency budget. The initial results were sluggish, so she compared A* with Dijkstra, toggling heuristics until the run time dipped below 500 ms, well under the one-second target. Her teammates were impressed not because she reinvented an algorithm, but because she knew which one to choose, how to reason about its complexity, and where to find a rock-solid library implementation. Later, in a sprint retrospective, Zara admitted that mastering algorithms in college hadn’t been about memorizing code—it had trained her to dissect problems, weigh trade-offs, and plug in the right tool when every millisecond and memory block counted.
281+
```
282+
189283
### Understanding Algorithmic Complexity
190284

191285
Algorithmic complexity helps us understand the computational resources (time or space) an algorithm needs as the input size increases. Here’s a breakdown of different types of complexity:
192286

193-
* *Best-case complexity* describes how quickly or efficiently an algorithm runs under the most favorable conditions. For example, an algorithm with a best-case complexity of O(1) performs its task instantly, regardless of how much data it processes.
194-
* *Average-case complexity* reflects the typical performance of an algorithm across all possible inputs. Determining this can be complex, as it involves analyzing how often different inputs occur and how each one influences the algorithm's overall performance.
195-
* *Worst-case complexity* defines the maximum amount of time or resources an algorithm could consume when faced with the most difficult or demanding inputs. Understanding the worst-case scenario is crucial because it sets an upper limit on performance, ensuring predictable and reliable behavior.
196-
* *Space complexity* refers to how much memory an algorithm needs relative to the amount of data it processes. It's an important consideration when memory availability is limited or when optimizing an algorithm to be resource-efficient.
197-
* *Time complexity* indicates how the execution time of an algorithm increases as the input size grows. Typically, this is the primary focus when evaluating algorithm efficiency because faster algorithms are generally more practical and user-friendly.
287+
* In an ideal input scenario, *best-case complexity* shows the minimum work an algorithm will do; include it to set expectations for quick interactions, omit it and you may overlook fast paths that are useful for user experience, as when insertion sort finishes almost immediately on a nearly sorted list.
288+
* When you ask what to expect most of the time, *average-case complexity* estimates typical running time; include it to make useful forecasts under normal workloads, omit it and designs can seem fine in tests but lag on common inputs, as with randomly ordered customer IDs that need $O(n log n)$ sorting.
289+
* By establishing an upper bound, *worst-case complexity* tells you the maximum time or space an algorithm might need; include it to ensure predictable behavior, omit it and peak loads can surprise you, as when quicksort degrades to $O(n^2)$ on already sorted input without careful pivot selection.
290+
* On memory-limited devices, *space complexity* measures how much extra storage an algorithm requires; include it to fit within available RAM, omit it and an otherwise fast solution may crash or swap, as when merge sort’s $O(n)$ auxiliary array overwhelms a phone with little free memory.
291+
* As your dataset scales, *time complexity* describes how running time expands with input size; include it to choose faster approaches, omit it and performance can degrade sharply, as when an $O(n^2)$ deduplication routine turns a minute-long job into hours after a customer list doubles.
198292

199293
#### Analyzing Algorithm Growth Rates
200294

@@ -208,6 +302,8 @@ If we designate $f(n)$ as the actual complexity and $g(n)$ as the function in Bi
208302

209303
For instance, if an algorithm has a time complexity of $O(n)$, it signifies that the algorithm's running time does not grow more rapidly than a linear function of the input size, in the worst-case scenario.
210304

305+
<img width="1750" height="1110" alt="0902bace-952d-4c80-9533-5706e28ef3e9" src="https://github.com/user-attachments/assets/152fe1b7-3e0b-4a6d-b2d1-abf248ca90cf" />
306+
211307
##### Big Omega Notation (Ω-notation)
212308

213309
The Big Omega notation provides an asymptotic lower bound that expresses the best-case scenario for the time or space complexity of an algorithm.
@@ -216,13 +312,17 @@ If $f(n) = Ω(g(n))$, this means that $f(n)$ grows at a rate that is at least as
216312

217313
For example, if an algorithm has a time complexity of $Ω(n)$, it implies that the running time is at the bare minimum proportional to the input size in the best-case scenario.
218314

315+
<img width="1707" height="1103" alt="d189ece7-e9c2-4797-8e0d-720336c4ba4a" src="https://github.com/user-attachments/assets/9984cad4-e131-4d52-bcad-8206b03e625f" />
316+
219317
##### Theta Notation (Θ-notation)
220318

221319
Theta notation offers a representation of the average-case scenario for an algorithm's time or space complexity. It sets an asymptotically tight bound, implying that the function grows neither more rapidly nor slower than the bound.
222320

223321
Stating $f(n) = Θ(g(n))$ signifies that $f(n)$ grows at the same rate as $g(n)$ under average circumstances. This indicates the time or space complexity is both at most and at least a linear function of the input size.
224322

225-
Remember, these notations primarily address the growth rate as the input size becomes significantly large. While they offer a high-level comprehension of an algorithm's performance, the actual running time in practice can differ based on various factors, such as the specific input data, the hardware or environment where the algorithm is operating, and the precise way the algorithm is implemented in the code.
323+
<img width="1707" height="1103" alt="ef39373a-8e6a-4e5b-832f-698b4dde7c7e" src="https://github.com/user-attachments/assets/bb11e34a-da8f-45a6-9eab-cbc05676a334" />
324+
325+
These notations primarily address the growth rate as the input size becomes significantly large. While they offer a high-level comprehension of an algorithm's performance, the actual running time in practice can differ based on various factors, such as the specific input data, the hardware or environment where the algorithm is operating, and the precise way the algorithm is implemented in the code.
226326

227327
#### Diving into Big O Notation Examples
228328

0 commit comments

Comments
 (0)