Data Structures and Algorithms

Chapter 7
Searching, Sorting and Selection

Bachelors Program in AI & ML
Data Structures and Algorithms   •   Bachelor

Searching, Sorting and Selection

Introduction

Searching, sorting, and selection are among the most frequently used operations in computing. A database query searches for records, a compiler sorts and organizes symbols, a recommendation system ranks alternatives, and an operating system selects tasks according to priority. Although these operations appear simple, their efficiency often determines whether a system remains responsive as data volume grows.

This chapter develops the principles behind major searching, sorting, and selection algorithms. It compares their time and space requirements, explains stability and in-place processing, introduces comparison lower bounds and non-comparison sorting, and shows how algorithm choice depends on data characteristics. The emphasis is not merely on memorizing algorithms but on reasoning about when each algorithm is appropriate.

Learning Objectives

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

1. Explain linear and binary search and identify their applicability conditions.

2. Analyze the time and space complexity of common searching and sorting algorithms.

3. Distinguish stable, unstable, in-place, and out-of-place algorithms.

4. Apply insertion sort, merge sort, quicksort, heapsort, counting sort, radix sort, and bucket sort.

5. Explain order statistics and use selection algorithms to find ranks efficiently.

6. State and interpret the comparison-sorting lower bound.

7. Select algorithms according to input size, order, key type, memory, and stability requirements.

1. Searching Fundamentals

Searching is the process of determining whether a target value occurs in a collection and, commonly, returning its location or associated record. A search problem usually contains:

  • a collection of elements;
  • a search key or target;
  • a comparison or matching rule; and
  • a required result, such as an index, record, or indication of failure.

The appropriate search algorithm depends strongly on whether the data are ordered and whether additional indexing structures are available.

1.1 Linear Search

Linear search examines elements sequentially from one end of a collection until the target is found or all elements have been examined. It requires no ordering assumption and works with arrays, linked lists, and streams.

For an array A containing n elements and target x, the algorithm compares x with A[0], A[1], and so on. In the worst case, n comparisons are required. If the target is equally likely to occur at any position and is guaranteed to occur, the average number of comparisons is (n + 1)/2. If unsuccessful searches are included and all n positions are examined, the unsuccessful case requires n comparisons.

The time complexity is O(n) in the worst and average cases and Ω(1) in the best case. Its auxiliary space complexity is O(1). Linear search is appropriate when the collection is small, unsorted, frequently changing, or stored in a structure that does not support efficient random access.

A useful improvement for a frequently searched, unsorted list is move-to-front ordering: after a successful search, the found item is moved toward the front. This can be effective when access frequencies are highly unequal, although it changes the order of the data.

1.2 Binary Search

Binary search applies to a sorted sequence that supports efficient access to its middle element, such as an array. At each step, the target is compared with the middle element. If the target is smaller, the left half is retained; if larger, the right half is retained. Thus, each comparison approximately halves the remaining search interval.

After k iterations, at most n/2^k elements remain. To reduce the interval to one element, k must satisfy 2^k approximately equal to n, so k is approximately log2 n. Therefore, binary search has O(log n) worst-case and average-case time complexity, and O(1) auxiliary space in an iterative implementation. A recursive implementation uses O(log n) call-stack space.

Binary search is not generally efficient on a linked list. Although the number of comparisons is logarithmic, locating the middle node may require linear traversal, producing O(n) total time. The data structure must therefore support fast random access, or an index must be maintained.

A robust implementation uses a lower-bound style midpoint calculation such as low + (high - low) // 2 rather than (low + high) // 2, avoiding integer overflow when index values are large. Variants include finding the first occurrence, last occurrence, insertion position, or smallest value satisfying a monotonic predicate.

1.3 Search Decision

Use linear search when data are unsorted, small, streaming, or stored in a linked structure. Use binary search when data are sorted and random access is available. If searches are numerous and updates are limited, a hash table, balanced search tree, or database index may provide better overall performance than repeatedly searching a raw array.

2. Sorting Concepts and Evaluation Criteria

