Welcome back to a new lesson! We're diving into Binary Search, a clever technique for locating specific elements within a sorted list. We can find the targeted item by repeatedly dividing the search interval in half. It's akin to flipping through a dictionary — instead of going page by page, you'd start in the middle, then narrow down the section in half until you find your desired word.
Binary Search begins at the midpoint of a sorted list, halving the search area at each step until it locates the target. For example, if you're looking for the number 8
in a sorted list ranging from 1
to 10
, you would start at 5
. Since 8
is larger than the midpoint, you narrow the search to the second half of the list, leaving you with numbers 6
to 10
. In this new sublist, the middle number is 8
, and thus, you've found your target. This efficient approach significantly reduces the number of comparisons needed compared to a linear search.
Let's see how Binary Search can be implemented in Go, taking a recursive approach. This involves a function calling itself — with a base case in place to prevent infinite loops — and a recursive case to solve smaller parts of the problem.
Go1func BinarySearch(arr []int, start, end, target int) int { 2 if start > end { 3 return -1 // Base case 4 } 5 6 mid := start + (end-start)/2 // Find the midpoint 7 8 if arr[mid] == target { 9 return mid // Target found 10 } 11 12 if arr[mid] > target { 13 return BinarySearch(arr, start, mid-1, target) // Search the left half 14 } 15 return BinarySearch(arr, mid+1, end, target) // Search the right half 16}
In this Go code, the base case is defined first. If the start
index is greater than the end
index, it indicates the search area is exhausted, resulting in a -1
return. The code then locates the midpoint. If the midpoint equals our target, the index of the target element is returned. Depending on whether the target is less than or more than the midpoint, the search continues within the left or right half, respectively.
We can also visualize this search below:
Go1[ 1 2 3 4 5 6 7 8 9 ] <- we want to find 3 2| | <-the lines are the limits of our search 3[ 1 2 3 4 5 6 7 8 9 ] 4| ^ | <- mid point is on 4 5[ 1 2 3 4 5 6 7 8 9 ] <- 4 is larger than 3, ignore the left 6| | <- our search area is cut in half! 7[ 1 2 3 4 5 6 7 8 9 ] 8| ^ | <- mid point is now 2 9[ 1 2 3 4 5 6 7 8 9 ] <- 2 is smaller than 3, ignore the right 10 | | <- our search area is cut again! 11[ 1 2 3 4 5 6 7 8 9 ] 12 | ^ | <- midpoint is now 3, out target!
Let's analyze the time complexity of Binary Search, which measures how much time an algorithm takes as the input size increases. Notably, Binary Search halves the list at every step, necessitating steps for an array of size n
. Therefore, the time complexity of Binary Search is .
You can also implement the Binary Search algorithm iteratively using a loop. Here is the Go code for the iterative approach.
Go1func BinarySearchIterative(arr []int, target int) int { 2 start := 0 3 end := len(arr) - 1 4 5 for start <= end { 6 mid := start + (end-start)/2 // Calculate the midpoint to divide the search area 7 8 if arr[mid] == target { 9 return mid // Target found, return the index 10 } 11 12 if arr[mid] < target { 13 start = mid + 1 // Move start to right of mid to search the right half 14 } else { 15 end = mid - 1 // Move end to left of mid to search the left half 16 } 17 } 18 return -1 // Target not found, return -1 19}
Instead of dividing the array recursively, this code uses a for
loop that continues while the start
index is less than or equal to the end
index. The middle element is calculated similarly to the recursive approach. If this middle element equals the target, its index is returned. Otherwise, the search area is adjusted: if the target is greater, the start
index is set to one position after the middle; if the target is smaller, the end
index is set to one position before the middle.
Both the recursive and iterative versions of the Binary Search algorithm have a time complexity of , making them both very efficient.
In Go, recursion may consume more memory since each recursive call adds a frame to the call stack. This can lead to stack overflow errors if the recursion depth is too high. On the other hand, iterative solutions generally use less stack space as they do not add additional frames with each iteration.
Some developers find recursive code easier to understand and debug due to its simplicity and alignment with mathematical definitions. However, the iterative approach might be chosen for its stability in memory usage. The choice depends on the specifics of the problem, the performance characteristics of the system, and personal or team preferences. Both methods are valuable tools for developers.
Binary Search is a smart method for locating specific items within a sorted list. By repeatedly narrowing down the search area, it finds the target until the search area is reduced to zero. The key to mastering these concepts lies in practice. Starting with straightforward tasks, we will gradually navigate toward more complex problems that showcase the strength of Binary Search. Great job!