Searching Algorithms and Big O Notation
Chapter 5: Searching Algorithms and Big O Notation
Course Context and Programming Philosophy
Programming 1 vs. Programming 2/3: Programming 1 focuses on fundamental concepts, while Programming 2 and, to a greater extent, Programming 3, delve into algorithms and more intricate programming concepts. Much of it becomes a "show and tell" of Python's capabilities.
Class Progress: The class is at its midpoint, with students performing well.
1. Linear Search (Sequential Search)
Definition: An algorithm that iterates over all elements in an array (or list) to check if the current element matches a target element.
Process:
Starts at the beginning of the list (element at index ).
Compares each element sequentially with the target element.
If a match is found, it returns the index of the current element.
If no element equals the target element after checking the entire list, it returns (indicating the element was not found).
Analogy: Counting through a list from start to finish to find a specific value.
Example: Searching for
30in[10, 50, 30, 70, 80, 20, 90, 40].Compares
30with10(no).Compares
30with50(no).Compares
30with30(yes) - stops and returns the index.
**Python Code Example (
linear_search.py):A
searchfunction takes anarray, itslength(), and thetarget element().It uses a
forloopfor i in range(0, n)to iterate.if arr[i] == x: returnsi(the index).If the loop completes without finding
x, it returns .Driver Code: Creates an
array(e.g.,[2, 3, 4, 10, 40]), definesx(e.g.,10), calculatesn = len(array), calls thesearchfunction, and prints the result, indicating if the element was found and at what index.
2. Big O Notation
Purpose: Used to determine the complexity of algorithms, particularly how their runtime or space requirements grow as the input size increases.
Basic Examples:
The algorithm takes a constant amount of time, regardless of the input size. Example: Accessing an element directly, or a single line of code execution. Best-case scenario for searching at the first element.
The algorithm's runtime grows linearly with the input size (). If there are elements, it might perform operations. Example: A single loop that iterates through all elements. is a placeholder for an unknown number of iterations that depends on the input size.
The algorithm's runtime grows proportionally to the square of the input size. Example: A nested loop (a loop inside another loop), commonly seen in matrix operations.
Best Algorithms: Algorithms with as small as possible (e.g., or ) are considered the most efficient, though they might be more complex and potentially use more memory.
Big O Complexity of Linear Search
Best Case: - when the target element is the first element in the list.
Worst Case: - when the target element is the last element or not present in the list, requiring a scan of all elements.
Average Case: Approximately - on average, it will need to check about half the elements, which is still considered linear for Big O purposes.
Applications, Advantages, Disadvantages, When to Use It: (Details left for personal study from external resources mentioned in the transcript).
3. Binary Search
Definition: A search algorithm used to find the position of a target value within a sorted array (or list).
Prerequisite: The data structure must be sorted. Accessing any element must take constant time ().
Process (Iterative Logic):
Find the Middle: Compare the target element with the middle value of the current search interval.
Halve the Interval:
If the middle value equals the target, the search is complete.
If the middle value is less than the target, discard the left half of the interval (including the middle element) and continue searching in the right half.
If the middle value is greater than the target, discard the right half of the interval (including the middle element) and continue searching in the left half.
Repeat: Continuously divide the search interval in half until the target is found or the interval becomes empty.
Example: Finding
7in[1, 2, 3, 4, 5, 6, 7, 8, 9].Middle is
5.7 > 5, so discard[1, 2, 3, 4, 5]. New interval:[6, 7, 8, 9].Middle of
[6, 7, 8, 9]is7(or8depending on rounding, for simplicity let's say7). If7is chosen,7 = 7, so target found.
Versions: Iterative and Recursive.
3.1 Iterative Binary Search (Python Code Example)
Function Signature:
binary_search(arr, low, high, x)arr: The sorted array.low: The starting index of the search interval (initially ).high: The ending index of the search interval (initiallylen(arr) - 1).x: The target element.
Algorithm:
while low <= high:loop continues as long as the search interval is valid.mid = low + (high - low) // 2: Calculates the middle index. Using integer division//to ensure an integer index. (The formula(low + high) // 2also works, butlow + (high - low) // 2is safer to prevent integer overflow with very largelowandhighvalues in some languages).if arr[mid] == x:: If target found,return mid.elif arr[mid] < x:: If target is in the right half, updatelow = mid + 1.else:(meaningarr[mid] > x): If target is in the left half, updatehigh = mid - 1.return -1: If the loop finishes (meaninglow > high), the element was not found.
Performance: For small datasets, its speed difference with linear search might not be visually apparent, but for large datasets (millions of elements), binary search is significantly faster.
3.2 Recursive Binary Search (Python Code Example)
Function Signature:
binary_search_recursive(arr, low, high, x)(similar parameters).Algorithm:
Base Case:
if high >= low:(if the interval is valid, proceed).Calculate
midsimilar to the iterative version.if arr[mid] == x:: Returnsmid.elif arr[mid] > x:: Recursively callsbinary_search_recursive(arr, low, mid - 1, x)(search left half).else:(meaningarr[mid] < x): Recursively callsbinary_search_recursive(arr, mid + 1, high, x)(search right half).else:(ifhigh < low, i.e., base case is False): Returns . The function calls then