Data Structures & Application Development

Chapter 10
Searching, Sorting, and Algorithm Selection

Certificate in Java-Python-Programming Languages
Data Structures & Application Development   •   Certificate

Searching, Sorting, and Algorithm Selection

Introduction

Searching and sorting are among the most frequently performed operations in software. A customer database may need to locate an account, a learning platform may rank results by score, and a logistics system may arrange deliveries by time or priority. Although these tasks appear simple, the choice of algorithm strongly affects execution time, memory use, scalability, and the reliability of an application.

Searching determines whether a required item exists and, if so, where it is located. Sorting rearranges items according to a specified ordering rule. These operations are closely related: sorted data can often be searched more efficiently, while searching is frequently used inside larger algorithms.

This chapter introduces linear search, binary search, bubble sort, selection sort, insertion sort, merge sort, and quicksort. It also explains complexity, stability, in-place processing, data-order assumptions, and practical algorithm selection in Java and Python.

Learning Objectives

By the end of this chapter, you should be able to:

  • explain the purpose and operation of common searching and sorting algorithms;
  • implement these algorithms in Java or Python;
  • compare time and space complexity using Big-O notation;
  • distinguish stable, unstable, in-place, and adaptive algorithms;
  • identify the assumptions required by binary search and efficient sorting methods;
  • select an algorithm according to data size, ordering, memory, stability, and performance requirements;
  • test implementations and recognize common correctness and efficiency errors.

1. Essential Terminology

An element is one item in a collection. A key is the value used for comparison, such as an employee ID or product price. A comparison determines whether one key is less than, equal to, or greater than another. A collection is ordered when its elements follow a defined sequence, such as ascending numeric order.

A sorting algorithm is stable if equal-key elements retain their original relative order. Stability matters when records contain several fields. For example, if employees are first sorted by department and then stably sorted by salary, employees with equal salaries remain ordered by department.

An algorithm is in-place when it uses only a small amount of additional memory beyond the input collection. An adaptive algorithm benefits from data that is already partly ordered. An algorithm is comparison-based when its decisions depend on comparing keys rather than exploiting special properties of their representation.

The best case describes the most favorable input, the worst case the least favorable input, and the average case the expected behavior under stated assumptions. Complexity analysis normally focuses on how resource use grows as input size grows.

2. Measuring Algorithm Performance

If a collection contains n elements, the running time of an algorithm is commonly described by asymptotic notation. Big-O notation gives an upper-growth classification and ignores constant factors and lower-order terms. For example, an algorithm requiring 3n + 5 operations is O(n).

Space complexity describes additional memory requirements. The input collection itself is usually not counted when discussing auxiliary space, although conventions should be stated clearly.

ComplexityTypical interpretationExample
O(1)Constant growthAccessing an array element by index
O(log n)Very slow growthBinary search
O(n)Linear growthLinear search
O(n log n)Efficient comparison sortingMerge sort, average quicksort
O(n²)Quadratic growthBubble, selection, insertion sort worst case

Complexity does not predict exact elapsed time. A simple O(n²) algorithm may be adequate for 20 items, while an O(n log n) algorithm may be preferable for millions of items. Hardware, programming language, cache behavior, data distribution, and implementation quality also matter.

3. Searching Algorithms

3.1 Linear Search

Linear search examines elements one at a time from the beginning until it finds the target or reaches the end. It requires no ordering and works with arrays, lists, and streams of data.

def linear_search(items, target):
    for index, value in enumerate(items):
        if value == target:
            return index
    return -1

The best case is O(1) when the first element matches. The worst and average cases are O(n), because many or all elements may need to be examined. Its auxiliary space is O(1). Linear search is appropriate when a collection is small, unsorted, searched infrequently, or stored in a structure without efficient indexing.

3.2 Binary Search

Binary search operates on a collection sorted according to the same comparison rule used by the search. It compares the target with the middle element. If the target is smaller, the right half is discarded; if larger, the left half is discarded. The process repeats until the target is found or the search interval is empty.

def binary_search(items, target):
    low, high = 0, len(items) - 1
    while low <= high:
        middle = low + (high - low) // 2
        if items[middle] == target:
            return middle
        if items[middle] < target:
            low = middle + 1
        else:
            high = middle - 1
    return -1

The interval is approximately halved after each comparison. After k iterations, at most n/2k candidates remain. To reduce the candidates to one, 2k ≥ n, so k ≥ log2 n. Therefore, binary search has worst-case time complexity O(log n), best-case complexity O(1), and iterative auxiliary space O(1).

