# Chapter 5: Search Algorithms

## 5.2: Binary Search

Binary Search is an algorithm that is highly efficient when compared to Linear Search, provided that certain conditions are met. In this case, Binary Search follows the divide-and-conquer principle, which we have talked about in detail in Chapter 4.

To further understand this concept, let's take a closer look at how it works. First, Binary Search examines the middle element of a sorted list. If the target value is equal to this middle element, it means that we have successfully found our target and the search process can be terminated. However, if the target value is less than the middle element, we can assume that the target value cannot be found in the right half of the list. As a result, the search process will only continue on the left half of the list. On the other hand, if the target value is greater than the middle element, we can safely assume that the target value cannot be found in the left half of the list. Hence, the search process will only continue on the right half of the list.

This process of searching and dividing the search space continues until we find the target value or until the search space is empty. With each iteration, the search space is reduced by half, allowing Binary Search to quickly narrow down the possibilities and find the target value much faster than Linear Search.

As promised, let's now walk through an example of how Binary Search works and provide some code to help illustrate the process.

Now, let's illustrate this with an example. Suppose we have a sorted list of numbers:

`numbers = [2, 5, 8, 12, 16, 23, 38, 56, 72, 91]`

and we want to find the number `23`

.

- First, we look at the middle of the list, which is
`16`

. Since`23 > 16`

, we know`23`

must be in the right half of the list. - Next, we look at the middle of the right half, which is
`38`

. Since`23 < 38`

, we know`23`

must be in the left half of this sublist (`[23, 38]`

). - We then look at the middle of
`[23, 38]`

, which is`23`

. Since`23 == 23`

, we've found our target, and the search is over.

Now, let's see how we can implement this in Python:

`def binary_search(arr, target):`

left = 0

right = len(arr) - 1

while left <= right:

mid = (left + right) // 2

if arr[mid] == target:

return mid # Element found, return its index

elif arr[mid] < target:

left = mid + 1

else:

right = mid - 1

return -1 # Element not found

# Test our function

numbers = [2, 5, 8, 12, 16, 23, 38, 56, 72, 91]

print(binary_search(numbers, 23)) # Outputs: 5

In the above Python code, we define a function `binary_search`

that takes a sorted list `arr`

and a `target`

value as inputs. It starts by setting `left`

and `right`

pointers to the start and end of the list, respectively. It then enters a loop that continues until `left`

and `right`

meet.

In each iteration of the loop, it calculates `mid`

, the index of the middle element. If this middle element is equal to `target`

, it returns the index. If `target`

is greater, it moves the `left`

pointer to `mid + 1`

. If `target`

is less, it moves the `right`

pointer to `mid - 1`

. If the loop completes without finding the target, it returns `-1`

, indicating that the target is not present in the list.

In the following sections, we'll delve deeper into understanding the performance of Binary Search and how it compares to other search algorithms.

The Binary Search algorithm demonstrates a significant improvement in time complexity over Linear Search. Each comparison cuts down our search space in half. Therefore, in a list of

elements, in the worst-case scenario, we would need to make **n**

comparisons to either find our target or confirm it's not in the list.**log2(n)**

Given this logarithmic relationship between the size of the list and the number of steps, we say that Binary Search has a time complexity of

. Remember, "log" here is base 2, but in Big O notation, we typically ignore this detail, as we're most interested in the rate of growth, not the specific calculations.**O(log n)**

In terms of space complexity, Binary Search shines again: it only needs a constant amount of extra space to store the

, **left**

, and **right**

variables, regardless of the size of the list. So the space complexity is **mid**

, which means it uses a constant amount of space.**O(1)**

The improved time and space complexities are what make Binary Search a preferred choice for searching in sorted lists, especially when the size of the list is large. However, the requirement for the list to be sorted is a trade-off that needs to be considered.

In summary, understanding the complexities of an algorithm like Binary Search allows us to make informed decisions about which algorithms are best suited to solve specific problems, especially when dealing with constraints around time and space.

## 5.2: Binary Search

Binary Search is an algorithm that is highly efficient when compared to Linear Search, provided that certain conditions are met. In this case, Binary Search follows the divide-and-conquer principle, which we have talked about in detail in Chapter 4.

To further understand this concept, let's take a closer look at how it works. First, Binary Search examines the middle element of a sorted list. If the target value is equal to this middle element, it means that we have successfully found our target and the search process can be terminated. However, if the target value is less than the middle element, we can assume that the target value cannot be found in the right half of the list. As a result, the search process will only continue on the left half of the list. On the other hand, if the target value is greater than the middle element, we can safely assume that the target value cannot be found in the left half of the list. Hence, the search process will only continue on the right half of the list.

