Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added assets/BinarySearch.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

This file was deleted.

This file was deleted.

49 changes: 49 additions & 0 deletions src/algorithms/searches/binarySearch/BinarySearch.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package src.algorithms.searches.binarySearch;

/** Here, we are implementing BinarySearch where we search an array for a target value at O(log n) time complexity.
*
* Assumptions:
* The array is sorted in ascending order.
* All elements in the array are unique. (to allow for easy testing)
*
* Brief Description and Implementation Invariant:
* With the assumption that the array is sorted in ascending order, BinarySearch reduces the search range by half or
* half + 1 (due to floor division) after every loop. This is done by reassigning the max (high) or min (low) of the
* search range to the middle of the search range when the target value is smaller than or larger than the current
* middle value respectively, and continuing the search thereafter.
*
* In both scenarios where the target is not equal to arr[mid], the high and low pointers are reassigned mid decremented
* /incremented by 1. This ensures that there will never be an infinite loop as the search range will no longer include
* the mid-value, causing the search range to constantly "shrink". We know we can decrement/increment mid by 1 during
* the reassignment as the mid-value is definitely not the target and should no longer be in the search range.
*
* At the end of every loop, we know that the target value is either within the search range or does not exist in the
* array, thereby ensuring the correctness of the algorithm.
*
* Since after each iteration, the search range is halved, it will only take a maximum of O(log n) times before the
* target is either found or determined to not exist in the array.
*/
public class BinarySearch {
/**
* Searches for a target value within a sorted array using the binary search algorithm.
* @param arr the sorted array in which the search is to be performed.
* @param target the value to be searched for.
* @return the index of the target if found, otherwise -1.
*/
public static int search(int[] arr, int target) {
int high = arr.length - 1; // max index is 3 if array length is 4
int low = 0;
while (low <= high) { // When low == high, arr[low] can still == target, therefore should still check
int mid = low + (high - low) / 2; // equivalent to high + low / 2 but reduces cases of integer overflow
if (arr[mid] > target) {
high = mid - 1; // -1 since current mid is not == target and should not be in the search range anymore
} else if (arr[mid] < target) {
low = mid + 1; // +1 since current mid is not == target and should not be in the search range anymore
} else {
return mid;
}
}

return -1;
}
}
66 changes: 66 additions & 0 deletions src/algorithms/searches/binarySearch/BinarySearchTemplated.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package src.algorithms.searches.binarySearch;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for the templated version, can also construct ur own problem / source from tutorial qn (this would be best) or give a link to a leetcode BS qn like koko eating bananas


/** Here, we are implementing BinarySearchTemplated where we search an array for a target value at O(log n) time
* complexity.
*
* Assumptions:
* The array is sorted in ascending order.
* All elements in the array are unique. (to allow for easy testing)
*
* Brief Description and Implementation Invariant:
* With the assumption that the array is sorted in ascending order, BinarySearchTemplated reduces the search range by
* half or half + 1 (due to floor division) after every loop. This is done by reassigning the max (high) or min (low) of
* the search range to the middle of the search range when the target value is smaller than or larger than the current
* middle value respectively, and continuing the search thereafter.
*
* In this version, there is no condition to check if the current mid is equal to the target to prematurely end the
* search. Hence, the only way for the loop to complete is when low exceeds high.
*
* At the end of every loop, we know that the target value is either within the search range or does not exist in the
* array, thereby ensuring the correctness of the algorithm.
*
* Since after each iteration, the search range is halved, it will only take a maximum of O(log n) times before the
* target is either found or determined to not exist in the array.
*/
public class BinarySearchTemplated {
/**
* A utility method to compare two integers.
* @param value The current value from the array.
* @param target The value being searched for.
* @return true if the current value is less than the target, otherwise false.
*/
// The condition should be changed accordingly
public static boolean condition(int value, int target) {
return value >= target;
}

/**
* Conducts a binary search to find the target within the sorted array.
*
* @param arr The sorted input array.
* @param target The value to be searched within the array.
* @return The index of the target value if found, otherwise -1.
*/
public static int search(int[] arr, int target) {
// The search space i.e. high and low should be changed accordingly.
int high = arr.length - 1; // max index is 3 if array length is 4
int low = 0;
while (low < high) {
int mid = low + (high - low) / 2; // equivalent to high + low / 2 but reduces cases of integer overflow
if (condition(arr[mid], target)) { // if value >= target
high = mid;
} else { // if value < target
low = mid + 1;
}
}

// Checks if low value is indeed the target
// Note that the following checks may not be required in other use cases of this template
if (low < arr.length && arr[low] == target) {
// returned value should be changed accordingly (low or low - 1)
return low;
}
// Returns -1 if loop was exited without finding the target
return -1;
}
}
78 changes: 78 additions & 0 deletions src/algorithms/searches/binarySearch/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Binary Search
Binary search is a search algorithm that finds the position of a target value within a sorted array or list. It compares
the target value to the middle element of the search range, then, based on the comparison, narrows the search to the
upper or lower half of the current search range.