Binary search has strict conditions:

  1. the data must be sorted;
  2. the ordering must be consistent with the search comparisons;
  3. the structure must permit efficient access to the middle element.

The third condition explains why binary search is ideal for arrays but less useful on a linked list, where reaching the middle may itself require linear traversal. If duplicate values exist, ordinary binary search may return any matching position. A modified version can find the first or last occurrence.

3.3 Choosing Between Linear and Binary Search

Suppose a list contains 1,000,000 records. A single search on an unsorted list may be cheaper overall than sorting the list first. However, if thousands of searches will be performed, the one-time sorting cost may be justified by the much lower cost of repeated binary searches. A database index, hash table, or tree may be even more appropriate when updates and searches are frequent.

The key decision is therefore not simply “Which search is faster?” but “What is the total cost of preparing, storing, updating, and searching the data?”

4. Sorting Algorithms

4.1 Bubble Sort

Bubble sort repeatedly compares adjacent elements and exchanges them when they are in the wrong order. After each pass, a largest unsorted element moves toward the end.

def bubble_sort(items):
    n = len(items)
    for end in range(n - 1, 0, -1):
        swapped = False
        for i in range(end):
            if items[i] > items[i + 1]:
                items[i], items[i + 1] = items[i + 1], items[i]
                swapped = True
        if not swapped:
            break

With the early-termination test, the best case is O(n) when the list is already sorted. Average and worst cases are O(n²). Bubble sort is stable and in-place. Its simplicity makes it useful for teaching and very small collections, but it is normally unsuitable for large data.

4.2 Selection Sort

Selection sort divides the collection into sorted and unsorted regions. It finds the smallest remaining element and swaps it with the first element in the unsorted region.

Selection sort performs approximately the same number of comparisons regardless of input order. Its best, average, and worst-case time complexities are O(n²), and its auxiliary space is O(1). The basic exchange-based implementation is generally not stable because a swap can move an equal-key record past another equal-key record. It is useful when memory is extremely limited and the number of writes should be small.

4.3 Insertion Sort

Insertion sort builds a sorted prefix one element at a time. It removes the next element and shifts larger elements to the right until the correct position is available.

def insertion_sort(items):
    for i in range(1, len(items)):
        current = items[i]
        position = i - 1
        while position >= 0 and items[position] > current:
            items[position + 1] = items[position]
            position -= 1
        items[position + 1] = current

Insertion sort is stable, in-place, and adaptive. Its best case is O(n) for an already sorted collection. Average and worst cases are O(n²). It is often effective for small arrays or data that is nearly sorted. Many sophisticated sorting implementations use insertion sort for small subarrays because its constants are low.

4.4 Merge Sort

Merge sort uses divide and conquer. It divides the collection into two halves, recursively sorts each half, and merges the sorted halves. During merging, the smallest front element of the two temporary sequences is repeatedly copied into the result.

def merge_sort(items):
    if len(items) <= 1:
        return items
    middle = len(items) // 2
    left = merge_sort(items[:middle])
    right = merge_sort(items[middle:])
    result = []
    i = j = 0
    while i < len(left) and j < len(right):
        if left[i] <= right[j]:
            result.append(left[i])
            i += 1
        else:
            result.append(right[j])
            j += 1
    return result + left[i:] + right[j:]

At each level, merging processes n elements. The number of levels is approximately log2 n, so the total time is O(n log n) in the best, average, and worst cases. The shown list-slicing implementation uses additional memory; a conventional array implementation generally requires O(n) auxiliary space. Merge sort is stable when the merge operation chooses the left element first when keys are equal. It is valuable when predictable performance and stability are more important than minimal memory use.

4.5 Quicksort

Quicksort also uses divide and conquer. It chooses a pivot, partitions the remaining elements into values less than and greater than the pivot, and recursively sorts the partitions.

A balanced partition produces subproblems of roughly half the original size and leads to average time complexity O(n log n). If the pivot repeatedly produces an empty and an almost-full partition, the recursion depth becomes approximately n, resulting in worst-case time O(n²). Randomized pivots or careful pivot strategies reduce the likelihood of consistently poor partitions but do not change the theoretical worst-case bound.

