- Published on
Complete Guide to Searching Algorithms - Linear, Binary, Ternary, Jump & Exponential Search
- Overview of Searching Algorithms
- 1. Linear Search (Sequential Search)
- 2. Binary Search
- 3. Ternary Search
- 4. Jump Search (Block Search)
- 5. Exponential Search (Doubling Search)
- ๐ Algorithm Comparison Summary
- ๐ฏ Interview Preparation Tips
- ๐ Advanced Topics
- ๐ Practice Problems
Searching algorithms are fundamental to computer science and essential for technical interviews. This comprehensive guide covers all major searching techniques with detailed explanations, implementations, and analysis.
Overview of Searching Algorithms
What is Searching? Finding a specific element in a collection of data.
Key Considerations:
- Time Complexity: How fast is the algorithm?
- Space Complexity: How much extra memory is needed?
- Data Requirements: Does the data need to be sorted?
- Use Cases: When to use each algorithm?
1. Linear Search (Sequential Search)
๐ Concept
Linear search checks each element one by one from the beginning until the target is found or the end is reached.
Key Points to Remember
- Simplest searching algorithm
- Works on both sorted and unsorted arrays
- No preprocessing required
- Checks elements sequentially
Algorithm Steps
- Start from the first element
- Compare current element with target
- If match found, return index
- If not found, move to next element
- Repeat until found or end reached
Implementation
package main
import "fmt"
// LinearSearch performs linear search on an array
func LinearSearch(arr []int, target int) int {
for i := 0; i < len(arr); i++ {
if arr[i] == target {
return i // Return index if found
}
}
return -1 // Return -1 if not found
}
// Example usage
func main() {
arr := []int{64, 34, 25, 12, 22, 11, 90}
result := LinearSearch(arr, 22)
fmt.Printf("Element found at index: %d\n", result) // Output: 4
}
Time & Space Complexity
- Time Complexity:
- Best Case: O(1) - element at first position
- Average Case: O(n) - element at middle
- Worst Case: O(n) - element at last position or not found
- Space Complexity: O(1) - no extra space needed
When to Use
โ Use Linear Search When:
- Array is unsorted
- Small datasets (< 100 elements)
- One-time search operations
- Memory is limited
โ Avoid When:
- Large datasets with frequent searches
- Data is already sorted
Interview Questions
Q: Why use linear search when binary search is faster? A: Linear search works on unsorted data and has O(1) space complexity.
Q: Can you optimize linear search? A: Yes, using sentinel search or early termination techniques.
2. Binary Search
๐ Concept
Binary search repeatedly divides a sorted array in half, comparing the target with the middle element.
Key Points to Remember
- Requires sorted array (crucial prerequisite)
- Divide and conquer approach
- Most efficient for sorted data
- Logarithmic time complexity
Algorithm Steps
- Set low = 0, high = array length - 1
- Calculate mid = (low + high) / 2
- If arr[mid] == target, return mid
- If arr[mid] < target, search right half (low = mid + 1)
- If arr[mid] > target, search left half (high = mid - 1)
- Repeat until found or low > high
Implementation
package main
import "fmt"
// BinarySearch performs iterative binary search
func BinarySearch(arr []int, target int) int {
low, high := 0, len(arr)-1
for low <= high {
mid := low + (high-low)/2 // Avoid overflow
if arr[mid] == target {
return mid
} else if arr[mid] < target {
low = mid + 1 // Search right half
} else {
high = mid - 1 // Search left half
}
}
return -1 // Not found
}
// BinarySearchRecursive performs recursive binary search
func BinarySearchRecursive(arr []int, target, low, high int) int {
if low > high {
return -1
}
mid := low + (high-low)/2
if arr[mid] == target {
return mid
} else if arr[mid] < target {
return BinarySearchRecursive(arr, target, mid+1, high)
} else {
return BinarySearchRecursive(arr, target, low, mid-1)
}
}
// Example usage
func main() {
arr := []int{11, 12, 22, 25, 34, 64, 90} // Must be sorted!
result := BinarySearch(arr, 22)
fmt.Printf("Element found at index: %d\n", result) // Output: 2
}
Time & Space Complexity
- Time Complexity: O(log n)
- Space Complexity:
- Iterative: O(1)
- Recursive: O(log n) - due to call stack
Important Notes
โ ๏ธ Common Pitfalls:
- Integer Overflow: Use
mid = low + (high - low) // 2
instead of(low + high) // 2
- Array must be sorted
- Handle edge cases: empty array, single element
Binary Search Variants
// FindFirstOccurrence finds the first occurrence of target
func FindFirstOccurrence(arr []int, target int) int {
result := -1
low, high := 0, len(arr)-1
for low <= high {
mid := low + (high-low)/2
if arr[mid] == target {
result = mid
high = mid - 1 // Continue searching left
} else if arr[mid] < target {
low = mid + 1
} else {
high = mid - 1
}
}
return result
}
// Dry run for arr = [2, 4, 4, 4, 5, 6], target = 4
// Step 1: low=0, high=5, mid=2, arr[2]=4 == 4 โ result=2, low=3
// Step 2: low=3, high=5, mid=4, arr[4]=5 > 4 โ high=3
// Step 3: low=3, high=3, mid=3, arr[3]=4 == 4 โ result=3, low=4
// End: low=4, high=3, loop ends, return result=3 (last occurrence)
func FindLastOccurrence(arr []int, target int) int {
result := -1
low, high := 0, len(arr)-1
for low <= high {
mid := low + (high-low)/2
if arr[mid] == target {
result = mid
low = mid + 1 // Continue searching right
} else if arr[mid] < target {
low = mid + 1
} else {
high = mid - 1
}
}
return result
}
When to Use
โ Use Binary Search When:
- Array is sorted
- Frequent searches on same dataset
- Large datasets (> 100 elements)
Interview Questions
Q: What if array has duplicates? A: Use modified binary search to find first/last occurrence.
Q: Can binary search work on unsorted arrays? A: No, sorting is required. Consider sorting cost: O(n log n).
3. Ternary Search
๐ Concept
Ternary search divides the array into three parts instead of two, comparing with two middle elements.
Key Points to Remember
- Requires sorted array
- Divides into 3 parts (not 2 like binary)
- Two comparison points
- Theoretically faster but practically slower due to more comparisons
Algorithm Steps
- Calculate mid1 = low + (high - low) / 3
- Calculate mid2 = high - (high - low) / 3
- If target == arr[mid1] or target == arr[mid2], return index
- If target < arr[mid1], search left third
- If target > arr[mid2], search right third
- Otherwise, search middle third
Implementation
package main
import "fmt"
// TernarySearch performs recursive ternary search
func TernarySearch(arr []int, target, low, high int) int {
if low > high {
return -1
}
// Divide array into 3 parts
mid1 := low + (high-low)/3
mid2 := high - (high-low)/3
// Check if target is at mid1 or mid2
if arr[mid1] == target {
return mid1
}
if arr[mid2] == target {
return mid2
}
// Decide which third to search
if target < arr[mid1] {
return TernarySearch(arr, target, low, mid1-1)
} else if target > arr[mid2] {
return TernarySearch(arr, target, mid2+1, high)
} else {
return TernarySearch(arr, target, mid1+1, mid2-1)
}
}
// TernarySearchIterative performs iterative ternary search
func TernarySearchIterative(arr []int, target int) int {
low, high := 0, len(arr)-1
for low <= high {
mid1 := low + (high-low)/3
mid2 := high - (high-low)/3
if arr[mid1] == target {
return mid1
}
if arr[mid2] == target {
return mid2
}
if target < arr[mid1] {
high = mid1 - 1
} else if target > arr[mid2] {
low = mid2 + 1
} else {
low = mid1 + 1
high = mid2 - 1
}
}
return -1
}
// Example usage
func main() {
arr := []int{11, 12, 22, 25, 34, 64, 90}
result := TernarySearch(arr, 25, 0, len(arr)-1)
fmt.Printf("Element found at index: %d\n", result) // Output: 3
}
Time & Space Complexity
- Time Complexity: O(logโ n) โ O(log n)
- Space Complexity:
- Recursive: O(log n)
- Iterative: O(1)
Binary vs Ternary Search
Aspect | Binary Search | Ternary Search |
---|---|---|
Divisions | 2 parts | 3 parts |
Comparisons per iteration | 1 | 2 |
Theoretical complexity | logโ n | logโ n |
Practical performance | Faster | Slower |
Why Ternary is Slower in Practice
- More comparisons: 2 comparisons vs 1 in binary
- Cache performance: Binary search has better locality
- Constant factors: Higher constant in ternary search
When to Use
โ Use Ternary Search When:
- Theoretical analysis required
- Educational purposes
- Finding maximum/minimum in unimodal functions
โ Prefer Binary Search for general searching
4. Jump Search (Block Search)
๐ Concept
Jump search combines linear and binary search by jumping ahead by fixed steps, then performing linear search in the identified block.
Key Points to Remember
- Requires sorted array
- Optimal jump size: โn
- Two-phase approach: Jump + Linear
- Better than linear, worse than binary
Algorithm Steps
- Choose jump size (usually โn)
- Jump ahead by jump_size until arr[i] >= target or end reached
- Perform linear search in the identified block
- Return index if found, -1 otherwise
Implementation
package main
import (
"fmt"
"math"
)
// JumpSearch - Jump Search Implementation
func JumpSearch(arr []int, target int) int {
n := len(arr)
if n == 0 {
return -1
}
blockSize := int(math.Sqrt(float64(n)))
start := 0
next := blockSize
// Find the block where target may be present
for start < n && arr[min(next, n)-1] < target {
start = next
next += blockSize
if start >= n {
return -1
}
}
// Linear search in the found block
for i := start; i < min(next, n); i++ {
if arr[i] == target {
return i
}
}
return -1
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
// Example usage
func main() {
arr := []int{0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610}
result := JumpSearch(arr, 55)
fmt.Printf("Element found at index: %d\n", result) // Output: 10
}
Time & Space Complexity
- Time Complexity: O(โn)
- Space Complexity: O(1)
Advantages & Disadvantages
โ Advantages:
- Better than linear search: O(โn) vs O(n)
- Better cache performance than binary search
- Simple implementation
- Good for large datasets where binary search might have cache misses
โ Disadvantages:
- Requires sorted array
- Slower than binary search
- Fixed jump size might not be optimal for all datasets
When to Use
โ Use Jump Search When:
- Cache performance is important
- Memory access cost is high
- Dataset is large and sorted
- Simple implementation is preferred
Interview Questions
Q: Why is โn the optimal jump size? A: It minimizes the total time: O(n/m) for jumping + O(m) for linear search.
Q: How does jump search compare to binary search? A: Jump search: O(โn), Binary search: O(log n). Binary is faster but jump has better cache performance.
5. Exponential Search (Doubling Search)
๐ Concept
Exponential search finds the range where the element might exist by doubling the search size, then applies binary search in that range.
Key Points to Remember
- Requires sorted array
- Two-phase approach: Find range + Binary search
- Exponential growth: 1, 2, 4, 8, 16, ...
- Excellent for unbounded/infinite arrays
Algorithm Steps
- Start with index = 1
- Keep doubling index until arr[index] >= target or end reached
- Apply binary search between previous_index and current_index
- Return result from binary search
Implementation
package main
import "fmt"
// ExponentialSearch - Exponential Search Implementation
func ExponentialSearch(arr []int, target int) int {
n := len(arr)
// If element is at first position
if arr[0] == target {
return 0
}
// Find range for binary search by repeated doubling
index := 1
for index < n && arr[index] <= target {
index *= 2
}
// Apply binary search in the found range
return binarySearchRange(arr, target, index/2, min(index, n-1))
}
// binarySearchRange - Binary search in given range
func binarySearchRange(arr []int, target, low, high int) int {
for low <= high {
mid := low + (high-low)/2
if arr[mid] == target {
return mid
} else if arr[mid] < target {
low = mid + 1
} else {
high = mid - 1
}
}
return -1 // Not found
}
// ExponentialSearchUnbounded - Exponential Search for unbounded arrays
func ExponentialSearchUnbounded(arr []int, target int) int {
index := 1
// Find the range where element may be present
for index < len(arr) && arr[index] <= target {
index *= 2
}
// Binary search in the identified range
if index >= len(arr) {
return binarySearchRange(arr, target, index/2, len(arr)-1)
}
return binarySearchRange(arr, target, index/2, index-1)
}
// Example usage
func main() {
arr := []int{2, 3, 4, 10, 40, 50, 80, 100, 120, 140}
result := ExponentialSearch(arr, 40)
fmt.Printf("Element found at index: %d\n", result) // Output: 4
}
Step-by-Step Example
// Searching for 40 in [2, 3, 4, 10, 40, 50, 80, 100, 120, 140]
// Step 1: index = 1, arr[1] = 3 <= 40, continue
// Step 2: index = 2, arr[2] = 4 <= 40, continue
// Step 3: index = 4, arr[4] = 40 <= 40, continue
// Step 4: index = 8, arr[8] = 120 > 40, stop
# Range found: [index//2, index] = [4, 8]
# Apply binary search in range [4, 8]
Time & Space Complexity
- Time Complexity: O(log n)
- Space Complexity: O(1)
Advantages & Disadvantages
โ Advantages:
- Same time complexity as binary search: O(log n)
- Excellent for unbounded arrays
- Good cache performance
- Finds range quickly with exponential growth
โ Disadvantages:
- Requires sorted array
- Slightly more complex than binary search
- Two-phase approach adds overhead
When to Use
โ Use Exponential Search When:
- Unbounded or infinite arrays
- Unknown array size
- Element likely to be at beginning
- Cache-friendly search needed
Real-World Applications
- Database indexing
- File system searches
- Network routing tables
- Memory management
๐ Algorithm Comparison Summary
Detailed Use Case Comparison
Algorithm | Ideal Use Cases | Avoid When | Real-World Examples |
---|---|---|---|
Linear Search | Unsorted, small data; simple code | Large or sorted data | Config files, small lookups |
Binary Search | Large, sorted data; frequent search | Unsorted or tiny data | Database indexes, dictionaries |
Ternary Search | Unimodal/peak finding; research | General search tasks | Math optimization, research |
Jump Search | Large, sorted, cache-sensitive data | Small or unsorted data | File systems, embedded DBs |
Exponential Search | Unbounded/unknown size, sorted | Small, known-size data | Streams, network routing |
Decision Matrix by Scenario
Scenario | Array Size | Is Sorted? | Search Frequency | Recommended Algorithm | Reasoning |
---|---|---|---|---|---|
Web Search Engine | Very Large (10M+) | Yes | Very High | Binary Search | O(log n) with high frequency |
Mobile App Config | Small (< 50) | No | Low | Linear Search | Simple, no sorting overhead |
Database Index | Large (1M+) | Yes | Very High | Binary Search | Optimized for frequent queries |
IoT Sensor Data | Medium (10K) | Yes | Medium | Jump Search | Cache-friendly for embedded |
Real-time Stream | Unknown/Growing | Yes | High | Exponential Search | Handles unbounded data |
Game Leaderboard | Medium (1K) | Yes | High | Binary Search | Fast lookups for rankings |
Log File Analysis | Large (1M+) | Partially | Low | Linear Search | Data not fully sorted |
Scientific Computing | Large (100K+) | Yes | Research | Ternary Search | Academic/research context |
๐ฏ Interview Preparation Tips
Common Interview Questions
Q: Which search algorithm would you choose for a sorted array of 1 million elements? A: Binary search - O(log n) time complexity, most efficient for large sorted arrays.
Q: How would you search in a rotated sorted array? A: Modified binary search by first identifying which half is sorted.
Q: What if the array is sorted but you don't know its size? A: Exponential search - designed for unbounded arrays.
Q: When would you prefer linear search over binary search? A: When array is unsorted, very small, or search is one-time operation.
Q: How do you handle duplicates in binary search? A: Use modified binary search to find first/last occurrence.
Code Templates to Remember
// BinarySearchTemplate - Standard binary search template
func BinarySearchTemplate(arr []int, target int) int {
left, right := 0, len(arr)-1
for left <= right {
mid := left + (right-left)/2
if arr[mid] == target {
return mid
} else if arr[mid] < target {
left = mid + 1
} else {
right = mid - 1
}
}
return -1
}
// FindFirst - Binary search for first occurrence
func FindFirst(arr []int, target int) int {
left, right := 0, len(arr)-1
result := -1
for left <= right {
mid := left + (right-left)/2
if arr[mid] == target {
result = mid
right = mid - 1 // Continue searching left
} else if arr[mid] < target {
left = mid + 1
} else {
right = mid - 1
}
}
return result
}
// FindLast - Binary search for last occurrence
func FindLast(arr []int, target int) int {
left, right := 0, len(arr)-1
result := -1
for left <= right {
mid := left + (right-left)/2
if arr[mid] == target {
result = mid
left = mid + 1 // Continue searching right
} else if arr[mid] < target {
left = mid + 1
} else {
right = mid - 1
}
}
return result
}
Quick Decision Tree
Is array sorted?
โโโ No โ Linear Search
โโโ Yes โ Array size?
โโโ < 100 โ Linear Search
โโโ 100-10k โ Binary Search
โโโ > 10k โ Consider:
โโโ Frequent searches โ Binary Search
โโโ Unbounded array โ Exponential Search
โโโ Cache sensitive โ Jump Search
โโโ Default โ Binary Search
๐ Advanced Topics
Binary Search Variations
- Search in Rotated Array
- Find Peak Element
- Search in 2D Matrix
- Find Minimum in Rotated Array
Optimization Techniques
- Interpolation Search - O(log log n) for uniformly distributed data
- Fibonacci Search - Uses Fibonacci numbers for division
- Sentinel Linear Search - Optimized linear search
Real-World Applications
- Database Indexing: B-trees use binary search principles
- System Design: Load balancing, caching strategies
- Machine Learning: Hyperparameter tuning, feature selection
- Networking: Routing table lookups
๐ Practice Problems
Easy Problems
- Search insert position
- First bad version
- Square root using binary search
Medium Problems
- Search in rotated sorted array
- Find first and last position of element
- Search a 2D matrix
Hard Problems
- Median of two sorted arrays
- Find minimum in rotated sorted array II
- Search in rotated sorted array II