Binary Search: Binary Search, Efficient Algorithms, Advanced Applications
How Binary Search Works
Initial Setup: Start with two pointers,
low
andhigh
, which represent the bounds of the search interval. Initially,low
is set to 0, andhigh
is set to the length of the array minus one.Middle Element: Calculate the middle index
mid
of the current interval. The middle index is computed asmid = (low + high) // 2
.Comparison:
- If the middle element
arr[mid]
is the target value, the search is complete, and the indexmid
is returned. - If the target value is less than
arr[mid]
, adjust thehigh
pointer tomid - 1
. - If the target value is greater than
arr[mid]
, adjust thelow
pointer tomid + 1
.
- If the middle element
Repeat: Repeat steps 2 and 3 until the
low
pointer exceeds thehigh
pointer. If the target value is not found, return-1
to indicate that the value is not in the array.
Binary Search Algorithm in Python
Here is the Python implementation of binary search:
pythondef binary_search(arr, target):
low = 0
high = len(arr) - 1
while low <= high:
mid = (low + high) // 2
mid_value = arr[mid]
if mid_value == target:
return mid # Target found, return its index
elif mid_value < target:
low = mid + 1 # Search in the right half
else:
high = mid - 1 # Search in the left half
return -1 # Target not found
# Example usage
sorted_array = [1, 3, 5, 7, 9, 11, 13, 15, 17, 19]
target = 7
result = binary_search(sorted_array, target)
if result != -1:
print(f"Element {target} found at index {result}")
else:
print(f"Element {target} not found in the array")
Explanation of the Example
Initial State:
arr = [1, 3, 5, 7, 9, 11, 13, 15, 17, 19]
target = 7
low = 0
,high = 9
First Iteration:
mid = (0 + 9) // 2 = 4
arr[mid] = 9
target < arr[mid]
, sohigh = mid - 1 = 3
Second Iteration:
mid = (0 + 3) // 2 = 1
arr[mid] = 3
target > arr[mid]
, solow = mid + 1 = 2
Third Iteration:
mid = (2 + 3) // 2 = 2
arr[mid] = 5
target > arr[mid]
, solow = mid + 1 = 3
Fourth Iteration:
mid = (3 + 3) // 2 = 3
arr[mid] = 7
arr[mid] == target
, target found at index3
Visual Representation
Imagine the array as follows:
makefileIndex: 0 1 2 3 4 5 6 7 8 9
Array: 1 3 5 7 9 11 13 15 17 19
Target: 7
- Initial bounds:
low = 0
,high = 9
- After first iteration:
mid = 4
,high = 3
- After second iteration:
mid = 1
,low = 2
- After third iteration:
mid = 2
,low = 3
- After fourth iteration:
mid = 3
, found target at index3
Recursive Implementation of Binary Search
Binary search can also be implemented recursively. The recursive approach follows the same logic as the iterative approach but uses function calls to reduce the search range. Here's how you can implement it:
Recursive Binary Search Algorithm in Python
pythondef binary_search_recursive(arr, target, low, high):
if low > high:
return -1 # Base case: target is not found
mid = (low + high) // 2
mid_value = arr[mid]
if mid_value == target:
return mid # Target found, return its index
elif mid_value < target:
return binary_search_recursive(arr, target, mid + 1, high) # Search in the right half
else:
return binary_search_recursive(arr, target, low, mid - 1) # Search in the left half
# Example usage
sorted_array = [1, 3, 5, 7, 9, 11, 13, 15, 17, 19]
target = 7
result = binary_search_recursive(sorted_array, target, 0, len(sorted_array) - 1)
if result != -1:
print(f"Element {target} found at index {result}")
else:
print(f"Element {target} not found in the array")
def binary_search_recursive(arr, target, low, high):
if low > high:
return -1 # Base case: target is not found
mid = (low + high) // 2
mid_value = arr[mid]
if mid_value == target:
return mid # Target found, return its index
elif mid_value < target:
return binary_search_recursive(arr, target, mid + 1, high) # Search in the right half
else:
return binary_search_recursive(arr, target, low, mid - 1) # Search in the left half
# Example usage
sorted_array = [1, 3, 5, 7, 9, 11, 13, 15, 17, 19]
target = 7
result = binary_search_recursive(sorted_array, target, 0, len(sorted_array) - 1)
if result != -1:
print(f"Element {target} found at index {result}")
else:
print(f"Element {target} not found in the array")
Explanation of the Recursive Example
Initial Call: binary_search_recursive(arr, 7, 0, 9)
low = 0
, high = 9
mid = (0 + 9) // 2 = 4
arr[mid] = 9
target < arr[mid]
, so call binary_search_recursive(arr, 7, 0, 3)
Second Call: binary_search_recursive(arr, 7, 0, 3)
low = 0
, high = 3
mid = (0 + 3) // 2 = 1
arr[mid] = 3
target > arr[mid]
, so call binary_search_recursive(arr, 7, 2, 3)
Third Call: binary_search_recursive(arr, 7, 2, 3)
low = 2
, high = 3
mid = (2 + 3) // 2 = 2
arr[mid] = 5
target > arr[mid]
, so call binary_search_recursive(arr, 7, 3, 3)
Fourth Call: binary_search_recursive(arr, 7, 3, 3)
low = 3
, high = 3
mid = (3 + 3) // 2 = 3
arr[mid] = 7
arr[mid] == target
, target found at index 3
Initial Call: binary_search_recursive(arr, 7, 0, 9)
low = 0
,high = 9
mid = (0 + 9) // 2 = 4
arr[mid] = 9
target < arr[mid]
, so callbinary_search_recursive(arr, 7, 0, 3)
Second Call: binary_search_recursive(arr, 7, 0, 3)
low = 0
,high = 3
mid = (0 + 3) // 2 = 1
arr[mid] = 3
target > arr[mid]
, so callbinary_search_recursive(arr, 7, 2, 3)
Third Call: binary_search_recursive(arr, 7, 2, 3)
low = 2
,high = 3
mid = (2 + 3) // 2 = 2
arr[mid] = 5
target > arr[mid]
, so callbinary_search_recursive(arr, 7, 3, 3)
Fourth Call: binary_search_recursive(arr, 7, 3, 3)
low = 3
,high = 3
mid = (3 + 3) // 2 = 3
arr[mid] = 7
arr[mid] == target
, target found at index3
Visual Representation for Recursive Approach
The process of narrowing down the search interval in the recursive approach can be visualized similarly to the iterative approach:
- Initial bounds:
low = 0
,high = 9
- After first recursive call:
mid = 4
,high = 3
- After second recursive call:
mid = 1
,low = 2
- After third recursive call:
mid = 2
,low = 3
- After fourth recursive call:
mid = 3
, found target at index3
Time Complexity
The time complexity of binary search, both iterative and recursive, is , where is the number of elements in the array. This efficiency makes binary search suitable for large sorted datasets.
Advantages of Binary Search
- Efficiency: With a time complexity of , binary search is much faster than linear search for large datasets.
- Simplicity: The algorithm is straightforward and easy to implement.
- Deterministic: Binary search always follows the same steps and guarantees finding the target if it exists in the array.
Practical Considerations
- Sorted Array: Binary search requires the array to be sorted. If the array is not sorted, you must sort it first, which takes time.
- Non-recursive Approach: In practice, the iterative approach is often preferred over the recursive one because it avoids the overhead of recursive function calls, especially in languages with limited stack size.
Applications of Binary Search
Binary search is a versatile algorithm with numerous applications beyond simply finding an element in an array. Here are some key applications:
- Finding the Lower/Upper Bound:
- Lower Bound: The smallest index at which a given value could be inserted to maintain sorted order.
- Upper Bound: The largest index at which a given value could be inserted to maintain sorted order.
- Searching in Infinite Lists:
- Used in situations where the list size is not known, such as in certain streaming data applications.
- Optimization Problems:
- Binary search can be used to solve optimization problems where you need to find the minimum or maximum value that satisfies certain conditions.
Example: Finding the Lower Bound
The lower bound of a value in a sorted array is the index of the first element that is not less than the target value. Here’s how you can find the lower bound using binary search:
pythondef lower_bound(arr, target):
low = 0
high = len(arr)
while low < high:
mid = (low + high) // 2
if arr[mid] < target:
low = mid + 1
else:
high = mid
return low
# Example usage
sorted_array = [1, 3, 3, 5, 7, 9, 11, 13, 15, 17, 19]
target = 3
index = lower_bound(sorted_array, target)
print(f"Lower bound for {target} is at index {index}")
Example: Finding the Upper Bound
The upper bound of a value in a sorted array is the index of the first element that is greater than the target value. Here’s how you can find the upper bound using binary search:
pythondef upper_bound(arr, target):
low = 0
high = len(arr)
while low < high:
mid = (low + high) // 2
if arr[mid] <= target:
low = mid + 1
else:
high = mid
return low
# Example usage
sorted_array = [1, 3, 3, 5, 7, 9, 11, 13, 15, 17, 19]
target = 3
index = upper_bound(sorted_array, target)
print(f"Upper bound for {target} is at index {index}")
Searching in Infinite Lists
In cases where the list size is unknown or potentially infinite, such as searching for a specific condition in a stream of data, binary search can be adapted. This involves first identifying a range where the target could be and then performing binary search within that range.
pythondef find_in_infinite_list(is_target):
low = 0
high = 1
# First, find the bounds
while not is_target(high):
low = high
high *= 2
# Now perform binary search within these bounds
while low <= high:
mid = (low + high) // 2
if is_target(mid):
return mid
elif not is_target(mid):
low = mid + 1
else:
high = mid - 1
return -1
# Example usage
def is_target(index):
# Example condition for target: true if index is greater than or equal to 100
return index >= 100
result = find_in_infinite_list(is_target)
print(f"Target found at index {result}")
Binary Search in Practice
In real-world applications, binary search is often used in conjunction with other algorithms and data structures. Here are a few examples:
- Databases: Indexes in databases are often implemented using binary search on sorted keys for quick lookup.
- Libraries: Many programming libraries provide binary search functions for common tasks, such as searching in sorted lists or arrays.
- Competitive Programming: Binary search is frequently used in competitive programming problems, particularly in scenarios involving range queries or finding optimal solutions efficiently.
Binary Search in Libraries and Frameworks
Many programming languages and libraries provide built-in functions to perform binary search, making it easier for developers to implement this algorithm efficiently without having to write it from scratch. Here are some examples:
Python: bisect
Module
Python’s bisect
module provides functions for binary search operations on sorted lists:
bisect_left
: Finds the insertion point for a value in a sorted list to maintain order (i.e., the lower bound).bisect_right
: Finds the insertion point for a value in a sorted list to maintain order, but returns the position to the right of any existing entries of the target (i.e., the upper bound).
Here’s how to use these functions:
pythonimport bisect
sorted_array = [1, 3, 3, 5, 7, 9, 11, 13, 15, 17, 19]
target = 3
# Finding the lower bound
lower_bound_index = bisect.bisect_left(sorted_array, target)
print(f"Lower bound for {target} is at index {lower_bound_index}")
# Finding the upper bound
upper_bound_index = bisect.bisect_right(sorted_array, target)
print(f"Upper bound for {target} is at index {upper_bound_index}")
C++: std::lower_bound
and std::upper_bound
In C++, the <algorithm>
library provides std::lower_bound
and std::upper_bound
for binary search operations:
cpp#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> sorted_array = {1, 3, 3, 5, 7, 9, 11, 13, 15, 17, 19};
int target = 3;
// Finding the lower bound
auto lower = std::lower_bound(sorted_array.begin(), sorted_array.end(), target);
std::cout << "Lower bound for " << target << " is at index " << (lower - sorted_array.begin()) << std::endl;
// Finding the upper bound
auto upper = std::upper_bound(sorted_array.begin(), sorted_array.end(), target);
std::cout << "Upper bound for " << target << " is at index " << (upper - sorted_array.begin()) << std::endl;
return 0;
}
Java: Collections.binarySearch
Java provides a binarySearch
method in the Collections
class for performing binary search on lists:
javaimport java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class BinarySearchExample {
public static void main(String[] args) {
List<Integer> sortedList = Arrays.asList(1, 3, 3, 5, 7, 9, 11, 13, 15, 17, 19);
int target = 7;
// Performing binary search
int index = Collections.binarySearch(sortedList, target);
if (index >= 0) {
System.out.println("Element " + target + " found at index " + index);
} else {
System.out.println("Element " + target + " not found in the list");
}
}
}
Common Mistakes and How to Avoid Them
Assuming the Array is Sorted:
- Mistake: Applying binary search to an unsorted array.
- Solution: Ensure the array is sorted before performing binary search. If not, sort it using a sorting algorithm or library function.
Incorrect Calculation of mid
:
- Mistake: Miscomputing the middle index, which can lead to incorrect results or infinite loops.
- Solution: Use
mid = (low + high) // 2
in languages like Python. In languages prone to overflow (e.g., C++ or Java), use mid = low + (high - low) / 2
.
Not Handling Edge Cases:
- Mistake: Failing to handle edge cases such as empty arrays or arrays with one element.
- Solution: Explicitly check for these conditions before proceeding with the binary search.
Infinite Loops:
- Mistake: Incorrectly updating the
low
and high
pointers, leading to infinite loops. - Solution: Ensure the pointers are correctly adjusted in each iteration. For example, use
low = mid + 1
when searching the right half and high = mid - 1
when searching the left half.
Assuming the Array is Sorted:
- Mistake: Applying binary search to an unsorted array.
- Solution: Ensure the array is sorted before performing binary search. If not, sort it using a sorting algorithm or library function.
Incorrect Calculation of mid
:
- Mistake: Miscomputing the middle index, which can lead to incorrect results or infinite loops.
- Solution: Use
mid = (low + high) // 2
in languages like Python. In languages prone to overflow (e.g., C++ or Java), usemid = low + (high - low) / 2
.
Not Handling Edge Cases:
- Mistake: Failing to handle edge cases such as empty arrays or arrays with one element.
- Solution: Explicitly check for these conditions before proceeding with the binary search.
Infinite Loops:
- Mistake: Incorrectly updating the
low
andhigh
pointers, leading to infinite loops. - Solution: Ensure the pointers are correctly adjusted in each iteration. For example, use
low = mid + 1
when searching the right half andhigh = mid - 1
when searching the left half.
Share
# Tags