Quicksort is usually in-place or nearly in-place, depending on the partition method, but the basic version is not stable. Recursion requires stack space; average auxiliary stack space is often O(log n), while poor partitions can require O(n). Quicksort can be very fast in practice because of good locality and low constant factors.

4.6 Counting and Non-Comparison Methods

When keys are integers in a limited known range, counting sort can achieve time complexity O(n + k), where n is the number of items and k is the range of possible key values. It is not a general comparison sort and may use excessive memory when k is large. It can be stable if implemented with cumulative counts and ordered placement.

The lesson is that an algorithm may exploit domain knowledge. Sorting examination grades from 0 to 100 is different from sorting arbitrary strings or large identifiers.

5. Stability, Memory, and Correctness

Algorithm selection requires more than comparing Big-O values.

AlgorithmBest timeAverage timeWorst timeStable?Typical extra space
Linear searchO(1)O(n)O(n)Not applicableO(1)
Binary searchO(1)O(log n)O(log n)Not applicableO(1) iterative
Bubble sortO(n)O(n²)O(n²)YesO(1)
Selection sortO(n²)O(n²)O(n²)Usually noO(1)
Insertion sortO(n)O(n²)O(n²)YesO(1)
Merge sortO(n log n)O(n log n)O(n log n)Yes, if implemented appropriatelyO(n) typical
QuicksortO(n log n)O(n log n)O(n²)Usually noO(log n) average stack

Correctness testing should include empty collections, one element, duplicate keys, already sorted data, reverse-sorted data, negative values, and very large inputs. A useful invariant for insertion sort is that before each iteration, the prefix before the current position is sorted. Such invariants help explain why an algorithm works and identify implementation errors.

In Java, library sorting is normally preferred for production code. Arrays.sort and Collections.sort or list methods are extensively tested and optimized. Python’s list.sort() and sorted() use Timsort, a stable algorithm designed to exploit naturally ordered runs. Reimplementing an algorithm is valuable for learning, but production software should normally use a standard library unless a specific requirement justifies a custom implementation.

6. A Framework for Algorithm Selection

A disciplined selection process can be expressed as follows:

  1. Clarify the operation. Is the requirement one search, repeated lookup, complete ordering, top-k selection, or incremental maintenance?
  2. Characterize the data. Determine size, duplicate frequency, key type, existing order, update frequency, and storage structure.
  3. Identify constraints. Consider response-time targets, memory limits, stability, worst-case guarantees, and external-storage requirements.
  4. Estimate total cost. Include preprocessing, sorting, searching, updates, copying, and maintenance—not only the main loop.
  5. Choose a baseline. Use a simple correct method for small data, then optimize when measurements or scale require it.
  6. Test realistic workloads. Benchmark representative data, including favorable and unfavorable orderings.
  7. Verify correctness and maintainability. A theoretically fast but opaque or error-prone solution may be inappropriate.

Typical decisions include:

  • small, unsorted collection and one lookup: linear search;
  • sorted array and repeated lookup: binary search;
  • nearly sorted small collection: insertion sort;
  • stable ordering with predictable O(n log n) performance: merge sort or a stable library sort;
  • memory-sensitive general sorting: a carefully implemented quicksort or library algorithm;
  • bounded integer keys: counting sort, if its range is practical;
  • frequent dynamic lookup: consider a hash table or balanced search tree rather than repeatedly sorting.

7. Worked Example: Choosing a Search Strategy

A training application stores 50,000 learner IDs. The IDs arrive unsorted, and the application performs only five lookups before discarding the collection.

Sorting costs at least approximately O(n log n), while five linear searches cost at most roughly 5n comparisons. Since the number of searches is small and the data is discarded, sorting is unlikely to repay its preparation cost. Linear search is the appropriate baseline.

Now assume the same collection is retained and queried 100,000 times. Sorting once and using binary search gives an approximate overall growth of:

O(n log n + q log n)

where n is the number of records and q is the number of searches. Repeated linear search gives:

O(qn)

For large q, the second expression grows much faster. A hash table may provide expected constant-time lookup, but it does not naturally provide sorted order and requires suitable memory and key hashing. Thus, binary search is a strong choice when ordered traversal is also needed; a hash table is strong when exact-key lookup is the dominant requirement.

8. Applied Case: Product Catalogue Search

This is an illustrative case.

Background

A small online retailer has 80,000 products. Each product has an ID, name, category, price, and stock status. Customers search by product ID and browse products ordered by price. Product records are updated throughout the day, while catalogue browsing is frequent.