This process of searching and dividing the search space continues until we find the target value or until the search space is empty. With each iteration, the search space is reduced by half, allowing Binary Search to quickly narrow down the possibilities and find the target value much faster than Linear Search.

As promised, let's now walk through an example of how Binary Search works and provide some code to help illustrate the process.

Now, let's illustrate this with an example. Suppose we have a sorted list of numbers:

`numbers = [2, 5, 8, 12, 16, 23, 38, 56, 72, 91]`

and we want to find the number `23`

.

- First, we look at the middle of the list, which is
`16`

. Since`23 > 16`

, we know`23`

must be in the right half of the list. - Next, we look at the middle of the right half, which is
`38`

. Since`23 < 38`

, we know`23`

must be in the left half of this sublist (`[23, 38]`

). - We then look at the middle of
`[23, 38]`

, which is`23`

. Since`23 == 23`

, we've found our target, and the search is over.

Now, let's see how we can implement this in Python:

`def binary_search(arr, target):`

left = 0

right = len(arr) - 1

while left <= right:

mid = (left + right) // 2

if arr[mid] == target:

return mid # Element found, return its index

elif arr[mid] < target:

left = mid + 1

else:

right = mid - 1

return -1 # Element not found

# Test our function

numbers = [2, 5, 8, 12, 16, 23, 38, 56, 72, 91]

print(binary_search(numbers, 23)) # Outputs: 5

In the above Python code, we define a function `binary_search`

that takes a sorted list `arr`

and a `target`

value as inputs. It starts by setting `left`

and `right`

pointers to the start and end of the list, respectively. It then enters a loop that continues until `left`

and `right`

meet.

In each iteration of the loop, it calculates `mid`

, the index of the middle element. If this middle element is equal to `target`

, it returns the index. If `target`

is greater, it moves the `left`

pointer to `mid + 1`

. If `target`

is less, it moves the `right`

pointer to `mid - 1`

. If the loop completes without finding the target, it returns `-1`

, indicating that the target is not present in the list.

In the following sections, we'll delve deeper into understanding the performance of Binary Search and how it compares to other search algorithms.

The Binary Search algorithm demonstrates a significant improvement in time complexity over Linear Search. Each comparison cuts down our search space in half. Therefore, in a list of

elements, in the worst-case scenario, we would need to make **n**

comparisons to either find our target or confirm it's not in the list.**log2(n)**

Given this logarithmic relationship between the size of the list and the number of steps, we say that Binary Search has a time complexity of

. Remember, "log" here is base 2, but in Big O notation, we typically ignore this detail, as we're most interested in the rate of growth, not the specific calculations.**O(log n)**

In terms of space complexity, Binary Search shines again: it only needs a constant amount of extra space to store the

, **left**

, and **right**

variables, regardless of the size of the list. So the space complexity is **mid**

, which means it uses a constant amount of space.**O(1)**

The improved time and space complexities are what make Binary Search a preferred choice for searching in sorted lists, especially when the size of the list is large. However, the requirement for the list to be sorted is a trade-off that needs to be considered.

In summary, understanding the complexities of an algorithm like Binary Search allows us to make informed decisions about which algorithms are best suited to solve specific problems, especially when dealing with constraints around time and space.

## 5.2: Binary Search

Binary Search is an algorithm that is highly efficient when compared to Linear Search, provided that certain conditions are met. In this case, Binary Search follows the divide-and-conquer principle, which we have talked about in detail in Chapter 4.

To further understand this concept, let's take a closer look at how it works. First, Binary Search examines the middle element of a sorted list. If the target value is equal to this middle element, it means that we have successfully found our target and the search process can be terminated. However, if the target value is less than the middle element, we can assume that the target value cannot be found in the right half of the list. As a result, the search process will only continue on the left half of the list. On the other hand, if the target value is greater than the middle element, we can safely assume that the target value cannot be found in the left half of the list. Hence, the search process will only continue on the right half of the list.

This process of searching and dividing the search space continues until we find the target value or until the search space is empty. With each iteration, the search space is reduced by half, allowing Binary Search to quickly narrow down the possibilities and find the target value much faster than Linear Search.

As promised, let's now walk through an example of how Binary Search works and provide some code to help illustrate the process.

Now, let's illustrate this with an example. Suppose we have a sorted list of numbers:

`numbers = [2, 5, 8, 12, 16, 23, 38, 56, 72, 91]`

and we want to find the number `23`

.

- First, we look at the middle of the list, which is
`16`

. Since`23 > 16`

, we know`23`

must be in the right half of the list. - Next, we look at the middle of the right half, which is
`38`