Sorting rearranges records according to a key and an ordering relation, usually nondecreasing or nonincreasing order. Sorting is often a preprocessing step that enables binary search, duplicate detection, merging, reporting, and ranking.

Important evaluation criteria include:

  • Running time: How the number of operations grows with n.
  • Auxiliary space: Extra memory beyond the input representation.
  • Stability: Whether equal-key records retain their original relative order.
  • In-place processing: Whether the algorithm uses only constant or small additional working memory. Definitions vary; in this chapter, an algorithm is called in-place when it rearranges the input using O(1) auxiliary storage, excluding recursion stack unless stated otherwise.
  • Adaptiveness: Whether performance improves when input is already or nearly sorted.
  • Worst-case guarantees: Whether poor input arrangements can cause severe slowdown.
  • Key restrictions: Whether keys must be comparable, integral, bounded, or drawn from a known range.

Stability matters when records have multiple fields. Suppose employees are first sorted by department and then stably sorted by salary. Within equal-salary groups, the previous department order is preserved. An unstable second sort may destroy that information.

3. Comparison-Based Sorting

Comparison sorts determine order only by comparing pairs of keys. Examples include insertion sort, selection sort, bubble sort, merge sort, quicksort, and heapsort.

3.1 Insertion Sort

Insertion sort builds a sorted prefix. It takes the next element and shifts larger elements in the prefix one position to the right until the correct location is found.

Its worst-case and average-case running time is O(n^2), while its best-case time is O(n) when the input is already sorted. It uses O(1) auxiliary space and is stable when equal elements are not moved past one another. It is adaptive and performs particularly well for small or nearly sorted arrays. It is also useful as a base case inside more complex algorithms.

For example, sorting [5, 2, 4, 6] proceeds as follows:

  • Insert 2 before 5: [2, 5, 4, 6].
  • Insert 4 between 2 and 5: [2, 4, 5, 6].
  • Insert 6 at the end: [2, 4, 5, 6].

3.2 Selection Sort

Selection sort repeatedly finds the smallest element in the unsorted suffix and swaps it into the next position. It performs Θ(n^2) comparisons in every case, regardless of input order, and uses O(1) auxiliary space. The usual swapping version is not stable. Its principal advantage is a small number of writes, which can be valuable when writing is expensive, although it is rarely the best general-purpose sort.

3.3 Bubble Sort

Bubble sort repeatedly exchanges adjacent elements that are out of order. With a flag indicating whether a swap occurred, it can terminate after one pass on an already sorted input. Its best-case time is O(n), but its average and worst-case time are O(n^2). It is stable and in-place, but generally inferior to insertion sort because of high data movement and comparison overhead. It is mainly pedagogical or useful in specialized tiny cases.

3.4 Merge Sort

Merge sort divides the array into two halves, recursively sorts each half, and merges the sorted halves. The merge operation takes Θ(n) time. Its recurrence is:

T(n) = 2T(n/2) + Θ(n),

with T(1) = Θ(1). By the recurrence theorem, T(n) = Θ(n log n).

Merge sort has the same asymptotic running time in the best, average, and worst cases. The standard array implementation requires O(n) auxiliary memory for merging and is stable if equal elements are taken from the left half first. It is well suited to linked lists and external sorting because merging can be sequential and predictable. External sorting is essential when the data set does not fit into main memory: sorted runs are generated and then merged from storage.

3.5 Quicksort

Quicksort selects a pivot, partitions the elements into values less than, equal to, and greater than the pivot, and recursively sorts the subarrays. Its performance depends on partition balance.

If each partition divides the input approximately in half, the recurrence is T(n) = 2T(n/2) + Θ(n), giving Θ(n log n). If the pivot is repeatedly the smallest or largest element, the recurrence becomes T(n) = T(n - 1) + Θ(n), giving Θ(n^2). Randomized pivot selection makes consistently poor behavior unlikely, but does not remove the formal worst-case bound. Median-of-three or other sampling strategies can also improve practical balance.