Problem

The original prototype scans every product for an ID lookup and sorts the entire list whenever a customer requests price order. Response time becomes inconsistent during busy periods.

Analysis

A linear scan has O(n) worst-case lookup time. Repeatedly sorting the full collection adds unnecessary work. The application has two different access requirements: exact-key lookup and ordered presentation. One data structure should not be forced to serve both purposes.

Approach and Solution

The design maintains a hash map from product ID to product record for expected constant-time exact lookup. A separate list or database query supports price ordering. If an in-memory list must be sorted, a stable library sort is used with price as the key. Stability ensures that products with equal prices retain a meaningful prior order, such as product name or insertion order. Sorting is performed only when the catalogue changes significantly, or a maintained index is used.

Input validation prevents missing or malformed IDs. Authorization controls which staff members may change prices or stock. Logging records updates without exposing unnecessary customer information.

Outcome

ID searches no longer depend linearly on catalogue size, and price ordering is separated from exact lookup. The design uses more memory and requires synchronization between representations, but it provides predictable behavior and clearer responsibilities.

Learning Points

  • Different operations may justify different indexes or structures.
  • Sorting cost should not be paid repeatedly when data has not changed.
  • Stability can preserve meaningful order among equal keys.
  • Performance improvements must include consistency, security, and maintenance costs.

9. Limitations, Risks, and Professional Practice

Big-O analysis hides constants and does not capture every hardware effect. Two algorithms with the same complexity can have different practical performance. Worst-case analysis is especially important in systems with strict response-time requirements, where an occasional quadratic quicksort partition may be unacceptable.

Recursive algorithms can cause stack overflow on hostile or unusually large inputs. Poorly chosen comparators can violate transitivity—for example, claiming that A is less than B, B less than C, but C less than A. Such comparators can produce incorrect or unpredictable sorting behavior.

Data quality also matters. Case-sensitive and case-insensitive string ordering produce different results. Numeric values stored as strings may sort lexicographically, so “100” can appear before “20”. Locale, accents, null values, and time zones must be handled explicitly.

In security-sensitive systems, attackers may deliberately provide data that triggers worst-case behavior. Randomized pivots, input limits, balanced trees, or trusted library implementations can reduce this risk. Timing differences in searches should also be considered when comparing secrets; ordinary search algorithms are not automatically suitable for cryptographic data.

AI-assisted programming tools can generate algorithm implementations, but generated code must be reviewed, tested, benchmarked, and checked for licensing or data-privacy implications. The programmer remains responsible for correctness and safe use.

10. Best Practices

  • State whether input data is sorted and which ordering rule applies.
  • Use library implementations in production unless a custom algorithm is justified.
  • Prefer iterative binary search when simple constant auxiliary space is desirable.
  • Use stable sorting when records have multiple meaningful fields.
  • Test duplicates, empty input, reverse order, and already ordered input.
  • Benchmark with realistic data rather than relying only on asymptotic labels.
  • Separate correctness tests from performance tests.
  • Document assumptions, especially key uniqueness and null handling.
  • Consider indexes, hash tables, trees, and database operations when searching is frequent.
  • Treat memory, worst-case latency, maintainability, and security as part of algorithm selection.

Chapter Summary

Searching locates items, while sorting arranges them according to a comparison rule. Linear search requires no ordering and has O(n) worst-case time. Binary search requires sorted, efficiently indexable data and has O(log n) worst-case time. Bubble sort and selection sort are simple but generally quadratic. Insertion sort is stable, in-place, and effective for small or nearly sorted data. Merge sort gives stable, predictable O(n log n) performance but generally uses additional memory. Quicksort is often fast and memory-efficient, but its worst case is quadratic and it is usually unstable.

Good algorithm selection considers data size, existing order, repeated operations, stability, memory, worst-case guarantees, data structure, and maintainability. Production solutions should use tested library facilities where appropriate, validate assumptions, and benchmark realistic workloads. The best solution is not always the algorithm with the lowest theoretical complexity; it is the one that satisfies the complete operational requirements.

Key Terms

Big-O notation: A description of how resource use grows as input size increases.

Binary search: A search that repeatedly halves a sorted search interval.

Comparison: An operation that determines the ordering relationship between two keys.

Counting sort: A sorting method that uses frequencies of keys in a bounded range.

Element: An individual item in a collection.

In-place: Performed using only a small amount of additional memory.

