Skip to content

Commit 6c0fc7e

Browse files
authored
Merge pull request #65 from junnengsoo/branch-binarySearch
docs: Branch binary search
2 parents 8a0d54a + 37ef1e4 commit 6c0fc7e

File tree

7 files changed

+180
-135
lines changed

7 files changed

+180
-135
lines changed
Lines changed: 11 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -1,137 +1,22 @@
11
# Binary Search
22

3+
## Background
4+
35
Binary search is a search algorithm that finds the position of a target value within a sorted array or list. It compares
46
the target value to the middle element of the search range, then, based on the comparison, narrows the search to the
57
upper or lower half of the current search range.
68

7-
Two versions of binary search has been implemented in this repository - BinarySearch and BinarySearchTemplated.
8-
9-
## BinarySearch
10-
11-
![binary search img](../../../../../docs/assets/images/BinarySearch.png)
12-
Image Source: GeeksforGeeks
13-
14-
BinarySearch is a more straightforward and intuitive version of the binary search algorithm. In this approach, after the
15-
mid-value is calculated, the high or low pointer is adjusted by just one unit. From the above example, after mid points
16-
to index 4 in the first search, the low pointer moves to index 5 (+1 from 4) when narrowing the search. Similarly, when
17-
mid points to index 7 in the second search, the high pointer shifts to index 6 (-1 from 7) when narrowing the search.
18-
This prevents any possibility of infinite loops. During the search, the moment mid-value is equal to the target value,
19-
the search ends prematurely. Note that there is no need for a "condition" method as the condition is already captured
20-
in the predicates of the if-else blocks.
21-
22-
## BinarySearchTemplated
23-
24-
BinarySearchTemplated removes the condition that checks if the current mid-value is equal to the target (which helps to
25-
end the search the moment the target is found). The template adds a "condition" method which will be modified based on
26-
the requirements of the implementation.
27-
28-
The narrowing of the search space differs from BinarySearch - only the high pointer will be adjusted by one unit.
29-
30-
This template will work for most binary search problems and will only require the following changes:
31-
32-
- Search space (high and low)
33-
- Condition method
34-
- Returned value (low or low - 1)
35-
36-
### Search Space (Requires change)
37-
38-
Simply modify the initialisation of the high and low pointer according to the [search space](#search-space-adjustment).
39-
40-
### Condition (Requires change)
41-
42-
We assume that when the condition returns true, the current value "passes" and when the condition returns false, the
43-
current value "fails".
44-
45-
Note that in this template, the conditional blocks
46-
47-
```
48-
if (condition(x)) {
49-
high = mid;
50-
} else {
51-
low = mid + 1;
52-
}
53-
```
54-
55-
requires elements that "fail" the condition to be on the left of the elements that "pass" the condition, see below, in a
56-
sorted array due to the way the high and low pointers are reassigned.
57-
58-
![binary search templated 1 img](../../../../../docs/assets/images/BinarySearchTemplated1.jpeg)
59-
60-
Hence, we will need to implement a condition method that is able to discern between arrays that "pass" and "fail"
61-
accurately and also place them in the correct relative positions i.e. "fail" on the left of "pass". Suppose we change
62-
the condition method implementation in BinarySearchTemplated from `value >= target` to `value <= target`, what will
63-
happen?
64-
<details>
65-
<summary> <b>what will happen?</b> </summary>
66-
The array becomes "P P F F F F" and the low and high pointers are now reassigned wrongly.
67-
</details>
68-
69-
### Returned Value (Requires change)
70-
71-
In this implementation of BinarySearchTemplated, we return the first "pass" in the array with `return low`. This is
72-
because our condition method implementation encompasses the target value that we are finding i.e. when
73-
`value == target`.
74-
75-
```java
76-
public static boolean condition(int value, int target) {
77-
return value >= target;
78-
}
79-
```
80-
81-
![binary search templated 1 img](../../../../../docs/assets/images/BinarySearchTemplated2.jpeg)
82-
83-
However, if we want to return the last "fail" in the array, we will `return low - 1`.
84-
85-
Suppose now we modify the condition to be `value > target`, how can we modify our BinarySearchTemplated to still work as
86-
expected?
87-
<details>
88-
<summary> <b>value > target?</b> </summary>
89-
Replace `return low` with `return low - 1` and replace arr[low] with arr[low - 1] as now the target value is the last
90-
"fail".
91-
</details>
92-
93-
### Search Space Adjustment
94-
95-
What should be the search space adjustment? Why is only low reassigned with an increment and not high?
96-
97-
Due to the nature of floor division in Java's \ operator, if there are two mid-values within the search range, which is
98-
when the number of elements is even, the first mid-value will be selected. Suppose we do not increment the low pointer
99-
during reassignment, `low = mid`, let us take a look at the following example:
100-
101-
![binary search templated 1 img](../../../../../docs/assets/images/BinarySearchTemplated3.jpeg)
102-
103-
The search space has been narrowed down to the range of index 1 (low) to 2 (high). The mid-value is calculated,
104-
`mid = (1 + 2) / 2`, to be 1 due to floor division. Since `2 < 5`, we enter the else block where there is reassignment
105-
of `low = mid`. This means that the low pointer is still pointing to index 1 and the high pointer remains unchanged at
106-
index 2. This results in an infinite loop as the search range is not narrowed down.
107-
108-
To resolve this issue, we need `low = mid + 1`, which will result in the low pointer pointing to index 2 in this
109-
scenario. We still ensure correctness because the mid-value is not the target value, as the mid-value < target, and we
110-
can safely exclude it from the search range.
111-
112-
Why do we not need to increment the high pointer during reassignment? This is because the mid-value could be the target
113-
as the condition implemented is `value >= target`, hence, we cannot exclude it from the search range.
114-
115-
See [here](./binarySearchTemplatedExamples/README.md) to use the template for other problems
116-
117-
Credits: [Powerful Ultimate Binary Search Template](https://leetcode.com/discuss/general-discussion/786126/python-powerful-ultimate-binary-search-template-solved-many-problems)
118-
119-
## Complexity Analysis
9+
## Implementation Invariant
12010

121-
**Time**:
11+
At the end of each iteration, the target value is either within the search range or does not exist in the search space.
12212

123-
- Worst case: O(log n)
124-
- Average case: O(log n)
125-
- Best case:
126-
- BinarySearch O(1)
127-
- BinarySearchTemplated O(log n)
13+
## BinarySearch and BinarySearchTemplate
12814

129-
BinarySearch:
130-
In the worst case, the target is either in the first index or does not exist in the array at all.
131-
In the best case, the target is the middle (odd number of element) or the first middle element (even number of elements)
132-
if floor division is used to determine the middle.
15+
We will discuss more implementation-specific details and complexity analysis in the respective folders. In short,
16+
1. The [binarySearch](binarySearch) method is a more straightforward and intuitive version of the binary search
17+
algorithm.
18+
2. The [binarySearchTemplate](binarySearchTemplated) method provides a more generalised template that can be used for
19+
most binary search problems by introducing a condition method that can be modified based on the requirements of the
20+
implementation.
13321

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

137-
**Space**: O(1) since no new data structures are used and searching is only done within the array given

src/main/java/algorithms/binarySearch/BinarySearch.java renamed to src/main/java/algorithms/binarySearch/binarySearch/BinarySearch.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package algorithms.binarySearch;
1+
package algorithms.binarySearch.binarySearch;
22

33
/**
44
* Here, we are implementing BinarySearch where we search an array for a target value at O(log n) time complexity.
@@ -8,6 +8,8 @@
88
* All elements in the array are unique. (to allow for easy testing)
99
* <p>
1010
* Brief Description and Implementation Invariant:
11+
*
12+
* Brief Description:
1113
* With the assumption that the array is sorted in ascending order, BinarySearch reduces the search range by half or
1214
* half + 1 (due to floor division) after every loop. This is done by reassigning the max (high) or min (low) of the
1315
* search range to the middle of the search range when the target value is smaller than or larger than the current
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# BinarySearch
2+
3+
## Background
4+
5+
BinarySearch is a more straightforward and intuitive version of the binary search algorithm. In this approach, after the
6+
mid-value is calculated, the high or low pointer is adjusted by just one unit.
7+
8+
### Illustration
9+
10+
![binary search img](../../../../../../docs/assets/images/BinarySearch.png)
11+
12+
Image Source: GeeksforGeeks
13+
14+
From the above example, after mid points to index 4 in the first search, the low pointer moves to index 5 (+1 from 4)
15+
when narrowing the search. Similarly, when mid points to index 7 in the second search, the high pointer shifts to index
16+
6 (-1 from 7) when narrowing the search. This prevents any possibility of infinite loops. During the search, the moment
17+
mid-value is equal to the target value, the search ends prematurely.
18+
19+
## Complexity Analysis
20+
**Time**:
21+
- Worst case: O(log n)
22+
- Average case: O(log n)
23+
- Best case: O(1)
24+
25+
In the worst case, the target is either in the first or last index or does not exist in the array at all.
26+
In the best case, the target is the middle (odd number of element) or the first middle element (even number of elements)
27+
if floor division is used to determine the middle.
28+
29+
**Space**: O(1)
30+
31+
Since no new data structures are used and searching is only done within the array given.

src/main/java/algorithms/binarySearch/BinarySearchTemplated.java renamed to src/main/java/algorithms/binarySearch/binarySearchTemplated/BinarySearchTemplated.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package algorithms.binarySearch;
1+
package algorithms.binarySearch.binarySearchTemplated;
22

33
/**
44
* Here, we are implementing BinarySearchTemplated where we search an array for a target value at O(log n) time
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
# BinarySearchTemplated
2+
3+
## Background
4+
5+
BinarySearchTemplated is a more generalised algorithm of [BinarySearch](../binarySearch) that removes the condition that
6+
checks if the current mid-value is equal to the target (which helps to end the search the moment the target is found).
7+
The template adds a "condition" method which will be modified based on the requirements of the implementation.
8+
9+
The narrowing of the search space differs from BinarySearch - only the high pointer will be adjusted by one unit.
10+
11+
This template will work for most binary search problems and will require the following changes when used for different
12+
problems:
13+
- Search space (high and low pointers)
14+
- Condition method
15+
- Returned value (low or low - 1)
16+
17+
### Search Space (Requires change)
18+
Initialise the boundary values of the high and low pointers to include all possible elements in the search space.
19+
20+
### Condition (Requires change)
21+
We assume that when the condition returns true, the current value "passes" and when the condition returns false, the
22+
current value "fails".
23+
24+
Note that in this template, the conditional blocks
25+
```
26+
if (condition(x)) {
27+
high = mid;
28+
} else {
29+
low = mid + 1;
30+
}
31+
```
32+
requires elements that "fail" the condition to be on the left of the elements that "pass" the condition, see below, in a
33+
sorted array due to the way the high and low pointers are reassigned.
34+
35+
![binary search templated 1 img](../../../../../../docs/assets/images/BinarySearchTemplated1.jpeg)
36+
37+
Hence, we will need to implement a condition method that is able to discern between arrays that "pass" and "fail"
38+
accurately and also place them in the correct relative positions i.e. "fail" on the left of "pass". Suppose we change
39+
the condition method implementation by inverting the inputs that "pass" the condition, i.e, inputs that "fail" now
40+
"pass" and vice-versa, what will happen?
41+
<details>
42+
<summary> <b>what will happen?</b> </summary>
43+
The array becomes "P P F F F F" and our current template will not work as expected. This is because the low and high
44+
pointers are now reassigned wrongly - the loop invariant is broken as the search space is narrowed down in the wrong
45+
direction.
46+
47+
To resolve this issue, there are two fixes:
48+
1. Swap the conditional blocks of low = mid + 1 and high = mid.
49+
**OR**
50+
2. Simply add a "not" in front of the condition, converting "P P F F F F" back to "F F P P P P".
51+
52+
Note that some conditions may be easier to define with "pass" elements being on the left of "fail" elements, i.e.
53+
"P P F F F F", hence, it is important to adjust the code with the above fixes accordingly.
54+
</details>
55+
56+
### Returned Value (Requires change)
57+
In this implementation of BinarySearchTemplated, we return the first "pass" in the array with `return low`. This is
58+
because our condition method implementation encompasses the target value that we are finding i.e. when
59+
`value == target`.
60+
61+
```java
62+
public static boolean condition(int value, int target) {
63+
return value >= target;
64+
}
65+
```
66+
![binary search templated 2 img](../../../../../../docs/assets/images/BinarySearchTemplated2.jpeg)
67+
68+
However, if we want to return the last "fail" in the array, we will `return low - 1`.
69+
70+
Suppose now we modify the condition to be `value > target`, how can we modify our BinarySearchTemplated to still work as
71+
expected?
72+
<details>
73+
<summary> <b>value > target?</b> </summary>
74+
Replace `return low` with `return low - 1` and replace arr[low] with arr[low - 1] as now the target value is the last
75+
"fail".
76+
</details>
77+
78+
79+
### Search Space Adjustment
80+
What should be the search space adjustment? Why is only low reassigned with an increment and not high?
81+
82+
Due to the nature of floor division in Java's \ operator, if there are two mid-values within the search range, which is
83+
when the number of elements is even, the first mid-value will be selected. Suppose we do not increment the low pointer
84+
during reassignment, `low = mid`, let us take a look at the following example:
85+
86+
![binary search templated 1 img](../../../../../docs/assets/images/BinarySearchTemplated3.jpeg)
87+
88+
The search space has been narrowed down to the range of index 1 (low) to 2 (high). The mid-value is calculated,
89+
`mid = (1 + 2) / 2`, to be 1 due to floor division. Since `2 < 5`, we enter the else block where there is reassignment
90+
of `low = mid`. This means that the low pointer is still pointing to index 1 and the high pointer remains unchanged at
91+
index 2. This results in an infinite loop as the search range is not narrowed down.
92+
93+
To resolve this issue, we need `low = mid + 1`, which will result in the low pointer pointing to index 2 in this
94+
scenario. We still ensure correctness because the mid-value is not the target value, as the mid-value < target, and we
95+
can safely exclude it from the search range.
96+
97+
Why should we not decrement the high pointer during reassignment? This is because the mid-value could be the target
98+
as the condition implemented is `value >= target`, hence, we cannot exclude it from the search range.
99+
100+
Note that if ceiling division is used instead, `high = mid - 1` will be required to prevent an infinite loop. Both
101+
`low = mid + 1` and `low = mid` will work in this case.
102+
103+
See [here](binarySearchTemplatedExamples/README.md) to use the template for other problems.
104+
105+
Credits: [Powerful Ultimate Binary Search Template](https://leetcode.com/discuss/general-discussion/786126/python-powerful-ultimate-binary-search-template-solved-many-problems)
106+
107+
108+
## Complexity Analysis
109+
**Time**:
110+
- Worst case: O(log n)
111+
- Average case: O(log n)
112+
- Best case: O(log n)
113+
114+
In all cases, O(log n) iterations will be required as there is no condition to exit the loop prematurely.
115+
116+
**Space**: O(1)
117+
118+
Since no new data structures are used and searching is only done within the array given.

src/main/java/algorithms/binarySearch/binarySearchTemplatedExamples/README.md renamed to src/main/java/algorithms/binarySearch/binarySearchTemplated/binarySearchTemplatedExamples/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ right of those that fail! Since "all the versions after a bad version are also b
7272

7373
Since we want to return the first bad version, we will return low.
7474

75-
![first bad version img](../../../../../../docs/assets/images/firstBadVersion.jpeg)
75+
![first bad version img](../../../../../../../docs/assets/images/firstBadVersion.jpeg)
7676

7777
<b> Full Solution: </b>
7878

src/test/java/algorithms/binarySearch/BinarySearchTest.java

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
package algorithms.binarySearch;
22

3-
import static org.junit.Assert.assertEquals;
4-
3+
import algorithms.binarySearch.binarySearch.BinarySearch;
4+
import algorithms.binarySearch.binarySearchTemplated.BinarySearchTemplated;
55
import org.junit.Test;
66

7-
/**
8-
* Test cases for {@link BinarySearch}.
9-
*/
7+
import static org.junit.Assert.assertEquals;
8+
109
public class BinarySearchTest {
1110
@Test
1211
public void test_binarySearch() {
@@ -37,12 +36,22 @@ public void test_binarySearchTemplated() {
3736
int[] secondArray = {1, 5, 10, 11, 12};
3837
int secondResult = BinarySearchTemplated.search(secondArray, 11);
3938

40-
// Test 3: target not in array
39+
// Test 3: target not in array but could exist within search space
4140
int[] thirdArray = {1, 5, 10, 11, 12};
4241
int thirdResult = BinarySearchTemplated.search(thirdArray, 3);
4342

43+
// Test 4: target not in array but could exist on the right of search space
44+
int[] fourthArray = {1, 5, 10, 11, 12};
45+
int fourthResult = BinarySearchTemplated.search(thirdArray, 13);
46+
47+
// Test 3: target not in array but could exist on the left of search space
48+
int[] fifthArray = {1, 5, 10, 11, 12};
49+
int fifthResult = BinarySearchTemplated.search(thirdArray, 0);
50+
4451
assertEquals(0, firstResult);
4552
assertEquals(3, secondResult);
4653
assertEquals(-1, thirdResult);
54+
assertEquals(-1, fourthResult);
55+
assertEquals(-1, fifthResult);
4756
}
4857
}

0 commit comments

Comments
 (0)