For additional reading and resources, you can refer to https://jeffe.cs.illinois.edu/teaching/algorithms/book/00-intro.pdf and https://jeffe.cs.illinois.edu/teaching/algorithms/book/01-recursion.pdf
In the morning, we learned about linear search:
def linear_search(arr, target):
for i in range(0, len(arr)):
if arr[i] == target:
return i
return -1
drinks = ["coffee", "tea", "soda", "water"]
print(linear_search(drinks, "coffee"))
0
print(linear_search(drinks, "juice"))
-1
We said that we could IMPROVE on the linear search by dividing our search space in half each time. This leads us to a new algorithm: binary search!
Binary search works by dividing our search space in half. Consider the sorted array below:
grades = [10, 20, 30, 40, 50, 60, 70, 80, 90]
If we want to find the value, 20
, we can do this by taking the element in the middle of the array and comparing it to the value we want to find.
This is conceptually more like:
where we reduce the elements we need to look at.
def binary_search(arr, target):
pass
3
print(binary_search(grades, 80) != -1)
print(binary_search(grades, 25) != -1)
We wrote the above binary search algorithm in an iterative way. However, we've also talked about recursion. The cool thing is that we can also write this binary search function recursively.
How do we do this? Let's think about how we will set up our recursion.
What inputs would we need for our recursive function? Keep in mind that for the iterative solution, we have the array and the element we are looking for.
A recursive function is made up of 2 parts: the base case and the recursive step. First, let's think about what the base case would be.
# Base case pseudocode
# Recursive step pseudocode
Now that we have these 3 parts, we can put it together into a recursive function:
def binary_search():
pass
print(binary_search(grades, 80, 0, len(grades) - 1) != -1)
print(binary_search(grades, 25, 0, len(grades) - 1) != -1)
Binary search reduces the amount of space we have to look through each time. With linear search, in the worst case, we have to look at every element in the array ($O(N)$). However, with binary search, in the worst case we only have to look at half the elements in the array.
If the number of elements in the array is $N$, then with binary search we are looking at $N/2$ elements. In general, any time we are dividing our search space, this ends up being a logarithmic time complexity $O(log(N))$. This is because by dividing our space, we are reducing the time complexity to be less than $O(N)$.
Recall the graph shown from this morning:
In this graph, we can see $O(log(N))$ grows slower than $O(N)$.
We will see more algorithms where we divide our search space in the coming days.
Why does binary search work? It only works when we are given a SORTED array or list. When the array or list is sorted, we are guaranteed that all elements to the right of X are greater than X and all elements to the left of X are less than X.
What do you think would happen if we tried binary search on an unsorted array? Discuss with the student next to you.
unsorted_arr = [7, 2, 6, 0, 18, 4, 9]
print(binary_search(unsorted_arr, 4, 0, len(unsorted_arr) - 1) != -1)
# If there is time, if not it will be next lecture