Insertion sort: A sort that inserts each new item into an already sorted prefix.

Key: The field or value used to compare records.

Linear search: A sequential search through collection elements.

Merge sort: A divide-and-conquer sort that recursively sorts and merges partitions.

Ordering rule: The definition of how keys are ranked.

Partition: The division of data around a pivot or other boundary.

Quicksort: A divide-and-conquer sort based on pivot partitioning.

Stable sort: A sort that preserves the relative order of equal-key records.

Time complexity: The growth of execution work as input size increases.

Worst case: The maximum resource use for inputs of a specified size.

Adaptive algorithm: An algorithm that benefits from favorable existing data order.

Auxiliary space: Extra memory used in addition to the input collection.

Review Questions

Short Answer Questions

  1. What condition must be satisfied before binary search can be correctly applied?
  2. Define a stable sorting algorithm.
  3. Why is insertion sort effective for nearly sorted data?
  4. State the average time complexity of merge sort.
  5. Give one reason why Big-O notation does not predict exact running time.

Long Answer Questions

  1. Explain linear search and binary search, including their assumptions, complexity, and suitable applications.
  2. Compare bubble sort, selection sort, and insertion sort in terms of operation, complexity, stability, and practical use.
  3. Explain how merge sort and quicksort apply divide-and-conquer reasoning.
  4. Discuss why stability and auxiliary memory can affect the choice of a sorting algorithm.
  5. Describe a systematic framework for selecting a searching or sorting algorithm in a software project.

Analytical / Application Questions

  1. A program sorts 25 records that are already almost ordered and must preserve the order of equal-priority records. Which algorithm or library property would you choose, and why?
  2. A service performs millions of exact-ID lookups on a collection that changes occasionally but must also support alphabetical reports. Compare a hash table plus a separate ordered view with binary search on one sorted array.
  3. A quicksort implementation repeatedly selects the first element as pivot. Explain the input pattern that can cause poor performance and recommend two ways to reduce the risk.

Multiple Choice Questions with Answers

Question 1

Which condition is required for binary search?

A. The collection must contain unique values

B. The collection must be sorted according to the search rule

C. The collection must be stored in a linked list

D. The collection must contain only integers

Correct Answer: B. The collection must be sorted according to the search rule

Question 2

What is the worst-case time complexity of linear search on n elements?

A. O(1)

B. O(log n)

C. O(n)

D. O(n log n)

Correct Answer: C. O(n)

Question 3

Which sorting algorithm is stable and particularly suitable for nearly sorted data?

A. Insertion sort

B. Selection sort

C. Unmodified quicksort

D. Counting sort in every situation

Correct Answer: A. Insertion sort

Question 4

What is the typical worst-case time complexity of merge sort?

A. O(1)

B. O(log n)

C. O(n)

D. O(n log n)

Correct Answer: D. O(n log n)

Question 5

Why might a basic selection sort be unsuitable when stability is required?

A. It cannot compare keys

B. Its swaps can change the order of equal-key records

C. It always requires O(n) extra memory

D. It requires sorted input

Correct Answer: B. Its swaps can change the order of equal-key records

Question 6

A collection is searched five times and then discarded. It is unsorted and contains 50,000 items. Which approach is generally the most reasonable baseline?

A. Sort it and use binary search

B. Use linear search for each lookup

C. Use merge sort before every lookup

D. Use binary search without sorting

Correct Answer: B. Use linear search for each lookup

Question 7

What is a common worst-case cause of O(n²) quicksort behavior?

A. A balanced partition at every step

B. A pivot that repeatedly creates highly unbalanced partitions

C. Using an array rather than a list

D. Comparing duplicate values

Correct Answer: B. A pivot that repeatedly creates highly unbalanced partitions

Question 8

Which algorithm generally provides predictable O(n log n) time in best, average, and worst cases?

A. Bubble sort

B. Insertion sort

C. Merge sort

D. Basic selection sort

Correct Answer: C. Merge sort

Question 9

What does stable sorting preserve?

A. The original capacity of the collection

B. The relative order of records with equal keys

C. The memory address of every record

D. The number of comparisons in every implementation

Correct Answer: B. The relative order of records with equal keys

Question 10

For integer keys in a small known range, which method may run in O(n + k), where k is the key range?

A. Counting sort

B. Binary search

C. Selection sort

D. Quicksort in every case

Correct Answer: A. Counting sort

Data Structures & Application Development
Chapter 10 of 15