Quicksort is often in-place and has good cache behavior. The usual partition schemes are not stable. Recursion requires stack space: expected O(log n) with balanced partitions, but potentially O(n) in the worst case. A practical implementation recurses first on the smaller partition and processes the larger one iteratively, limiting stack depth.

Three-way partitioning, which separates elements into less-than, equal-to, and greater-than regions, is particularly effective when many duplicate keys occur.

3.6 Heapsort

A binary heap supports efficient extraction of the maximum or minimum. Heapsort first builds a heap and then repeatedly places the root at its final position. Building a heap can be done in O(n) time using bottom-up heap construction, not O(n log n). The subsequent n removals require O(n log n) time, so the total is O(n log n) in all cases.

Heapsort uses O(1) auxiliary space in its array form and has a strong worst-case guarantee. It is generally unstable and may have poorer cache performance than quicksort. It is valuable when predictable O(n log n) time and small memory usage are more important than average-case speed.

3.7 Comparison Table

| Algorithm | Best | Average | Worst | Extra Space | Stable? |

|---|---:|---:|---:|---:|---|

| Insertion sort | O(n) | O(n²) | O(n²) | O(1) | Yes |

| Selection sort | O(n²) | O(n²) | O(n²) | O(1) | Usually no |

| Bubble sort | O(n) with early stop | O(n²) | O(n²) | O(1) | Yes |

| Merge sort | O(n log n) | O(n log n) | O(n log n) | O(n) for arrays | Yes |

| Quicksort | O(n log n) expected | O(n log n) expected | O(n²) | O(log n) expected | Usually no |

| Heapsort | O(n log n) | O(n log n) | O(n log n) | O(1) | No |

The table describes standard implementations; variations can change stability and space requirements.

4. Non-Comparison Sorting

Non-comparison methods exploit information about key representation. They can outperform the comparison lower bound when their assumptions hold.

4.1 Counting Sort

Counting sort applies to integer keys in a limited range, commonly 0 through k. It counts how many times each key appears and uses cumulative counts to determine final positions. Its running time is O(n + k), and its auxiliary space is O(n + k) for the standard stable version.

Counting sort is attractive when k is not much larger than n. If n = 1,000 and keys range from 0 to 10^9, allocating a billion counters is inappropriate. A stable implementation processes the input from right to left while placing records according to cumulative positions. Stability is important when counting sort is used as a digit-level procedure in radix sort.

4.2 Radix Sort

Radix sort processes keys digit by digit using a stable intermediate sort, often counting sort. In least-significant-digit-first radix sort, d digits are processed. If each digit has base b and counting sort is used, the running time is O(d(n + b)). For fixed-width machine integers, d and b may be treated as constants, yielding practical linear behavior in n.

Radix sort is not a comparison sort. It requires a consistent digit representation and a stable pass. It is useful for integers, fixed-length strings, identifiers, and some specialized record formats, but less suitable for arbitrary objects ordered by complex comparison rules.

4.3 Bucket Sort

Bucket sort distributes values into intervals, sorts each bucket, and concatenates the buckets. If values are approximately uniformly distributed over a known interval and the number of buckets is proportional to n, expected time can be O(n), assuming the bucket-level sorting work remains small. The worst case is O(n²) with simple insertion sorting if all values fall into one bucket. Its performance depends on a distribution assumption, so it should not be presented as universally linear.

5. Selection and Order Statistics

The kth order statistic of n elements is the element that would appear at position k in sorted order, using either one-based rank or zero-based index by explicit convention. The minimum and maximum are order statistics; the median is the middle order statistic. Quantiles, percentiles, and top-k queries are practical examples.

Sorting the entire array and returning the kth element costs O(n log n) using a comparison sort. This may be unnecessary because selection requires only locating one rank, not ordering every element.

5.1 Quickselect

Quickselect uses the partition operation of quicksort but recursively continues only in the partition containing the desired rank. With a reasonably balanced or randomized pivot, its expected running time is O(n), because the work at successive levels is approximately n + n/2 + n/4 + ... = O(n). Its worst-case time is O(n²), and standard implementations are in-place apart from recursion space.