Two versions of binary search has been implemented in this repository - BinarySearch and BinarySearchTemplated.

## BinarySearch
![binary search img](../../../../assets/BinarySearch.png)
Image Source: GeeksforGeeks

BinarySearch is a more straightforward and intuitive version of the binary search algorithm. In this approach, after the
mid-value is calculated, the high and low pointers are adjusted by just one unit. From the above example, after mid
points to index 4 in the first search, the low pointer moves to index 5 (+1) when narrowing the search. Similarly, when
mid points to index 7 in the second search, the high pointer shifts to index 6 (-1) when narrowing the search. This
prevents any possibility of infinite loops.

## BinarySearchTemplated

BinarySearchTemplated removes the condition that checks if the current mid-value is equal to the target (which helps to
end the search the moment the target is found). The template adds a "condition" method which will be modified based on
the requirements of the implementation.

The narrowing of the search space differs from BinarySearch - only one of the high or low pointers will be adjusted by
one unit.

This template will work for most binary search problems and will only require the following changes:
- Search space (high and low)
- Condition method
- Returned value (low or low - 1)

### Search Space (Requires change)
Simply modify the initialisation of the high and low pointer according to the search space.

### Condition (Requires change)
We assume that when the condition returns true, the current value "passes" and when the condition returns false, the
current value "fails".

In this template, we want to find the first "pass" in the array.

INSERT IMAGE OF FIRST PASS

### Returned Value (Requires change)
In the implementation of BinarySearchTemplated, return low was used to find the first "pass".

EXPLANATION TBC, STILL THINKING HOW TO PHRASE IT.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LOL ok


### Search Space Adjustment
What should be the search space adjustment? (Why only low = mid + 1)

Due to the nature of floor division in Java's \ operator, the searched mid-index will always be smaller than the high
pointer of the previous search range. On the other hand, low = mid + 1, ensures that the searched mid-index is always
larger than the low pointer of the previous search range. This ensures that the search range is narrowed in every loop
and prevents the possibility of infinite loops.

INSERT IMAGE HERE TO EXPLAIN

As we close in towards the target value, the final low = mid + 1 narrows the search range from low. TBC ON EXPLANATION

Credits: [Powerful Ultimate Binary Search Template](https://leetcode.com/discuss/general-discussion/786126/python-powerful-ultimate-binary-search-template-solved-many-problems)

## Complexity Analysis
**Time**:
- Worst case: O(log n)
- Average case: O(log n)
- Best case:
- BinarySearch O(1)
- BinarySearchTemplated O(log n)

BinarySearch:
In the worst case, the target is either in the first index or does not exist in the array at all.
In the best case, the target is the middle (odd number of element) or the first middle element (even number of elements)
if floor division is used to determine the middle.

BinaryTemplated:
In all cases, O(log n) iterations will be required as there is no condition to exit the loop prematurely.

**Space**: O(1) since no new data structures are used and searching is only done within the array given
51 changes: 51 additions & 0 deletions test/algorithms/binarySearchTest/BinarySearchTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package test.algorithms.binarySearchTest;

import org.junit.Test;

import src.algorithms.searches.binarySearch.BinarySearchTemplated;
import src.algorithms.searches.binarySearch.BinarySearch;

import java.util.Arrays;

import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;

public class BinarySearchTest {
@Test
public void test_binarySearch() {
// Test 1: even number of elements
int[] firstArray = {1, 5, 10, 12};
int firstResult = BinarySearch.search(firstArray, 1);

// Test 2: odd number of elements
int[] secondArray = {1, 5, 10, 11, 12};
int secondResult = BinarySearch.search(secondArray, 11);

// Test 3: target not in array
int[] thirdArray = {1, 5, 10, 11, 12};
int thirdResult = BinarySearch.search(thirdArray, 3);

assertEquals(0, firstResult);
assertEquals(3, secondResult);
assertEquals(-1, thirdResult);
}

@Test
public void test_binarySearchTemplated() {
// Test 1: even number of elements
int[] firstArray = {1, 5, 10, 12};
int firstResult = BinarySearchTemplated.search(firstArray, 1);

// Test 2: odd number of elements
int[] secondArray = {1, 5, 10, 11, 12};
int secondResult = BinarySearchTemplated.search(secondArray, 11);

// Test 3: target not in array
int[] thirdArray = {1, 5, 10, 11, 12};
int thirdResult = BinarySearchTemplated.search(thirdArray, 3);

assertEquals(0, firstResult);
assertEquals(3, secondResult);
assertEquals(-1, thirdResult);
}
}