. Since`23 < 38`

, we know`23`

must be in the left half of this sublist (`[23, 38]`

). - We then look at the middle of
`[23, 38]`

, which is`23`

. Since`23 == 23`

, we've found our target, and the search is over.

Now, let's see how we can implement this in Python:

`def binary_search(arr, target):`

left = 0

right = len(arr) - 1

while left <= right:

mid = (left + right) // 2

if arr[mid] == target:

return mid # Element found, return its index

elif arr[mid] < target:

left = mid + 1

else:

right = mid - 1

return -1 # Element not found

# Test our function

numbers = [2, 5, 8, 12, 16, 23, 38, 56, 72, 91]

print(binary_search(numbers, 23)) # Outputs: 5

In the above Python code, we define a function `binary_search`

that takes a sorted list `arr`

and a `target`

value as inputs. It starts by setting `left`

and `right`

pointers to the start and end of the list, respectively. It then enters a loop that continues until `left`

and `right`

meet.

In each iteration of the loop, it calculates `mid`

, the index of the middle element. If this middle element is equal to `target`

, it returns the index. If `target`

is greater, it moves the `left`

pointer to `mid + 1`

. If `target`

is less, it moves the `right`

pointer to `mid - 1`

. If the loop completes without finding the target, it returns `-1`

, indicating that the target is not present in the list.

In the following sections, we'll delve deeper into understanding the performance of Binary Search and how it compares to other search algorithms.

The Binary Search algorithm demonstrates a significant improvement in time complexity over Linear Search. Each comparison cuts down our search space in half. Therefore, in a list of

elements, in the worst-case scenario, we would need to make **n**

comparisons to either find our target or confirm it's not in the list.**log2(n)**

Given this logarithmic relationship between the size of the list and the number of steps, we say that Binary Search has a time complexity of

. Remember, "log" here is base 2, but in Big O notation, we typically ignore this detail, as we're most interested in the rate of growth, not the specific calculations.**O(log n)**

In terms of space complexity, Binary Search shines again: it only needs a constant amount of extra space to store the

, **left**

, and **right**

variables, regardless of the size of the list. So the space complexity is **mid**

, which means it uses a constant amount of space.**O(1)**

The improved time and space complexities are what make Binary Search a preferred choice for searching in sorted lists, especially when the size of the list is large. However, the requirement for the list to be sorted is a trade-off that needs to be considered.

In summary, understanding the complexities of an algorithm like Binary Search allows us to make informed decisions about which algorithms are best suited to solve specific problems, especially when dealing with constraints around time and space.

## 5.2: Binary Search

Now, let's illustrate this with an example. Suppose we have a sorted list of numbers:

`numbers = [2, 5, 8, 12, 16, 23, 38, 56, 72, 91]`

and we want to find the number `23`

.

- First, we look at the middle of the list, which is
`16`

. Since`23 > 16`

, we know`23`

must be in the right half of the list. - Next, we look at the middle of the right half, which is
`38`

. Since`23 < 38`

, we know`23`

must be in the left half of this sublist (`[23, 38]`

). - We then look at the middle of
`[23, 38]`

, which is`23`

. Since`23 == 23`

, we've found our target, and the search is over.

Now, let's see how we can implement this in Python:

`def binary_search(arr, target):`

left = 0

right = len(arr) - 1

while left <= right:

mid = (left + right) // 2

if arr[mid] == target:

return mid # Element found, return its index

elif arr[mid] < target:

left = mid + 1

else:

right = mid - 1

return -1 # Element not found

# Test our function

numbers = [2, 5, 8, 12, 16, 23, 38, 56, 72, 91]

print(binary_search(numbers, 23)) # Outputs: 5

`binary_search`

that takes a sorted list `arr`

and a `target`

value as inputs. It starts by setting `left`

and `right`

pointers to the start and end of the list, respectively. It then enters a loop that continues until `left`

and `right`

meet.

`mid`

, the index of the middle element. If this middle element is equal to `target`

, it returns the index. If `target`

is greater, it moves the `left`

pointer to `mid + 1`

. If `target`

is less, it moves the `right`

pointer to `mid - 1`

. If the loop completes without finding the target, it returns `-1`

, indicating that the target is not present in the list.

elements, in the worst-case scenario, we would need to make **n**

comparisons to either find our target or confirm it's not in the list.**log2(n)**

. Remember, "log" here is base 2, but in Big O notation, we typically ignore this detail, as we're most interested in the rate of growth, not the specific calculations.**O(log n)**

, **left**

, and **right**

variables, regardless of the size of the list. So the space complexity is **mid**

, which means it uses a constant amount of space.**O(1)**