For example, to find the third smallest element, partition around a pivot. If the pivot finishes at rank 5, only the lower partition is relevant for rank 3. The upper partition can be discarded. The algorithm does not fully sort either partition.

5.2 Median-of-Medians

The deterministic linear-time selection algorithm, often called median-of-medians, groups elements into small groups, finds group medians, recursively finds their median, and uses it as a pivot. A constant fraction of elements is discarded at each stage, producing O(n) worst-case time. Although theoretically powerful, it has greater constant factors than randomized quickselect and is less common in ordinary application code.

5.3 Top-k Selection

If only the k largest items are required, alternatives include:

  • a min-heap of size k, taking O(n log k) time and O(k) space;
  • quickselect, with expected O(n) time followed by sorting the selected k items if ordered output is required;
  • full sorting, useful when all ranks or a completely ordered result will later be needed.

The best method depends on whether k is small, whether streaming input is involved, and whether sorted output is required.

6. The Comparison-Sorting Lower Bound

A comparison sort can be represented by a decision tree. Each internal node represents a comparison, and each outcome follows a branch. For n distinct elements, there are n! possible input permutations, so a correct algorithm must have at least n! leaves. A binary decision tree of height h has at most 2^h leaves. Therefore:

2^h ≥ n!,

which implies h ≥ log2(n!). Using the standard bound log2(n!) = Ω(n log n), every comparison sort has worst-case running time Ω(n log n).

This result explains why merge sort, heapsort, and expected quicksort achieve asymptotically optimal comparison-based performance. It does not apply to counting sort, radix sort, or bucket sort because these methods inspect key representations and use operations beyond pairwise comparisons. The lower bound also does not say that every practical sort needs n log n time: small inputs, special distributions, bounded keys, and presorted data can change the appropriate choice.

7. Worked Algorithm-Selection Examples

Example 1: Nearly Sorted Log Records

A system receives 2,000 records that are usually in timestamp order, with a few late arrivals. Insertion sort is a reasonable choice because it is stable, in-place, and adaptive. Its worst-case bound is quadratic, but the actual number of shifts is related to the number of inversions. If the records are almost sorted, this can be substantially less than n².

Example 2: Large Data Set with a Stability Requirement

A reporting system must sort millions of records by salary while preserving an earlier department ordering among equal salaries. A stable merge sort is appropriate. If memory is limited, an external merge sort can create sorted disk runs and merge them sequentially. Quicksort may be faster in some in-memory settings but does not automatically preserve equal-key order.

Example 3: Bounded Integer Keys

A classification system sorts 500,000 labels represented by integers from 0 to 255. Counting sort has time O(n + 256), which is effectively O(n), and modest auxiliary memory. A comparison sort would provide O(n log n) asymptotic time and would ignore useful information about the key range.

8. Applied Case: Selecting and Ranking Products in an Online Marketplace

This is an illustrative case.

Background

An online marketplace stores product records containing category, price, rating, seller identifier, and product identifier. Users may request all products in price order, the median price, or the top 20 products by rating.

Problem

A single algorithm cannot optimally answer every request. Full sorting for every query creates unnecessary work for median and top-k requests, while an unsorted scan is inconvenient for repeated range searches.

Analysis

For a one-time median request, randomized quickselect can provide expected O(n) selection. For the top 20 ratings in a stream of n products, a min-heap of size 20 requires O(n log 20) time and O(20) space. If users repeatedly request products by price within a category, maintaining sorted arrays, database indexes, or balanced trees may justify preprocessing and update costs.

If product results are displayed with equal ratings ordered by seller reputation, stability or an explicit secondary key is required. Relying on accidental ordering is unsafe; the desired tie-breaking rule should be specified directly.

Approach and Outcome

The marketplace uses an indexed structure for frequent price-range queries, a bounded min-heap for streaming top-20 results, and selection for occasional percentile analytics. Stable sorting is used when a prior ordering must be preserved. This combination reduces unnecessary full sorts and matches the algorithm to the query type.

Learning Points

1. Algorithm choice depends on workload, not only on the number of records.

