-
Notifications
You must be signed in to change notification settings - Fork 21k
Expand file tree
/
Copy pathBinarySearch.java
More file actions
134 lines (125 loc) · 5.64 KB
/
BinarySearch.java
File metadata and controls
134 lines (125 loc) · 5.64 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
package com.thealgorithms.searches;
import com.thealgorithms.devutils.searches.SearchAlgorithm;
/**
* Binary Search Algorithm Implementation
*
* <p>Binary search is one of the most efficient searching algorithms for finding a target element
* in a SORTED array. It works by repeatedly dividing the search space in half, eliminating half of
* the remaining elements in each step.
*
* <p>IMPORTANT: This algorithm ONLY works correctly if the input array is sorted in ascending
* order.
*
* <p>Algorithm Overview: 1. Start with the entire array (left = 0, right = array.length - 1) 2.
* Calculate the middle index 3. Compare the middle element with the target: - If middle element
* equals target: Found! Return the index - If middle element is less than target: Search the right
* half - If middle element is greater than target: Search the left half 4. Repeat until element is
* found or search space is exhausted
*
* <p>Performance Analysis: - Best-case time complexity: O(1) - Element found at middle on first
* try - Average-case time complexity: O(log n) - Most common scenario - Worst-case time
* complexity: O(log n) - Element not found or at extreme end - Space complexity: O(1) - Only uses
* a constant amount of extra space
*
* <p>Example Walkthrough: Array: [1, 3, 5, 7, 9, 11, 13, 15, 17, 19] Target: 7
*
* <p>Step 1: left=0, right=9, mid=4, array[4]=9 (9 > 7, search left half) Step 2: left=0,
* right=3, mid=1, array[1]=3 (3 < 7, search right half) Step 3: left=2, right=3, mid=2,
* array[2]=5 (5 < 7, search right half) Step 4: left=3, right=3, mid=3, array[3]=7 (Found!
* Return index 3)
*
* @author Varun Upadhyay (https://github.com/varunu28)
* @author Podshivalov Nikita (https://github.com/nikitap492)
* @see SearchAlgorithm
* @see IterativeBinarySearch
*/
class BinarySearch implements SearchAlgorithm {
/**
* Generic method to perform binary search on any comparable type. This is the main entry point
* for binary search operations.
*
* <p>Example Usage:
* <pre>
* Integer[] numbers = {1, 3, 5, 7, 9, 11};
* int result = new BinarySearch().find(numbers, 7);
* // result will be 3 (index of element 7)
*
* int notFound = new BinarySearch().find(numbers, 4);
* // notFound will be -1 (element 4 does not exist)
* </pre>
*
* @param <T> The type of elements in the array (must be Comparable)
* @param array The sorted array to search in (MUST be sorted in ascending order)
* @param key The element to search for
* @return The index of the key if found, -1 if not found or if array is null/empty
*/
@Override
public <T extends Comparable<T>> int find(T[] array, T key) {
// Handle edge case: null or empty array
if (array == null || array.length == 0) {
return -1;
}
// Handle edge case: null key
// Searching for null in an array of Comparables is undefined behavior
// Return -1 to indicate not found rather than throwing NPE
if (key == null) {
return -1;
}
// Delegate to the core search implementation
return search(array, key, 0, array.length - 1);
}
/**
* Core recursive implementation of binary search algorithm. This method divides the problem
* into smaller subproblems recursively.
*
* <p>How it works:
* <ol>
* <li>Calculate the middle index to avoid integer overflow</li>
* <li>Check if middle element matches the target</li>
* <li>If not, recursively search either left or right half</li>
* <li>Base case: left > right means element not found</li>
* </ol>
*
* <p>Time Complexity: O(log n) because we halve the search space each time.
* Space Complexity: O(log n) due to recursive call stack.
*
* @param <T> The type of elements (must be Comparable)
* @param array The sorted array to search in
* @param key The element we're looking for
* @param left The leftmost index of current search range (inclusive)
* @param right The rightmost index of current search range (inclusive)
* @return The index where key is located, or -1 if not found
*/
private <T extends Comparable<T>> int search(T[] array, T key, int left, int right) {
// Base case: Search space is exhausted
// This happens when left pointer crosses right pointer
if (right < left) {
return -1; // Key not found in the array
}
// Calculate middle index
// Using (left + right) / 2 could cause integer overflow for large arrays
// So we use: left + (right - left) / 2 which is mathematically equivalent
// but prevents overflow
int median = (left + right) >>> 1; // Unsigned right shift is faster division by 2
// Get the value at middle position for comparison
int comp = key.compareTo(array[median]);
// Case 1: Found the target element at middle position
if (comp == 0) {
return median; // Return the index where element was found
}
// Case 2: Target is smaller than middle element
// This means if target exists, it must be in the LEFT half
else if (comp < 0) {
// Recursively search the left half
// New search range: [left, median - 1]
return search(array, key, left, median - 1);
}
// Case 3: Target is greater than middle element
// This means if target exists, it must be in the RIGHT half
else {
// Recursively search the right half
// New search range: [median + 1, right]
return search(array, key, median + 1, right);
}
}
}