Searching Algorithms
Techniques for finding elements in data structures efficiently.
Why Search Matters
Searching is one of the most common operations in programming. Whether you're looking up a user in a database, finding a file on disk, or navigating a mapโsearching is everywhere.
๐ฏ Key Categories
Linear Search โ check every element
Binary Search โ divide and conquer
BFS, DFS โ traverse connected nodes
O(1) lookup with hash tables
๐ Linear Search
The simplest search: check each element one by one until you find the target. Works on any data โ sorted or unsorted.
โ Pros
- โข Works on unsorted data
- โข Simple to implement
- โข O(1) space complexity
โ Cons
- โข O(n) time โ slow for large data
- โข Must check every element
function linearSearch(arr, target) {
for (let i = 0; i < arr.length; i++) {
if (arr[i] === target) {
return i; // Found at index i
}
}
return -1; // Not found
}
// Time: O(n) | Space: O(1)๐ Binary Search
Divide and conquer: For sorted arrays, compare the target with the middle element and eliminate half the search space each time.
โก Key insight: Binary search is O(log n). For 1 billion elements, you only need ~30 comparisons instead of 1 billion!
How it works (finding 23):
function binarySearch(arr, target) {
let left = 0;
let right = arr.length - 1;
while (left <= right) {
const mid = Math.floor((left + right) / 2);
if (arr[mid] === target) {
return mid; // Found!
} else if (arr[mid] < target) {
left = mid + 1; // Search right half
} else {
right = mid - 1; // Search left half
}
}
return -1; // Not found
}
// Time: O(log n) | Space: O(1)
// IMPORTANT: Array must be sorted!โ ๏ธ Prerequisite: Binary search only works on sorted arrays. If your data isn't sorted, sort it first (O(n log n)) or use linear search.
๐ Breadth-First Search (BFS)
Explore nodes level by level. Visit all neighbors before moving deeper. Uses a queue (FIFO).
Best for: Finding the shortest path in unweighted graphs, level-order traversal of trees.
function bfs(graph, start) {
const visited = new Set();
const queue = [start];
const result = [];
while (queue.length > 0) {
const node = queue.shift(); // Dequeue
if (!visited.has(node)) {
visited.add(node);
result.push(node);
// Add all unvisited neighbors to queue
for (const neighbor of graph[node]) {
if (!visited.has(neighbor)) {
queue.push(neighbor);
}
}
}
}
return result;
}
// Example graph: { A: ['B', 'C'], B: ['D'], C: ['E'], D: [], E: [] }
// BFS from A: ['A', 'B', 'C', 'D', 'E']๐ Depth-First Search (DFS)
Explore as deep as possible before backtracking. Uses a stack (LIFO) or recursion.
Best for: Path finding, cycle detection, topological sorting, solving puzzles (mazes, sudoku).
// Recursive DFS
function dfs(graph, node, visited = new Set()) {
if (visited.has(node)) return;
visited.add(node);
console.log(node); // Process node
for (const neighbor of graph[node]) {
dfs(graph, neighbor, visited);
}
}
// Iterative DFS (using stack)
function dfsIterative(graph, start) {
const visited = new Set();
const stack = [start];
while (stack.length > 0) {
const node = stack.pop(); // LIFO
if (!visited.has(node)) {
visited.add(node);
console.log(node);
// Add neighbors to stack
for (const neighbor of graph[node]) {
stack.push(neighbor);
}
}
}
}BFS vs DFS
| Aspect | BFS | DFS |
|---|---|---|
| Data Structure | Queue | Stack / Recursion |
| Time Complexity | O(V + E) | O(V + E) |
| Space Complexity | O(V) โ wider | O(V) โ deeper |
| Shortest Path | โ Yes (unweighted) | โ Not guaranteed |
| Best For | Shortest path, level-order | Cycle detection, paths |
Quick Reference
Use Linear Search O(n)
Use Binary Search O(log n)
Use BFS
Use DFS
Use Hash Table
Content adapted from Hello ็ฎๆณ - Searching by Krahets, licensed under CC BY-NC-SA 4.0.