2. Selection is preferable to full sorting when only one rank is needed.

3. A heap is effective for small top-k results over a large stream.

4. Stability and tie-breaking rules affect correctness as well as presentation.

5. Indexing can shift cost from repeated queries to preprocessing and updates.

9. Practical Implementation and Engineering Considerations

Correctness comes before micro-optimization. Implementations should define whether ordering is ascending or descending, how duplicate keys are handled, and what happens for empty input or invalid ranks.

For binary search, explicitly choose whether the interval is closed or half-open and test boundary cases such as an empty array, one element, repeated values, and a target outside the range. For sorting, test already sorted, reverse-sorted, duplicate-heavy, random, and very small inputs. For quicksort, randomized or carefully chosen pivots and three-way partitioning can reduce pathological behavior.

Library sorting routines are usually highly optimized and should be preferred in production unless a special key structure, memory restriction, educational purpose, or real-time guarantee justifies a custom algorithm. Benchmarking should use representative workloads; asymptotic complexity does not capture cache locality, branch prediction, allocation cost, parallelism, or input distribution.

In AI and machine-learning systems, sorting and selection occur in nearest-neighbor search, ranking metrics, beam search, sampling, feature preprocessing, and top-k prediction. Large-scale systems may use parallel sorting, approximate quantiles, external storage, or specialized hardware. Approximation can be appropriate for exploratory analytics, but its error and reproducibility should be documented.

10. Limitations, Governance, and Responsible Use

Search and ranking algorithms can amplify data-quality problems. Missing, duplicated, or malformed keys may produce incorrect ordering. In applications such as hiring, lending, or content recommendation, ranking criteria can affect people’s opportunities. The algorithm itself may be mathematically correct while the selected key or tie-breaking rule produces unfair outcomes.

Professional practice therefore requires documenting key definitions, missing-value treatment, tie handling, data provenance, and reproducibility. Sensitive applications should test performance across relevant groups and audit whether ranking decisions create unjustified disparities. Security also matters: adversarial inputs can be designed to degrade poor pivot strategies or exploit predictable ranking behavior. Deterministic tie-breaking, input validation, monitoring, and appropriate access controls reduce these risks.

Chapter Summary

Searching locates a target, while sorting establishes an order that supports later operations. Linear search is simple and works without ordering, but requires O(n) time. Binary search reduces search time to O(log n) when data are sorted and random access is available.

Insertion sort is stable, in-place, and effective for small or nearly sorted data. Merge sort provides stable Θ(n log n) performance but normally needs additional array space. Quicksort has excellent expected performance and locality but can degrade to quadratic time without suitable pivot handling. Heapsort provides O(n log n) worst-case time with O(1) auxiliary space but is unstable. Counting, radix, and bucket sorting can achieve near-linear performance by exploiting key structure and distribution assumptions.

Selection finds an order statistic without necessarily sorting all data. Quickselect has expected linear time, while median-of-medians provides deterministic linear time. The comparison-sorting lower bound, derived from decision trees, shows that comparison sorting requires Ω(n log n) worst-case time, but it does not restrict non-comparison methods.

Effective algorithm selection considers input size, ordering, duplicates, key representation, stability, memory, worst-case guarantees, updates, query frequency, and workload distribution. In professional systems, correctness, testing, transparency, and responsible use are as important as asymptotic efficiency.

Key Terms

Linear search: Sequentially examines elements until a target is found or the collection ends.

Binary search: Searches a sorted random-access sequence by repeatedly halving the search interval.

Sorting: Rearranging records according to a specified ordering key.

Stable sort: A sort that preserves the relative order of records with equal keys.

In-place algorithm: An algorithm that uses only constant or small auxiliary storage while processing the input.

Adaptive algorithm: An algorithm whose performance improves when input has useful existing order.

Insertion sort: A stable, in-place algorithm that inserts each item into a sorted prefix.

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

Quicksort: A partition-based sorting algorithm whose performance depends on pivot quality.

Heapsort: A heap-based comparison sort with O(n log n) worst-case time.

