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