Counting sort: A non-comparison sort for keys from a manageable integer range.

Radix sort: A digit-based sort using stable passes over key representations.

Bucket sort: A distribution sort that places values into intervals or buckets.

Order statistic: The element occupying a specified rank in sorted order.

Quickselect: A partition-based algorithm for expected linear-time selection.

Top-k problem: Finding the k largest or smallest elements without necessarily sorting all elements.

Decision tree: A model representing the sequence of comparisons made by a comparison algorithm.

Comparison lower bound: The Ω(n log n) worst-case bound for comparison-based sorting.

External sorting: Sorting data that do not fit in main memory, commonly through sorted runs and merging.

Tie-breaking: A secondary rule used to order records with equal primary keys.

Review Questions

Short Answer Questions

1. What condition is required for binary search to be applicable to an array?

2. Define stability in the context of sorting.

3. Why can insertion sort be effective on nearly sorted data?

4. What is the expected running time of randomized quickselect?

5. Why does the comparison-sorting lower bound not apply to counting sort?

Long Answer Questions

1. Compare merge sort, quicksort, and heapsort in terms of time complexity, space, stability, and practical use.

2. Explain counting sort and radix sort, including their assumptions and limitations.

3. Derive the comparison-sorting lower bound using a decision-tree argument.

4. Explain how binary search works and why a linked list may not achieve logarithmic total running time.

5. Discuss the role of stability, in-place processing, adaptiveness, and worst-case guarantees in selecting a sorting algorithm.

Analytical / Application Questions

1. A system must process ten million integers whose values range from 0 to 100. Which sorting method would you recommend, and what practical issue should still be considered?

2. A live data stream requires the 50 highest scores at all times. Compare a size-50 min-heap with repeatedly sorting the entire stream.

3. A report first orders employees by department and then by salary while preserving department order for equal salaries. Which property must the second sorting operation have, and why?

Multiple Choice Questions with Answers

Question 1

What is the worst-case time complexity of binary search on a sorted array?

A. O(1)

B. O(log n)

C. O(n)

D. O(n log n)

Correct Answer: B. O(log n)

Question 2

Which sorting algorithm is stable and typically uses O(n) auxiliary space for arrays?

A. Merge sort

B. Heapsort

C. Selection sort

D. Standard in-place quicksort

Correct Answer: A. Merge sort

Question 3

What is the worst-case time complexity of standard quicksort with consistently poor pivots?

A. O(log n)

B. O(n)

C. O(n log n)

D. O(n²)

Correct Answer: D. O(n²)

Question 4

Which method is most appropriate when integer keys lie in a small known range?

A. Counting sort

B. Binary search

C. Heapsort

D. Linear search

Correct Answer: A. Counting sort

Question 5

What is the expected running time of randomized quickselect?

A. O(log n)

B. O(n)

C. O(n log n)

D. O(n²)

Correct Answer: B. O(n)

Question 6

Which property ensures that equal-key records retain their previous relative order?

A. Adaptiveness

B. Stability

C. Partitioning

D. Randomization

Correct Answer: B. Stability

Question 7

Why can standard binary search be inefficient on a linked list?

A. Linked lists cannot contain sorted values

B. Comparisons are impossible on linked-list nodes

C. Finding the middle position may require linear traversal

D. Linked lists require hashing before searching

Correct Answer: C. Finding the middle position may require linear traversal

Question 8

What is the worst-case time complexity of heapsort?

A. O(n)

B. O(log n)

C. O(n log n)

D. O(n²)

Correct Answer: C. O(n log n)

Question 9

Which data structure is suitable for maintaining the 20 largest values in a stream using O(20) extra space?

A. A min-heap of size 20

B. A maximum-size array containing every value

C. A binary-search decision tree of all values

D. A counting array for arbitrary real values

Correct Answer: A. A min-heap of size 20

Question 10

The comparison-sorting lower bound is based on the fact that n distinct inputs have how many possible permutations?

A. n

B. 2n

C. n²

D. n!

Correct Answer: D. n!

Data Structures and Algorithms
Chapter 7 of 14