Searching, Sorting and Efficiency

Learn how computers find and order data, and how to tell a fast method from a slow one. Trace linear and binary search and three classic sorting algorithms, bubble sort, selection sort and merge sort, on real lists. Count the steps each one takes, see how those counts grow as the data grows, and predict running times. Then argue that an algorithm always gives the right answer, test it with edge cases, and choose the algorithm that fits a situation.

BeginnerLesson 4 of 7 in Introduction to Computer Science Course

What you will learn

  1. Find a target value in a list with linear search and, when the list is sorted, with binary search. Trace each method on real lists and count how many items it checks.
    • Search any list with linear search
      Check items one at a time from the start, return the position of the first match or report that the target is absent, and count the comparisons in the best and worst case.
    • Search a sorted list with binary search
      Use the sorted order to halve the part of the list still in play at every check, trace the low, high and middle positions, and explain why about log₂ n checks are enough.
  2. State what a correct sort must produce, then trace bubble sort and selection sort pass by pass and count their comparisons and swaps.
    • Describe what a correctly sorted list looks like
      Define sorted order, sort keys, swaps, stability, sorting in place and inversions, so that sorting algorithms can be traced, checked and compared.
    • Trace and count bubble sort
      Sort a list by repeatedly swapping neighbors that are out of order, follow it pass by pass, stop early when a pass makes no swaps, and count its comparisons and swaps.
    • Trace and count selection sort
      Sort a list by repeatedly selecting the smallest remaining item and swapping it into place, follow it pass by pass, and compare its comparison and swap counts with bubble sort.
  3. Merge two sorted lists into one, trace merge sort as it splits a list and merges the pieces back, and explain why it needs about n log₂ n comparisons.
    • Merge two sorted lists into one
      Combine two lists that are already sorted by repeatedly taking the smaller front item, count the comparisons, and explain why the result is sorted.
    • Trace merge sort and explain its n log n growth
      Sort by splitting a list in halves until single items remain and merging the pieces back in order, using a base case to stop the splitting, and count the comparisons level by level.
  4. Count an algorithm's basic steps as a function of the input size, compare logarithmic, linear, n log n and quadratic growth, and use growth rates to predict running times.
    • Count the steps an algorithm takes
      Measure an algorithm by how many basic operations it performs for an input of size n, find the count for loops and nested loops, and separate the best, worst and average case.
    • Compare how step counts grow with input size
      Recognize constant, logarithmic, linear, n log n, quadratic and exponential growth, see what happens when the input doubles, read the informal O labels, and explain why growth rate matters more than constant factors for large inputs.
    • Predict and measure running times
      Use a known running time and a growth rate to estimate the time for a larger input, check a growth rate by timing a program at doubling sizes, and state predictions as estimates.
  5. Show that an algorithm stops and gives the right answer for every valid input, test it with edge cases, and weigh time, memory and other trade-offs to choose an algorithm for a situation.
    • Argue that an algorithm is correct
      State what an algorithm must achieve, explain why testing alone cannot prove it correct, and use loop invariants and a termination argument to show that searching and sorting algorithms work.
    • Test algorithms with edge cases
      Choose inputs at the boundaries of what an algorithm handles, predict the expected output before running, and find the off-by-one errors and endless loops that ordinary inputs miss.
    • Choose an algorithm for the situation
      Compare searching and sorting algorithms on time, memory, stability and their requirements on the data, and decide which one suits a given task, including when sorting first pays off.

Searching, Sorting and Efficiency study guide

5 chapters

1

Search a list step by step

Finding one value starts with a simple question: which positions still need checking? Follow linear search through an ordinary list, then use sorted order to rule out large parts of a list with binary search. You will track positions separately from values, see how each search reports failure, and count the work needed for easy and difficult inputs. Small traces also show why the last remaining position must still be checked.

Searching asks whether a target occurs and where it occurs. A list has numbered positions called indexes. Python counts from zero: in [7, 3, 9, 3, 5], index 0 holds 7, and index 4 holds 5. The value and its index answer different questions.

Check one item at a time

Linear search, also called sequential search, starts at the first item and compares each item with the target. It returns immediately when it finds a match. If the list ends without a match, it returns -1, our signal for “not found.”

def linear_search(items, target):
    for i in range(len(items)):
        if items[i] == target:
            return i
    return -1

len(items) gives the number of items. range(len(items)) supplies every valid index, starting at 0 and ending just before the length. items[i] reads the value at the current index. A return ends the function, so a later match never replaces the first one.

Searching the list above for 3 proceeds as follows:

  1. At index 0, compare 7 with 3. They differ, so continue.
  2. At index 1, compare 3 with 3. They match, so return index 1.

The result is index 1 after two comparisons. The second 3, at index 3, is never checked. Searching for 4 instead checks 7, 3, 9, 3, and 5, then returns -1 after five comparisons. A returned index of 1 means the second item, not an item whose value is 1.

Count the work

Let n be the number of items. For a nonempty list, the best case is one comparison: the first item matches. The worst case is n comparisons: the target is last or absent. An empty list needs zero comparisons and returns -1.

If a target occurs once and is equally likely to occupy any position, its average comparison count is

1+2+⋯+nn=n+12.

For 1,000 items, that is 500.5 comparisons. An average can be fractional even though every individual search makes a whole number of comparisons. This average describes that particular distribution of successful searches; frequent unsuccessful searches would increase the overall average.

Put failure after the loop

An else: return -1 inside the loop would give up at the first mismatch. On [7, 3, 9], it would reject a search for 3 after seeing only 7. The failure return belongs after the loop, when every position has been ruled out.

Linear search needs no sorted order or preparation. It works with numbers, words, or records compared by an ID. A version designed for sorted data can stop when it passes the target's possible position: in [2, 5, 8, 12], seeing 8 is enough to rule out 6. The code above deliberately keeps scanning until it finds a match or reaches the end.

Related tasks use the same scan but a different stopping rule. Counting every occurrence, collecting every matching index, and finding a largest item all require inspecting the full list; finding the first match permits an early return.

Key Takeaways

  • Linear search checks positions in order and returns the first matching index.
  • Return -1 only after every possible match has been ruled out.
  • A nonempty list needs one comparison at best and n at worst.
  • The average (n+1)/2 assumes one target occurrence equally likely at each position.

Binary search uses sorted order to discard positions without checking them individually. Its input must be sorted in ascending order. It also needs quick access to an item at a chosen index, as an array or Python list provides.

Keep an inclusive range

low and high mark the first and last positions still possible, including both endpoints. Start with low = 0 and high = len(items) - 1. Choose the middle index by rounding down. If the middle value is too small, every value to its left is also too small; move low past the middle. If it is too large, move high just before it.

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

// divides and rounds down. The loop includes the case low == high because one unchecked position remains. When low > high, the range is empty.

Trace a match and a miss

Use [3, 8, 12, 17, 23, 31, 42, 56, 64, 77, 85], indexed 0 through 10. To find 64, the first middle index is 5, holding 31. Since 31 is too small, set low = 6. The next middle index is 8, holding 64. Return 8 after two middle-item checks.

To search for 20 in the same list:

ChecklowhighmidMiddle valueUpdate
1010531high becomes 4
204212low becomes 3
334317low becomes 4
444423high becomes 3

Now the range is empty, so return -1. This is a reasoned failure: sorted order justified every discarded part.

Why it is fast

Each unsuccessful check leaves at most half the range. For a nonempty list, the maximum number of middle-item checks is ⌊log⁡2n⌋+1. The base-2 logarithm describes repeated halving: 210=1,024, so a thousand items need at most 10 checks; a million need at most 20. One check in this Python code can make two comparisons, == and <. Checks and comparisons are different counting units.

Conditions and boundary bugs

Sorted order is essential. Searching [9, 2, 7, 4, 1] for 2 first sees 7 and discards the right part, then sees 9 and discards the remaining match at index 1. Quick middle access matters too: following links to reach a linked list's middle loses the array version's speed advantage.

Always exclude the tested middle after a mismatch. Using low = mid can leave [10, 20] unchanged forever when searching for 20. Using while low < high skips the final candidate and fails on [5] searched for 5. On an empty list, high = -1, so the correct loop never starts.

With duplicates, this version returns any matching index. Searching [2, 4, 4, 4, 9] for 4 returns index 2 immediately; finding the first occurrence requires a modified search that continues left after a match.

Key Takeaways

  • Binary search requires sorted data and quick access to positions.
  • Track an inclusive range and continue while low <= high.
  • After a mismatch, exclude mid with mid + 1 or mid - 1.
  • The maximum is ⌊log⁡2n⌋+1 middle-item checks for n>0.
  • A matching index need not be the first occurrence.
  • An empty range means the target is absent.
2

Sort a list with bubble sort and selection sort

A list can look ordered while still containing the wrong items. Start by making the sorting task precise, including repeated keys and the records attached to them. Then follow two methods that build order through swaps: bubble sort settles the largest remaining item at the right, while selection sort places the smallest remaining item at the left. Their traces reveal different costs in comparisons and movement.

2.1 Describe what a correctly sorted list looks like

Sorting rearranges items by a chosen key. For numbers in ascending order, each item is less than or equal to the next. “Non-decreasing” makes room for equal neighbors: [2, 5, 5, 9] is sorted. Descending order reverses the direction.

Check two conditions

A correct sort must produce ordered output containing exactly the original items, with every repetition preserved. Sorting [3, 1, 2] into [1, 2, 3] succeeds. Both [1, 2] and [1, 1, 3] fail, even though their neighbors are in order. The first loses an item; the second changes which items occur. A rearrangement preserving all occurrences is called a permutation.

For a record, the key is only the field used for comparison. Cities could be sorted by name or population. The whole city record moves when its population key moves; detaching the name would corrupt the data.

Text order depends on the key

Plain text comparison follows character codes, one character at a time. In ASCII and Unicode, A–Z use codes 65–90 and a–z use 97–122. This puts “Zebra” before “apple” in a plain comparison. Comparing lowercase copies can provide case-insensitive order for these examples.

Text digits also behave as characters. “10” sorts before “9” because its first character is “1.” A numeric key converts those strings to the numbers they represent, giving 9 before 10. Decide what the data means before choosing the comparison.

Move values without losing them

A swap exchanges two positions. A temporary variable protects the value that would otherwise be overwritten:

temp = items[i]
items[i] = items[j]
items[j] = temp

Python also allows items[i], items[j] = items[j], items[i]. Writing the two assignments without saving the first value changes [4, 7] into [7, 7]. Bubble and selection sort move items by swapping; merge sort copies items into merged output.

Stability and memory

A stable sort keeps the original order of records whose keys are equal. Start with Ana 85, Ben 92, Cara 85, in alphabetical order. A stable descending score sort produces Ben 92, Ana 85, Cara 85. Ana remains before Cara. This lets a later sort by score retain an earlier alphabetical order within each score group. Python's built-in sorting is stable.

An in-place sort rearranges the existing list with only a few extra variables, independent of its length. The bubble and selection methods taught here are in place. The usual merge sort needs extra storage proportional to the number of items. Stability and memory describe different properties: a sort can be in place yet unstable.

Measure disorder with inversions

An inversion is a pair of positions where an earlier item's key is greater than a later one's. In [5, 1, 4, 2, 8], the inversions are (5, 1), (5, 4), (5, 2), and (4, 2): four in all. Pairs need not be neighbors.

A sorted list has zero inversions. A reversed list of n distinct items has n(n−1)/2, the maximum. Equal keys never form an inversion: [3, 3, 1] has two, one for each 3 before 1. Counting inversions is useful when explaining bubble sort's swap count.

Key Takeaways

  • A correct sort preserves every original item and puts keys in order.
  • A sort key decides which field or value controls order.
  • Stability preserves the order of equal-key records.
  • An in-place sort needs only a fixed amount of extra working storage.
  • An inversion is an out-of-order pair, including pairs that are not neighbors.

2.2 Trace and count bubble sort

Bubble sort compares neighboring items from left to right. When the left item is larger, it swaps the pair. Each sweep is a pass. A large item can move right repeatedly during one pass, until the largest remaining item reaches its final position.

Follow the first pass

Start with [5, 1, 4, 2, 8]:

  1. Compare 5 and 1; swap to get [1, 5, 4, 2, 8].
  2. Compare 5 and 4; swap to get [1, 4, 5, 2, 8].
  3. Compare 5 and 2; swap to get [1, 4, 2, 5, 8].
  4. Compare 5 and 8; leave them alone.

The pass makes four comparisons and three swaps. The largest item, 8, is settled. The rest is still unsorted: 4 precedes 2. One pass guarantees the final position of the maximum, not the order of every item.

Pass 2 needs only the first four positions. Compare 1 with 4, then swap 4 with 2, then compare 4 with 5. This gives [1, 2, 4, 5, 8] after three more comparisons and one swap. Pass 3 compares 1 with 2 and 2 with 4. It makes no swaps, confirming that the list is sorted. Totals: nine comparisons and four swaps.

Shorten each pass and stop early
def bubble_sort(items):
    n = len(items)
    for p in range(n - 1):
        swapped = False
        for j in range(n - 1 - p):
            if items[j] > items[j + 1]:
                items[j], items[j + 1] = items[j + 1], items[j]
                swapped = True
        if not swapped:
            break

Pass counter p starts at zero. The largest value of j is n - 2 - p, so j + 1 stays inside the unsorted part. Reset swapped at the beginning of each pass; a swap on an earlier pass tells you nothing about whether the current pass moved an item.

After each pass, one more position at the right is final. A pass with no swaps proves all compared neighbors are in order. The already settled suffix is also in order, so the algorithm can stop. The confirming pass still counts as work.

Compare best and worst cases

For a sorted nonempty list, the early-stop version makes n−1 comparisons and zero swaps. On a reversed list of distinct items, it makes

(n−1)+(n−2)+⋯+1=n(n−1)2

comparisons and the same number of swaps. Five items need 10; 1,000 items need 499,500. Without early stopping, the five-item worked example would also run its final one-comparison pass, reaching 10 comparisons.

Each swap removes exactly one inversion. The four inversions in the original worked list explain its four swaps. Equal neighbors stay put because the test uses >, making this version stable and in place. Replacing > with >= can reverse equal-key records.

Position matters even when there are few inversions. [5, 1, 2, 3, 4] uses four swaps and a second pass to confirm order, for seven comparisons. [2, 3, 4, 5, 1] also needs four swaps, but its 1 can move left only once per pass. It needs all four passes and 10 comparisons.

Key Takeaways

  • Each pass settles the largest remaining item at the right.
  • A pass with no swaps allows an early stop.
  • Count the pass that confirms order, even though it makes no swaps.
  • Worst-case comparisons are n(n−1)/2; sorted input needs n−1.
  • Swapping only strictly out-of-order neighbors preserves stability.
  • The number of swaps equals the input's inversion count.

2.3 Trace and count selection sort

Selection sort builds a sorted prefix on the left. Each pass finds the smallest item in the remaining suffix, then swaps it into the prefix's next position. The scan finds the position; the swap happens after the scan.

Remember an index

At position i, begin with smallest = i. Compare each later item with items[smallest]. Update smallest only when a smaller item appears. When the scan ends, exchange items[i] and items[smallest].

def selection_sort(items):
    n = len(items)
    for i in range(n - 1):
        smallest = i
        for j in range(i + 1, n):
            if items[j] < items[smallest]:
                smallest = j
        if smallest != i:
            items[i], items[smallest] = items[smallest], items[i]

Remembering only the smallest value would leave you without its position for the exchange. Starting the scan at index 0 would revisit the items already placed; the remaining search starts just after i.

Trace the passes

Start with [29, 10, 14, 37, 13].

PassRemaining minimumList after placementComparisonsMoving swaps
110[10, 29, 14, 37, 13]41
213[10, 13, 14, 37, 29]31
314[10, 13, 14, 37, 29]20
429[10, 13, 14, 29, 37]11

The third minimum is already in position, so no values move. After four passes, the last item must be the largest; it needs no separate pass. There are 10 comparisons and three swaps that move items. The conditional in the code skips exchanges of a position with itself; omitting that conditional leaves the result unchanged.

Comparisons and swaps have different costs

Selection sort always makes n(n−1)/2 item comparisons: every pass must inspect the entire remaining suffix to identify its minimum. The total is 10 for five items or 499,500 for 1,000, even on already sorted input. A scan that changes no remembered index establishes that pass's minimum, but gives no general guarantee that the remaining suffix is ordered.

For a nonempty list, there is at most one moving swap per pass, hence at most n−1 overall. Empty input needs no passes or swaps. Bubble sort can need n(n−1)/2 swaps on a reversed list of distinct items. If moving large records costs much more than comparing their keys, selection sort's small swap count can matter despite its many comparisons. Swapping every time a new minimum appears during the scan loses that advantage.

Equal keys can change order

This swap-based method is in place but unstable. Label equal-key records 4a and 4b so their identity is visible. Starting with [4a, 4b, 1], the first pass exchanges 4a and 1, producing [1, 4b, 4a]. The equal keys have reversed their original order even though the algorithm never directly compared them as unequal. Keeping the first minimum found during a scan does not prevent a distant swap from crossing an equal-key record.

Key Takeaways

  • Selection sort scans for a minimum, then swaps once after the scan.
  • After each pass, the sorted prefix gains one item.
  • Its comparison count is always n(n−1)/2.
  • For nonempty input, it uses at most n−1 moving swaps and fixed extra storage.
  • The usual distant-swap version is unstable.
3

Sort faster with merge sort

Sorting a large list by repeated full scans becomes expensive. Merge sort uses a different plan: divide the list into smaller pieces, sort those pieces, and combine their results. First learn why two sorted lists can be merged by looking only at their front items. Then follow the recursive splits and merges, keeping track of the work at each level and the extra memory needed to hold the output.

3.1 Merge two sorted lists into one

Merging combines two already sorted lists into one sorted output. Keep a marker at the first unused item of each input. Compare those two front items, copy the smaller one, and advance only its marker.

Choose the next smallest item

Merge [3, 27, 38, 43] with [9, 10, 82]:

Front comparisonCopy to outputOutput so far
3 and 93[3]
27 and 99[3, 9]
27 and 1010[3, 9, 10]
27 and 8227[3, 9, 10, 27]
38 and 8238[3, 9, 10, 27, 38]
43 and 8243[3, 9, 10, 27, 38, 43]

The left list is now empty. Copy the remaining 82 without another item comparison. The result is [3, 9, 10, 27, 38, 43, 82], using six comparisons. Copying the tail is part of the algorithm: stopping when the comparisons stop would lose data.

Why can the method ignore items behind the fronts? Each front is the smallest remaining item of its own list. The smaller front is therefore the smallest remaining item overall. This reasoning continues after each marker moves because the unused portions remain sorted.

Read the merge code
def merge(left, right):
    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
    result.extend(left[i:])
    result.extend(right[j:])
    return result

append adds one item. A slice such as left[i:] contains the unused tail, and extend copies its items. Only one unused tail can be nonempty when the loop ends. If an input starts empty, the loop makes zero comparisons and the other list is copied intact.

Count comparisons and storage

For two nonempty lists of lengths m and k, at most m+k−1 front comparisons are needed. Every comparison places one item, and at least the final item can be copied without comparing. The shortest possible merge comparison count is the length of the shorter list: [1, 2, 3] merged with [4, 5, 6] uses three comparisons before the left side empties.

The total copying work grows with m+k even when comparisons finish early, because every item must appear in the output. The new list also needs room for m+k items. Comparison count, copying work, and extra storage measure different parts of the same operation.

Handle ties and input order

The <= test takes from the left on equal keys. If the two inputs came from consecutive parts of an original list, this preserves the earlier record before the later equal-key record. It is the tie rule needed for stable merge sort.

The sorted-input requirement is firm. Merging [5, 1] with [3] copies 3, then the tail [5, 1], producing [3, 5, 1]. The front rule failed because 5 was not the smallest remaining value in its own input.

Key Takeaways

  • Merge compares the first unused items of two sorted inputs.
  • Copy the unused tail when one side empties.
  • Two nonempty inputs need at most m+k−1 front comparisons.
  • Taking from the left on equal keys preserves order across the two halves.

3.2 Trace merge sort and explain its n log n growth

Merge sort solves a large sorting task by splitting it into smaller sorting tasks. This is divide and conquer: divide the list, sort each half, and merge the sorted halves.

Stop splitting at the base case

A function is recursive when it calls itself. Merge sort calls itself on shorter lists. Its base case returns a list of zero or one items unchanged, since such a list is already sorted.

def merge_sort(items):
    if len(items) <= 1:
        return items
    mid = len(items) // 2
    left = merge_sort(items[:mid])
    right = merge_sort(items[mid:])
    return merge(left, right)

The slices divide the input without losing items. For an odd length, the right side has one more item: [6, 2, 9, 4, 1, 7, 3] splits into [6, 2, 9] and [4, 1, 7, 3]. Both halves are shorter, so recursive calls eventually reach the base case. A base case alone would not ensure termination if a call kept passing the original list back to itself.

Rebuild the sorted list

Trace [38, 27, 43, 3, 9, 82, 10, 15]. It splits into two four-item halves, then four two-item pieces, then single items. Splitting does not rearrange the values. Order is built during the merges:

  1. Merge singletons into [27, 38], [3, 43], [9, 82], and [10, 15]. Each uses one comparison: four total.
  2. Merge pairs into [3, 27, 38, 43] and [9, 10, 15, 82]. Each uses three comparisons: six total.
  3. Merge those halves into [3, 9, 10, 15, 27, 38, 43, 82]. This uses seven comparisons.

The full sort uses 4+6+7=17 item comparisons. Do not count only the final merge: the sorted halves had to be produced first. The seven-item example above sorts to [1, 2, 3, 4, 6, 7, 9] using 14 comparisons with this splitting rule.

Count levels and work per level

For eight items, halving reaches singletons after three levels because 23=8. In general, there are about log⁡2n merge levels. Across a complete level, the merges collectively handle all n items. Each level therefore does work proportional to n, leading to growth like nlog⁡2n.

For eight items, nlog⁡2n=24 is a useful loose comparison bound, not the exact count. The worked list used 17; an already sorted or reversed eight-item list uses 12. The input order changes when a merge exhausts one side, but every list still goes through the splitting and merging levels. The comparison growth remains nlog⁡n across best and worst cases for this version.

For a million items, about 20 levels give about 20 million comparisons as an upper-scale estimate. Selection sort uses about 500 billion. The difference grows rapidly with input size.

Pay for the extra output space

The ordinary list-based merge sort needs extra storage proportional to n. Choosing the left front on ties makes it stable.

Key Takeaways

  • Merge sort recursively sorts two shorter halves, then merges them.
  • Lists of zero or one items form the base case.
  • Odd lengths split into halves differing by one item.
  • About log⁡2n levels each do work proportional to n.
  • Exact comparison counts depend on order; the growth family is nlog⁡n.
  • The usual version is stable with left-first ties and needs proportional extra storage.
4

Count steps and compare how running times grow

A stopwatch tells you how one program ran once; a step count helps explain what happens when the input grows. Choose an operation to count, turn repeated work into a formula, and compare growth patterns from a single lookup to trying every selection. Use those patterns to estimate running times and time budgets, then design measurements that can check whether the predictions fit real runs.

4.1 Count the steps an algorithm takes

A measured running time depends on the machine and program. A step count describes the algorithm's work using a chosen repeated operation. The input size, written n, is the number of items being processed.

Choose the operation before counting

For searching and sorting, comparisons between item values usually make a useful basic operation. Count swaps separately when moving data matters. For binary search, it is convenient to count checks of a middle item. In the Python version, an unsuccessful middle check evaluates both equality and less-than, so it can involve two item comparisons.

MethodCounting unitWorst-case count
Linear searchItem-target comparisonsn
Binary search, nonempty inputMiddle-item checks⌊log⁡2n⌋+1
Bubble sortNeighbor comparisonsn(n−1)/2
Selection sortKey comparisonsn(n−1)/2
Merge sortFront-item comparisonsAt most about nlog⁡2n

These counts describe repeated operations, not lines of code. One line inside a loop can execute a million times; several setup lines may each execute once.

Separate cases at the same size

The best case takes the fewest steps among valid inputs of size n. The worst case takes the most and supplies a guarantee. The average case averages over a stated pattern of inputs.

For a nonempty list, linear search needs one comparison if the target is first, and n if it is last or absent. Its successful-search average is (n+1)/2 when the first matching position is equally likely to be any position. The word “average” needs that distribution; it does not mean the midpoint of the best and worst counts for every algorithm.

Translate loops into counts

One comparison per item in a full scan gives n comparisons. Two full nested loops, each running n times, execute their shared body n2 times. A different nested-loop pattern compares each pair only once:

for i in range(n):
    for j in range(i + 1, n):
        compare(items[i], items[j])

The first outer iteration compares its item with n−1 later items. The next compares with n−2, and the last compares with none. The total is

1+2+⋯+(n−1)=n(n−1)2.

Write the sum forward and backward. Each aligned pair adds to n, and there are n−1 such pairs. Their total is n(n−1), twice the original sum. For five items, 4+3+2+1=10; for 1,000, the pair count is 499,500.

Recognize repeated halving

A loop that halves its remaining work takes about log⁡2n repetitions. One million first falls to 1 or below after 20 halvings: 219=524,288 and 220=1,048,576 straddle a million. This differs sharply from removing just one item each time.

For growth comparisons, keep the fastest-growing term. The pair count is n2/2−n/2. At n=1,000, its terms are 500,000 and 500, so the quadratic term controls the growth. Dropping the smaller term and the constant factor describes the growth family; it does not turn the exact count of 499,500 into an exact count of one million.

Key Takeaways

  • Name the operation being counted and express the count using input size.
  • Worst-case counts are guarantees; averages depend on an input distribution.
  • Full nested scans give n2 body executions.
  • Comparing each distinct pair once gives n(n−1)/2 comparisons.
  • Repeated halving produces logarithmic growth.

4.2 Compare how step counts grow with input size

A growth rate describes how the amount of work changes as the input gets larger. It helps compare algorithms beyond the particular size used in one trace.

Read the common families
Growth familyExampleEffect of doubling input size
Constant, 1Read a known array positionSame work
Logarithmic, log⁡2nBinary searchAbout one extra check
Linear, nFull scanTwice the work
nlog⁡nMerge sortA little more than twice
Quadratic, n2Selection sortAbout four times
Exponential, 2nTry every selection of itemsSquares the count

For exponential growth, adding just one item already doubles the count: 2n+1=2×2n. Doubling the input is a much larger change than adding one.

Compare sizes numerically

For inputs of 10, 100, 1,000, and one million items, log⁡2n is about 3.3, 6.6, 10, and 20. The corresponding nlog⁡2n values are about 33, 664, 9,966, and 20 million. The quadratic values are 100, 10,000, one million, and one trillion.

Logarithmic growth is not “half the list.” For a million items, halving repeatedly takes only about 20 stages. When input rises from 1,000 to 2,000, nlog⁡2n rises from about 9,966 to 21,932, a factor of about 2.2. The logarithm changes too, so the count grows slightly faster than linear.

Exponential work becomes large quickly. 210=1,024 and 220 is about a million. At a billion steps per second, 250 steps still take about 13 days. Adding one more item doubles that time.

Use informal O labels

Programmers commonly label these families O(1), O(log⁡n), O(n), O(nlog⁡n), O(n2), and O(2n). Here they identify the usual worst-case growth family. Read O(n2) as “order n squared.” The label suppresses constant factors and lower-order terms: n(n−1)/2 comparisons belongs to the quadratic family.

An O label is not an exact running time or a number of seconds. It also does not claim that every input uses the worst-case count. Bubble sort with early stopping has a linear best case but a quadratic worst case.

Find the crossover

Suppose method A takes 100n steps and method B takes n2. At 50 items, A takes 5,000 steps and B takes 2,500. At 100, both take 10,000. At 1,000, A takes 100,000 and B takes one million. A's slower growth eventually overcomes its larger constant factor.

Hardware speed changes the scale without changing the family. A computer 100 times faster gives a quadratic method room for only 10 times as many items in the same time, since (10n)2=100n2. A linear method could handle 100 times as many. For a small task, setup and constants may dominate; for large inputs, growth becomes decisive.

Key Takeaways

  • Doubling input roughly doubles linear work and quadruples quadratic work.
  • Logarithmic work rises by about one step when input doubles.
  • Merge sort combines linear work per level with logarithmically many levels.
  • Adding one item doubles a 2n step count.
  • Growth labels omit constants; actual counts decide small-input crossovers.

4.3 Predict and measure running times

A known running time and a growth pattern let you predict how a larger task will behave. The prediction is an estimate because real time also depends on implementation, memory, hardware, and other work on the machine.

Scale the input, then the time

For linear work, multiply the time by the input-size ratio. If a worst-case search of one million items takes 3 milliseconds, ten million items take about 30 milliseconds under comparable conditions.

For quadratic work, square the ratio. Suppose selection sort takes 2 seconds for 50,000 items. Doubling to 100,000 items gives

2×(100,00050,000)2=2×4=8

seconds. Increasing to 500,000 items multiplies input size by 10, so the time estimate is 2×102=200 seconds. Scaling the time by 10 would incorrectly treat the algorithm as linear.

For nlog⁡2n growth, use the ratio of both full expressions. Doubling a large input costs a little more than twice as much because the logarithm increases along with the item count.

Work backward from a time budget

Use the same quadratic baseline: 2 seconds for 50,000 items. A 32-second budget allows 16 times the time. The input can grow by the square root of 16, which is 4, giving about 200,000 items. Check the direction by substitution: four times the input predicts 16 times the time, taking 2 seconds to 32 seconds.

Compare algorithms at a large size

For one million items, selection sort makes 499,999,500,000 comparisons, about 500 billion. Merge sort needs at most about 20 million. At a hypothetical billion comparisons per second, those counts correspond to about 500 seconds versus 0.02 seconds, roughly a 25,000-fold difference. This calculation uses equal comparison cost to isolate the algorithmic effect; measured programs also pay for movement and memory access.

Test a growth prediction

Time the same program on several doubling sizes, such as 10,000, 20,000, and 40,000 items. Divide each time by the previous time. Ratios near 2 suggest linear growth; a little above 2 suggests nlog⁡n; near 4 suggests quadratic growth.

Keep the machine and measurement method consistent. Repeat runs and use sizes large enough that startup time does not dominate. Compare like cases: searching at the first position at one size and searching unsuccessfully at another would mix input effects with growth. For bubble sort, sorted lists and reversed lists need different case labels.

If doubling sizes gives 0.04, 0.16, and 0.64 seconds, both ratios are 4, supporting a quadratic model over that range. One noisy ratio is weaker evidence than a repeated pattern. Step counts for a specified trace can be exact; a prediction in seconds should say “about” and be checked against measurements.

Key Takeaways

  • Linear time scales with the input ratio; quadratic time scales with its square.
  • For a quadratic time budget, use a square root to recover the input ratio.
  • Measure several sizes with comparable input cases and repeated runs.
  • Timing ratios suggest growth patterns; predicted seconds remain estimates.
5

Argue correctness and choose the right algorithm

A fast trace on one list leaves two questions open: will the algorithm always work, and does it fit this task? State what valid input and correct output mean, use a loop invariant to explain why the method preserves progress, and show why it stops. Then choose tests that expose boundary bugs and compare algorithms using the task's actual needs for time, memory, stability, and access to the data.

5.1 Argue that an algorithm is correct

A correct algorithm stops on every valid input and produces the required output. A specification states the input condition and the promised result. For binary search, the precondition is ascending sorted order. The postcondition is a matching index, or -1 exactly when the target is absent.

Use an invariant to connect steps

A loop invariant is a statement that holds before the loop begins and is preserved by every repetition. A correctness argument checks its start, its preservation, and what it implies when the loop ends. Separately, a termination argument shows that the loop eventually ends.

For selection sort, use this invariant: before pass i, the first i positions contain the i smallest items in order.

  1. Start: before pass 0, the prefix is empty. It already meets the statement.
  2. Preserve: the pass finds the smallest remaining item. Putting it next extends the sorted prefix by one, and the item is at least as large as every previously placed item.
  3. Finish: after at most n−1 passes, only the largest item remains outside the prefix. The whole list is sorted.

Every move is a swap, so the multiset of items also stays unchanged. Ordered output alone would not meet the full sorting specification.

Bubble sort uses a suffix invariant: after k passes, the last k positions contain the k largest items in order. The next pass carries the largest unsorted item to that suffix. A pass with no swaps establishes order throughout the remaining part, allowing early termination.

Explain binary search's discarded ranges

Use the invariant: if the target occurs, at least one occurrence is inside the inclusive low–high range. Initially that range covers the whole list. If the middle value is too small, sorted order makes every value at or left of mid too small. Updating low to mid + 1 preserves every possible match. The update to high = mid - 1 follows the same reasoning for values that are too large.

On a match, the returned position directly meets the postcondition. If the range empties, the invariant rules out a target anywhere in the list. The input remains sorted because the search never changes it.

Show why execution stops

A shrinking nonnegative whole-number measure proves progress toward an end. In binary search, the number of candidate positions strictly decreases after every mismatch. The tested midpoint is excluded, so the loop cannot keep the same range forever. Bubble and selection sort make at most n−1 passes on nonempty input and no passes on empty input.

Merge sort reaches lists of zero or one items because every recursive call uses a shorter list. Those base cases are sorted. Assuming the shorter calls return correct sorted halves, merge repeatedly takes the smallest remaining item and preserves all items. This carries correctness back up to the original call.

Use tests for a different job

A test compares one actual result with one expected result. Passing many selected tests builds confidence, while a single failing valid input is a counterexample that disproves correctness. A general invariant argument covers the unbounded family of possible inputs that a finite collection of sample tests leaves unchecked. Testing is especially useful for checking whether actual code implements the method used in the argument.

Key Takeaways

  • Correctness requires the specified result and termination on every valid input.
  • An invariant must hold initially, survive each iteration, and support the result at exit.
  • A sorting argument must preserve items as well as establish order.
  • A strictly decreasing nonnegative measure establishes termination.
  • One failing valid input disproves correctness.

5.2 Test algorithms with edge cases

An edge case is a valid input near a boundary where assumptions and loop limits are easy to get wrong. Plan the expected result from the specification before running the code. Otherwise a program's mistaken output can quietly become your expected answer.

Choose lists that exercise boundaries

Test empty, one-item, and two-item lists; already sorted and reversed lists; repeated and all-equal values; negative numbers; and odd and even lengths. For searches, include the first position, the last position, and targets absent below, above, and between stored values.

An empty binary search should return -1 without accessing an item: low = 0 and high = -1 make the initial range empty. Sorting [3, 3, 3] should return three copies of 3. An output of [3] would be ordered but would fail item preservation.

Trace the smallest failure

A binary search using while low < high fails to enter its loop for [5] with target 5. The fix is low <= high, since a one-position range still holds a candidate. This one-item test isolates the boundary more clearly than a long list does.

With low = mid, searching [10, 20] for 20 sets the midpoint to 0, sees 10, and leaves low at 0. The range is unchanged on the next repetition. Use low = mid + 1 to exclude a position already checked. A two-item list with the target last reveals this endless loop.

Bubble sort compares items[j] with items[j + 1]. On the first pass, the last valid left index is n - 2. Continuing to n - 1 reads beyond the list and raises Python's IndexError. Such a one-position boundary error is called an off-by-one error.

Check both output promises

For a sort, check adjacent order and the count of every original item. Use labeled records to test stability. Ana 85, Ben 92, Cara 85 must keep Ana before Cara when a stable score sort groups the equal scores. A score-only check would miss an exchange of those two records.

Generate random lists too. Compare their results with a trusted implementation such as Python's sorted. Keep duplicates and different lengths in the generated inputs. When a test fails, shrink the input while preserving the failure, then trace it by hand. Keep the small example as a regression test so the repaired bug stays detectable.

Keep midpoint arithmetic safe

Python integers grow as needed, so (low + high) // 2 does not overflow. Some languages use fixed-width integers. For very large nonnegative indexes, their sum can exceed the storable range. Java integer addition wraps; signed integer overflow in C has undefined behavior.

With valid indexes and low <= high, compute low + (high - low) // 2 instead. The difference is nonnegative, and the result stays between the endpoints. This calculates the same rounded-down midpoint without forming the potentially overflowing sum. Boundary testing and reasoning about the language's number representation work together here.

Key Takeaways

  • Write expected results before running tests.
  • Small lists expose missing final checks, unchanged ranges, and invalid neighbor access.
  • Check item preservation, order, and stability when stability is required.
  • Shrink random failures and keep them as regression tests.
  • The difference-based midpoint avoids overflow for valid nonnegative fixed-width indexes.

5.3 Choose an algorithm for the situation

An algorithm fits a task when its requirements and costs match the data and the work being done. Consider input order, access to positions, number of future operations, extra memory, stability, and the cost of moving records.

Compare the methods
MethodTime or step patternExtra storageStable?
Linear searchUp to n comparisonsFixedNot a sorting property
Binary searchUp to ⌊log⁡2n⌋+1 middle checksFixedNot a sorting property
Bubble sort with early stopn−1 comparisons if sorted; quadratic worst caseFixedYes, with strict swaps
Selection sortn(n−1)/2 comparisons; at most n−1 moving swapsFixedNo, in usual swap form
Merge sortnlog⁡n comparison growthProportional to nYes, with left-first ties

Binary search also needs sorted order and quick access to the middle. A good comparison includes those prerequisites; a small search count alone leaves out the cost of preparing unsuitable data.

Decide whether sorting first pays

For one search of a million unsorted items, linear search costs at most a million comparisons. Sorting first by merge sort costs about 20 million comparisons before the first binary search, which then needs at most 20 middle checks. Preparation overwhelms the saving on that one query.

For 100 queries on an unchanged list, repeated linear searches can cost 100 million comparisons. Sorting once and making 100 binary searches costs about 20 million comparisons plus 2,000 middle checks. The preparation is reused, making the second plan attractive. Under this rough cost model, break-even is around 20 searches. The estimate depends on comparable operation costs and worst-case scans; it is not a universal query threshold.

Weigh order, movement, and memory

Bubble sort can finish quickly when only a nearby pair is misplaced. [1, 2, 4, 3, 5] needs one swap in its first pass and a second pass to confirm order: seven comparisons. But “nearly sorted” does not guarantee few passes. A small value at the far right can move left only one position each pass.

Selection sort's few swaps help when records are expensive to move. Both selection and bubble sort use only a fixed amount of extra storage. Merge sort handles large lists with much slower time growth, but its usual list-based form needs room for about another list's worth of items.

If equal-key records must retain earlier ordering, choose a stable method. A list already alphabetical by name can be stably sorted by grade while staying alphabetical inside each grade group. Ordinary selection sort can disrupt those ties.

Use tested library tools

Real programs usually use the language's built-in sort. Python's stable sorting uses an adaptive merge-based method commonly called Timsort, benefiting from order already present in the input. sorted(items) returns a new list, while items.sort() changes the existing list. A method changing a list in place as an API behavior can still use temporary working memory internally; it need not satisfy the fixed-extra-storage definition used for the simple sorts above.

For a handful of items, simple, clear code may matter more than growth. For large collections, compare worst-case behavior and memory alongside the properties the task actually requires.

Key Takeaways

  • Include preparation cost when comparing searches on unsorted data.
  • Repeated searches can repay a one-time sort.
  • Selection sort limits swaps; merge sort limits time growth but needs extra storage.
  • Use a stable sort to preserve earlier order within equal-key groups.
  • Built-in sorts offer tested implementations; small inputs can favor simplicity.

Searching, Sorting and Efficiency examples

5 Walkthroughs

1

Find a target without losing track of positions

13 steps
Trace linear and binary search, count their work, and check the boundaries of each search.
  1. 1
    IntroductionFind a value and report its position

    A search must distinguish a stored value from its position. In [7, 3, 9, 3, 5], positions run from 0 to 4. The value 3 appears at indexes 1 and 3.

    We will first check positions in order (linear search), then use sorted order to exclude groups of positions (binary search), counting comparisons to see how much work each does. Both methods return an index on success and -1 when the target is absent.

  2. 2
    ExplanationLet one mismatch rule out one position

    Linear search compares the target with each item from left to right. A match ends the search immediately; a mismatch moves to the next position.

    An unsorted list gives no reason to reject later items after an early mismatch. That is why the “not found” return belongs after the loop, once every position has been checked.

  3. 3
    ExampleFind the first 3

    Search [7, 3, 9, 3, 5] for 3.

    At index 0, 7 differs from 3, so continue. At index 1, the stored value is 3, so return 1 after two comparisons.

    The second 3 is never examined. The returned index is 1, while the number of comparisons is 2; those numbers describe different parts of the trace.

  4. 4
    ChallengeTrace a later match

    Linear search checks [19, 4, 11, 4, 8] for 11. What index does it return, and how many comparisons does it make?

    ---reveal---

    It returns index 2 after three comparisons: 19, 4, then 11. A failure return after the first mismatch would incorrectly stop at 19. Index 2 is the third position because positions start at zero.

  5. 5
    Key InsightLet the target determine the stopping time

    For a nonempty list of n items, linear search uses one comparison at best and n at worst. An empty list uses zero.

    With one target occurrence equally likely at each position, the successful-search average is (n+1)/2. Counting every occurrence changes the task: even an early match cannot end that full scan.

  6. 6
    AnalogyUse order to rule out a side

    A dictionary lets you turn toward the half containing the word you want. Binary search uses the same ordering idea with exact index bounds.

    Its precondition is sorted data. If a middle number is too small, every earlier number is also too small. Without order, a comparison with the middle gives no such information about the other positions.

  7. 7
    ExplanationKeep both endpoints in play

    Start with low = 0 and high = n - 1. While low <= high, calculate mid = (low + high) // 2.

    Return mid if it matches. If its value is too small, set low = mid + 1; if too large, set high = mid - 1. The tested midpoint is excluded after either mismatch, so every update reduces the candidate range.

  8. 8
    ExampleCheck the middle, then the right half

    Find 64 in [3, 8, 12, 17, 23, 31, 42, 56, 64, 77, 85]. The initial bounds are 0 and 10. Their rounded-down midpoint is 5, holding 31.

    Since 31 is below 64, set low = 6. The remaining bounds 6 and 10 give midpoint 8. Its value is 64, so return 8 after two middle-item checks.

  9. 9
    ExampleWatch a missing target empty the range

    In that same sorted list, search for 20. The checked indexes and values are 5 → 31, 2 → 12, 3 → 17, and 4 → 23.

    The updates leave low = 4, high = 3. No candidate remains, so return -1. The last check mattered: equal endpoints just before it meant one candidate, not an empty range.

  10. 10
    ChallengeRecompute the midpoint after every update

    Search [2, 7, 13, 21, 34, 55] for 34 using inclusive bounds and rounded-down midpoints. What indexes are checked?

    ---reveal---

    First check index 2, holding 13. Set low = 3 while high stays 5, so the next midpoint is 4. It holds 34: return 4 after two checks. The next position after 2 was not automatically the next midpoint.

  11. 11
    TipCheck the last candidate and exclude old ones

    low <= high checks a one-position range. A strict < condition misses [5] when searching for 5.

    Use mid + 1 and mid - 1 after mismatches. With low = mid, a search for 20 in [10, 20] can repeat midpoint 0 forever. If duplicates match, this version returns a matching index, with no guarantee it is the first one.

  12. 12
    ChallengeChoose a search for the actual input

    You have an unsorted list and need one lookup, with no time to prepare a sorted copy. Which taught search applies directly, and why?

    ---reveal---

    Linear search applies because each equality check is valid without any ordering. Binary search needs sorted order to justify the ranges it discards. A small middle-check count cannot compensate for a missing precondition.

  13. 13
    RecapUse the right evidence to stop

    Linear search stops at the first match or after a full scan. Binary search uses sorted order and quick index access to shrink an inclusive range.

    Record the returned index separately from the work count. Binary search needs at most ⌊log⁡2n⌋+1 middle checks for nonempty input, but each check can contain more than one comparison in code.

2

Build order through neighboring and distant swaps

16 steps
Check sorting requirements, then trace bubble sort and selection sort and compare their costs.

Save this lesson to follow every Walkthrough step by step.

  1. 1
    IntroductionMake the sorting task precise
  2. 2
    ExplanationPreserve items as well as order
  3. 3
    ExampleSee what stability preserves
  4. 4
    ChallengeCheck both sorting conditions
  5. 5
    ExplanationCarry a large item to the right
  6. 6
    ExampleFollow three successive swaps
  7. 7
    ExampleCount the confirming pass
  8. 8
    ChallengeCompare swaps with comparisons
  9. 9
    Key InsightCount inversions to count swaps
  10. 10
    ExplanationSelect a minimum before moving it
  11. 11
    ExamplePlace the first two minima
  12. 12
    ExampleFinish without swapping a value with itself
  13. 13
    ChallengeFind a minimum after the prefix
  14. 14
    Key InsightA distant swap can cross an equal key
  15. 15
    ChallengeMatch a method to expensive movement
  16. 16
    RecapTrack the region each method settles
3

Split a list and rebuild it with merges

13 steps
Follow the merge operation, then trace a complete recursive merge sort and count its levels.

Save this lesson to follow every Walkthrough step by step.

  1. 1
    IntroductionUse order already built in smaller pieces
  2. 2
    ExplanationCompare only the unused fronts
  3. 3
    ExampleMerge the first few values
  4. 4
    ExampleCopy the tail after comparisons end
  5. 5
    ChallengeComplete a merge and its count
  6. 6
    ExplanationMake each recursive problem shorter
  7. 7
    ExampleSplit eight items into singletons
  8. 8
    ExampleRebuild pairs and halves
  9. 9
    ExampleCount every merge level
  10. 10
    ChallengeInclude the work inside the halves
  11. 11
    ConnectionPreserve ties during reconstruction
  12. 12
    ChallengeApply the method to a large stable sort
  13. 13
    RecapCount items across levels
4

Turn repeated work into a time estimate

14 steps
Count loop executions, compare growth families, and estimate running times and time budgets.

Save this lesson to follow every Walkthrough step by step.

  1. 1
    IntroductionAsk how much work the next size needs
  2. 2
    ExplanationChoose what counts as a step
  3. 3
    ExampleCount each pair only once
  4. 4
    ChallengeRead the bounds before naming the growth
  5. 5
    Key InsightKeep the case with the count
  6. 6
    ExplanationTry the doubling test
  7. 7
    ExampleSeparate a growth label from an exact count
  8. 8
    ChallengeFind a crossover from exact models
  9. 9
    ConnectionSee the limit of faster hardware
  10. 10
    ExampleScale a measured quadratic time
  11. 11
    ChallengeRecover an input size from a time budget
  12. 12
    TipMeasure comparable cases repeatedly
  13. 13
    ChallengeUse observed ratios for a prediction
  14. 14
    RecapCarry the counting model into the estimate
5

Show an algorithm works and fits the task

15 steps
Use invariants, termination arguments, edge-case tests, and cost comparisons to evaluate an algorithm.

Save this lesson to follow every Walkthrough step by step.

  1. 1
    IntroductionAsk for a guarantee and a practical fit
  2. 2
    ExplanationState the promises before proving them
  3. 3
    ExampleExtend selection sort’s invariant
  4. 4
    ExamplePreserve a possible match and shrink its range
  5. 5
    ChallengeSeparate preservation from termination
  6. 6
    ExplanationPredict boundary results before running
  7. 7
    ExampleLet a two-item range expose a loop
  8. 8
    ChallengeFind the neighbor-loop boundary
  9. 9
    TipKeep a minimal failing test
  10. 10
    ExplanationInclude the preparation cost
  11. 11
    ExampleReuse the sorted list across queries
  12. 12
    ChallengeCalculate whether preparation pays
  13. 13
    ConnectionChoose properties along with time growth
  14. 14
    ChallengeJoin correctness to a practical requirement
  15. 15
    RecapUse arguments, tests, and costs together

Searching, Sorting and Efficiency practice questions

40 Practice Questions in 4 sets

Set 1: Find values and follow the algorithms10 Practice Questions

Question 1 of 10

Linear search checks [12, 7, 18, 7] for 7. What index does it return?
  1. 0
  2. 1
  3. 2
  4. 3
Show answerHide answer
Answer: B
The first match is the second item, which has index 1 because indexing starts at zero. Linear search stops there, so it never reaches the second 7 at index 3. Returning 2 would confuse the number of items checked with the position returned.

Question 2 of 10

Binary search uses inclusive bounds and a rounded-down midpoint on [4, 9, 15, 22, 30, 41, 57]. What indexes does it check when searching for 41?
  1. 3, 4, 5
  2. 3, 6, 5
  3. 4, 5
  4. 3, 5
Show answerHide answer
Answer: D
The first midpoint is index 3, holding 22. Because 41 is larger, the remaining bounds are 4 and 6, whose midpoint is 5, where 41 is found. Checking index 4 next would scan the remaining range from its start instead of choosing its midpoint.

Question 3 of 10

A sorting function receives [6, 2, 6, 1]. What output meets the ascending sorting specification?
  1. [1, 2, 6, 6]
  2. [1, 2, 6]
  3. [1, 2, 2, 6]
  4. [6, 6, 2, 1]
Show answerHide answer
Answer: A
The output must be non-decreasing and preserve every original occurrence, including both copies of 6. The list [1, 2, 6] has ordered values but loses one item. Sorting rearranges data; it does not remove repeated values.

Question 4 of 10

What list remains after one complete left-to-right bubble sort pass on [8, 3, 6, 2]?
  1. [3, 6, 8, 2]
  2. [2, 3, 6, 8]
  3. [3, 6, 2, 8]
  4. [3, 8, 2, 6]
Show answerHide answer
Answer: C
The comparisons swap 8 with 3, then 8 with 6, then 8 with 2, leaving [3, 6, 2, 8]. The largest value is now settled at the right. A fully sorted result assumes a single pass also orders the remaining prefix, which this trace shows is false.

Question 5 of 10

Merge [2, 11] with [5, 14, 19]. After 11 is copied, what items must still be copied?
  1. 5 and 19
  2. 14 and 19
  3. 19 only
  4. 11 and 14
Show answerHide answer
Answer: B
After copying 2, 5, and 11, the left input is exhausted and the right input still holds 14 and 19. Both must be copied to preserve all items. Copying only 19 would skip 14, even though no more front-item comparisons are needed.

Question 6 of 10

A merge sort call receives [16]. What should it return?
  1. [16] immediately
  2. [] immediately
  3. [16, 16] after merging
  4. [] after splitting
Show answerHide answer
Answer: A
A one-item list already meets the sorting conditions, so the base case returns [16] unchanged. Returning an empty list would lose the original item. Another recursive split is unnecessary because the problem has already reached its stopping size.

Question 7 of 10

An algorithm compares every pair of distinct positions once in a 7-item list. How many item comparisons does it make?
  1. 49
  2. 42
  3. 28
  4. 21
Show answerHide answer
Answer: D
The successive items need 6, 5, 4, 3, 2, 1, and 0 later-position comparisons, totaling 21. Equivalently, use 7×6/2. A count of 42 counts each unordered pair twice, once from each direction.

Question 8 of 10

A method takes exactly n2 steps. How does the count change when input size doubles?
  1. It doubles.
  2. It triples.
  3. It quadruples.
  4. It stays unchanged.
Show answerHide answer
Answer: C
Replacing n by 2n gives (2n)2=4n2, so the count becomes four times as large. Doubling only the step count would describe linear growth. The square acts on the size ratio as well as the original input size.

Question 9 of 10

Selection sort has placed the smallest four items in order at indexes 0–3. Why does choosing the minimum of indexes 4 onward and swapping it into index 4 preserve that prefix property?
  1. The remaining suffix is already ordered.
  2. The fifth item is unchanged by every scan.
  3. The new item is no smaller than the placed four.
  4. The previous swaps removed every inversion.
Show answerHide answer
Answer: C
The remaining minimum is at least as large as every item already placed, so appending it extends the ordered prefix of smallest items. A full suffix scan identifies that minimum. It does not establish that the entire suffix was already ordered, which would be a stronger and unjustified claim.

Question 10 of 10

An inclusive-bound binary search accidentally uses while low < high. What smallest nonempty test exposes a missed present target?
  1. [13], target 13
  2. [13], target 8
  3. [8, 13], target 8
  4. [8, 13], target 20
Show answerHide answer
Answer: A
With one item, both bounds equal 0, so the incorrect strict condition skips the only candidate and reports failure for target 13. An absent target on that same list can still receive the correct result. The test needs a present target to expose the missing final check.
Set 2: Count work and choose practical methods10 Practice Questions

Save this lesson to see every answer and practice every set.

Question 1 of 10

A quadratic sorting method takes 3 seconds for 12,000 items. About how long should 36,000 items take under comparable conditions?
  1. 27 seconds
  2. 9 seconds
  3. 18 seconds
  4. 81 seconds
Show answerHide answer

Save this lesson to see this answer.

Question 2 of 10

A sort receives [4, 4, -2] and returns [-2, 4]. What requirement did it violate?
  1. Ascending key order
  2. Preserving every occurrence
  3. Returning the smallest first
  4. Placing negative values first
Show answerHide answer

Save this lesson to see this answer.

Question 3 of 10

Early-stop bubble sort uses shorter passes on [1, 3, 2, 4]. How many comparisons does it make before stopping?
  1. 3
  2. 4
  3. 5
  4. 6
Show answerHide answer

Save this lesson to see this answer.

Question 4 of 10

Standard selection sort receives 6 items that are already sorted. How many item comparisons does it make?
  1. 15
  2. 5
  3. 0
  4. 30
Show answerHide answer

Save this lesson to see this answer.

Question 5 of 10

A merge of [1, 8, 13] and [4, 9] counts only comparisons between fronts. How many does it make?
  1. 2
  2. 3
  3. 4
  4. 5
Show answerHide answer

Save this lesson to see this answer.

Question 6 of 10

A linear search reports an absent target in an unsorted list of 9 items. How many item-target comparisons does it make?
  1. 0
  2. 1
  3. 8
  4. 9
Show answerHide answer

Save this lesson to see this answer.

Question 7 of 10

A loop runs n times. On each repetition a second loop runs exactly 4 times. How many times does the inner body execute?
  1. n+4
  2. n2
  3. 4n
  4. 4n
Show answerHide answer

Save this lesson to see this answer.

Question 8 of 10

Binary search uses inclusive bounds and a rounded-down midpoint. In [6, 12, 18, 25, 33, 46], what are the bounds immediately after the first check for target 30?
  1. low 2, high 5
  2. low 3, high 5
  3. low 0, high 1
  4. low 0, high 2
Show answerHide answer

Save this lesson to see this answer.

Question 9 of 10

For an unchanged unsorted collection, use these cost estimates: sorting costs 9,000 units, each binary query 12 units, and each linear query 600 units. Which plan costs less for 20 queries, and by how much?
  1. Linear queries, by 2,760 units
  2. Linear queries, by 3,000 units
  3. Sort then binary, by 3,000 units
  4. Sort then binary, by 2,760 units
Show answerHide answer

Save this lesson to see this answer.

Question 10 of 10

A task can use only fixed extra working storage and must minimize swaps. Among the taught sorting methods, which choice best meets those priorities?
  1. Merge sort, with left-first ties
  2. Selection sort, with one swap per pass
  3. Bubble sort, with an early-stop flag
  4. Merge sort, with a single final merge
Show answerHide answer

Save this lesson to see this answer.

Set 3: Explain boundaries and compare trade-offs10 Practice Questions

Save this lesson to see every answer and practice every set.

Question 1 of 10

A stable merge combines left records [2A, 6B] and right records [2C, 5D], comparing only numeric keys. What is the merged record order?
  1. 2C, 2A, 5D, 6B
  2. 2A, 5D, 2C, 6B
  3. 2A, 2C, 5D, 6B
  4. 2A, 2C, 6B, 5D
Show answerHide answer

Save this lesson to see this answer.

Question 2 of 10

Binary search uses inclusive bounds and rounded-down midpoints on [5, 11, 16, 24, 38, 47, 59, 70]. What indexes are checked for the absent target 41?
  1. 3, 5, 4
  2. 3, 4, 5
  3. 4, 6, 5
  4. 3, 5, 6
Show answerHide answer

Save this lesson to see this answer.

Question 3 of 10

Records arrive as Eli 72, Jo 65, Kai 72, Luz 65. What order results from a stable ascending score sort?
  1. Luz, Jo, Eli, Kai
  2. Jo, Luz, Kai, Eli
  3. Jo, Luz, Eli, Kai
  4. Eli, Kai, Jo, Luz
Show answerHide answer

Save this lesson to see this answer.

Question 4 of 10

What is the list after the second selection-sort pass on [18, 7, 12, 4, 9]?
  1. [4, 7, 9, 12, 18]
  2. [7, 4, 12, 18, 9]
  3. [4, 9, 12, 18, 7]
  4. [4, 7, 12, 18, 9]
Show answerHide answer

Save this lesson to see this answer.

Question 5 of 10

Merge sort splits a 32-item list into halves down to singletons. How many merge levels rebuild it?
  1. 4
  2. 5
  3. 6
  4. 16
Show answerHide answer

Save this lesson to see this answer.

Question 6 of 10

Method P uses 60n steps and method Q uses n2. For positive input sizes, when does P use fewer steps?
  1. When n>60
  2. When n<60
  3. When n≥60
  4. When n≤60
Show answerHide answer

Save this lesson to see this answer.

Question 7 of 10

A quadratic process takes 5 seconds for 8,000 items. A 45-second budget allows about how many items under the same model?
  1. 24,000 items after scaling by 9
  2. 72,000 items after scaling by 9
  3. 16,000 items after scaling by 2
  4. 24,000 items after scaling by 3
Show answerHide answer

Save this lesson to see this answer.

Question 8 of 10

An inclusive binary search preserves every possible match inside its range, but after a low-valued midpoint it sets low = mid. What part of a full correctness argument can fail?
  1. The input-order precondition
  2. The guarantee that execution stops
  3. The equality test on a match
  4. The preservation of original items
Show answerHide answer

Save this lesson to see this answer.

Question 9 of 10

A list is already ordered by arrival time. It must now be ordered by priority while retaining arrival order within equal priorities. What sorting property is needed?
  1. Fixed extra storage
  2. Few moving swaps
  3. Stability across equal keys
  4. A quadratic comparison bound
Show answerHide answer

Save this lesson to see this answer.

Question 10 of 10

A complete bubble-sort pass makes no swaps. What does that establish about the whole list, including any settled suffix?
  1. It is sorted.
  2. Its values are distinct.
  3. It has one item.
  4. Its keys are equal.
Show answerHide answer

Save this lesson to see this answer.

Set 4: Analyze traces and justify decisions10 Practice Questions

Save this lesson to see every answer and practice every set.

Question 1 of 10

A standard linear search performs 4 comparisons on [14, 6, 21, 6]. If it stops immediately on the first match, which target-result pair is possible?
  1. Target 6, returned 3
  2. Target 21, returned 2
  3. Target 8, returned -1
  4. Target 14, returned 0
Show answerHide answer

Save this lesson to see this answer.

Question 2 of 10

An inclusive binary search has bounds low = 6, high = 6, and the remaining item equals the target. What does the correct loop do?
  1. Checks index 6 and returns 6
  2. Returns -1 without checking
  3. Moves low to 7 before checking
  4. Checks index 5 and returns 5
Show answerHide answer

Save this lesson to see this answer.

Question 3 of 10

How many inversions are in [5, 3, 5, 2, 4, 1]?
  1. 10
  2. 11
  3. 12
  4. 15
Show answerHide answer

Save this lesson to see this answer.

Question 4 of 10

Selection sort uses strict < when finding each minimum. What is the final record order for [7A, 3X, 7B, 2Y, 3Z], comparing only numeric keys?
  1. 2Y, 3Z, 3X, 7A, 7B
  2. 2Y, 3X, 3Z, 7B, 7A
  3. 2Y, 3Z, 3X, 7B, 7A
  4. 2Y, 3X, 3Z, 7A, 7B
Show answerHide answer

Save this lesson to see this answer.

Question 5 of 10

Use merge sort with rounded-down half splits on [9, 1, 7, 3]. Counting only item comparisons in merges, how many does the full sort make?
  1. 2
  2. 3
  3. 4
  4. 5
Show answerHide answer

Save this lesson to see this answer.

Question 6 of 10

An algorithm first compares every distinct pair once, then scans all n items once, counting one comparison per scanned item. What total comparison formula applies?
  1. n(n−1)/2
  2. n(n+1)/2
  3. n2+n
  4. n(n−1)
Show answerHide answer

Save this lesson to see this answer.

Question 7 of 10

A process tries every selection of n independent items, for 2n selections. What happens to this count when one more item is added?
  1. It rises by one.
  2. It is squared.
  3. It doubles.
  4. It quadruples.
Show answerHide answer

Save this lesson to see this answer.

Question 8 of 10

Repeated runs on doubling input sizes take about 0.06, 0.24, and 0.96 seconds. What growth pattern do these ratios support?
  1. Quadratic growth
  2. Linear growth
  3. Constant growth
  4. Logarithmic growth
Show answerHide answer

Save this lesson to see this answer.

Question 9 of 10

A proof shows a sorting loop stops with every neighboring pair in order. Its code also overwrites some entries without saving them. What additional property must be established before calling the sort correct?
  1. Every pass uses fewer comparisons.
  2. Every original item keeps its count.
  3. Every equal key keeps its old position.
  4. Every run uses fixed extra memory.
Show answerHide answer

Save this lesson to see this answer.

Question 10 of 10

Random testing finds a sort failure on a long list. What next step best helps identify the bug and guard against its return?
  1. Keep the full list and repeatedly trace every position.
  2. Replace the input with a typical list and keep its result.
  3. Generate more random lists before examining the first failure.
  4. Shrink the failing input and keep it as a regression test.
Show answerHide answer

Save this lesson to see this answer.

Stuck on your own question? Homework Help

Key Terms

62 Key Terms, each with its definition

A

Adjacent items
Adjacent items occupy neighboring list positions, so their indexes differ by exactly one when both positions exist.
Bubble sort compares items[j] with items[j + 1], making its movement depend on neighboring pairs.
Average case
The average case describes mean work under a specified distribution of inputs, so its value depends on which inputs occur.
Equal probabilities for successful target positions give linear search an average of (n+1)/2 comparisons.

B

Base case
A base case is an input handled directly without another recursive call, providing a stopping point for the recursion.
For merge sort, both an empty list and a one-item list return unchanged because they are already sorted.
Basic operation
A basic operation is the repeated action chosen for counting work, such as comparing two keys or checking a middle item.
A binary middle check can perform two item comparisons, so its check count needs a separate label.
Best case
The best case is the valid input situation requiring the fewest counted operations among inputs of the same size.
A linear search that finds its target at the first position makes one comparison on a nonempty list.
Binary search
A binary search uses sorted order to narrow an inclusive candidate range by comparing the target with a middle item.
Searching a sorted array of about a million items needs at most 20 middle-item checks.

C

Candidate range
A candidate range is the interval of positions that can still contain a target after earlier comparisons have ruled out the rest.
Bounds 4 through 7 include four candidate positions, and bounds 5 through 4 describe an empty range.
Constant growth
Constant growth means the counted work stays bounded independently of input size, as with reading one known array position.
Looking up one specified index does not require scanning every earlier array item as the list grows.
Counterexample
A counterexample is a valid input whose observed behavior violates a claimed general property, disproving that claim.
One sorted two-item list that causes an endless search disproves a claim that the search always terminates.
Crossover
A crossover is an input size where two cost models tie and beyond which their ordering can change as input grows.
The models 40n and n2 tie at 40 items; the linear model becomes cheaper at larger positive sizes.

D

Direct access
Direct access means reaching an item by its index without walking through earlier items, as an array or Python list allows.
Following links to a linked list’s middle takes extra traversal, weakening binary search’s advantage.
Divide and conquer
Divide and conquer solves a problem by splitting it into smaller problems, solving them, and combining their results.
Merge sort combines sorted half-results; the splitting alone leaves the original order of values unchanged.
Dominant term
A dominant term is the fastest-growing part of a step-count expression, controlling its long-run growth as input size rises.
In n2/2−n/2, the quadratic term dominates even though both terms affect an exact comparison count.
Doubling test
A doubling test compares costs at successive doubled input sizes to look for the ratios associated with growth families.
Repeated timing ratios near four suggest quadratic growth across the measured range of sizes.

E

Early stop
An early stop ends an algorithm before its maximum planned work once a checked condition establishes the required result.
Bubble sort’s no-swap pass confirms order; the comparisons on that pass still count toward the total.
Edge case
An edge case is a valid input at a boundary of an algorithm’s behavior, where loop limits or special handling can fail.
An empty list checks whether binary search tests its bounds before trying to access a middle item.
Exponential growth
Exponential growth multiplies work by a fixed factor for each extra item; a 2n count doubles when one item is added.
Trying every selection from 30 items requires twice as many selections as trying every selection from 29.

F

Front item
A front item in a merge is the first unused item of one sorted input, and therefore its smallest remaining item.
After taking the left front, advance only the left marker; the right front still awaits comparison.
Full scan
A full scan inspects every item in a collection, as needed when counting all matches or finding the largest value.
Counting every occurrence of 7 continues after the first match because later items can also contain 7.

G

Growth rate
A growth rate describes how an algorithm’s work changes with input size, separating its scaling pattern from a single run.
A low time at one small size leaves open how quickly that time rises when the data becomes much larger.

I

In-place sort
An in-place sort rearranges the existing list using only a fixed amount of extra working storage as input size grows.
Bubble sort needs a few counters and a swap flag instead of another list proportional to its input.
Index
An index is a numbered position used to access a list item; Python indexes start at zero and end one before the list length.
In [8, 13, 21], index 2 holds 21, even though it is the third item when counting in everyday language.
Input size
The input size measures how much data an algorithm receives; for the list algorithms here, it is the number of items, n.
Two lists with the same length have the same input size even if one is sorted and the other is reversed.
Integer overflow
Integer overflow occurs when arithmetic exceeds a fixed-width integer range; the effect depends on the language and operation.
A difference-based midpoint avoids an oversized endpoint sum when indexes are nonnegative and valid.
Inversion
An inversion is a pair of positions where an earlier item has a larger key than a later item; equal keys contribute none.
In [8, 3, 6], 8 forms an inversion with both later values, even though only one pair is adjacent.

L

Library sort
A library sort is a language or library’s tested sorting implementation, with documented behavior such as stability and output form.
Python’s sorted creates a new result list, while list.sort() rearranges the existing list.
Linear growth
Linear growth means work rises in direct proportion to input size, so doubling the number of items roughly doubles the work.
A full scan with one key comparison per item makes 2,000 comparisons when its input grows to 2,000 items.
Linear search
A linear search checks items sequentially from the start, stopping at the first match or reporting absence after the full list.
Finding the last item in a nine-item list by linear search requires nine item-target comparisons.
Logarithmic growth
Logarithmic growth occurs when repeated reduction by a fixed factor makes each input doubling add only a fixed amount of work.
Repeated halving gives binary search about one extra middle check each time the list size doubles.
Loop invariant
A loop invariant is a statement true before the loop starts and preserved after each repetition, supporting a result argument.
Selection sort’s invariant links the length of its prefix to the smallest items that have already been placed.

M

Marker
A marker tracks the next unused position in an input list so a merge knows which item to compare or copy next.
Separate left and right markers prevent a merge from copying an item twice or skipping one after a comparison.
Merge
A merge combines two sorted lists by repeatedly copying the smaller unused front item, then copying any remaining tail.
Merging [3, 10] and [6, 12] produces [3, 6, 10, 12] without re-sorting either input.
Merge sort
Merge sort recursively sorts two shorter halves and merges the sorted results, giving work growth proportional to nlog⁡n.
A large list passes through several split sizes before its sorted pieces are rebuilt into one output.
Midpoint
A midpoint is the middle position chosen within a search range, rounded down when the endpoints do not average to an integer.
For inclusive bounds 2 and 7, (2 + 7) // 2 selects index 4 as the next midpoint.

O

Off-by-one error
An off-by-one error makes a loop or index boundary include one position too many or too few, causing a miss or invalid access.
Using low < high for an inclusive binary range skips a final candidate when the endpoints become equal.

P

Pass
A pass is one sweep through the active part of a list, performing a sequence of comparisons or placements.
A bubble-sort pass settles the largest remaining item but can leave the earlier positions unsorted.
Permutation
A permutation is a rearrangement of the same items, preserving the number of occurrences of every value.
[2, 8, 2] and [2, 2, 8] are permutations of each other; [2, 8] loses one occurrence.
Postcondition
A postcondition states what must be true of an algorithm’s result when it finishes on an input meeting its precondition.
A sorting postcondition includes both ordered keys and preservation of the original occurrences.
Precondition
A precondition is a requirement that must hold for an input before an algorithm’s promised behavior is guaranteed.
Ascending order is a precondition for the ordinary binary search, so unsorted input invalidates its range reasoning.
Preparation cost
A preparation cost is work needed before the main operation, such as sorting a list so later binary searches are valid.
One query may favor a linear scan because its plan avoids a large one-time sorting cost.

Q

Quadratic growth
Quadratic growth means work grows in proportion to the square of input size, producing about fourfold work on doubling.
Selection sort’s pair-like comparison count grows quadratically even though its swap count grows much more slowly.

R

Random testing
Random testing generates varied inputs and checks results against a specification or trusted method to seek unexpected failures.
Comparing many randomly generated sort outputs with sorted can reveal a case missed by hand-picked tests.
Recursion
Recursion solves a problem through calls to the same procedure on smaller instances, with a stopping rule for simple cases.
Merge sort calls itself on each shorter half until a call receives at most one item.
Recursive case
A recursive case reduces the current problem to smaller instances and uses their results to build the answer.
Merge sort’s recursive case sorts two halves before calling merge on their returned ordered lists.
Regression test
A regression test retains a check for previously correct or repaired behavior so later changes expose a returning failure.
Keep a minimal list that once caused a lost duplicate to verify that future changes preserve all copies.

S

Search
A search checks whether a target occurs in a collection and can report its position or a result meaning that it is absent.
Searching a list of record IDs can return the position of the requested record without changing the list.
Selection sort
Selection sort repeatedly scans the unsorted part for its minimum and swaps that item into the next position of a sorted prefix.
A selection-sort scan remembers the smallest item’s index before performing its one possible moving swap.
Sort key
A sort key is the value or record field used to decide order; the rest of the record stays attached when the item moves.
Sorting city records by population moves each city name together with its population value.
Sorted prefix
A sorted prefix is a beginning portion of a list already in order; selection sort fills it with the smallest items first.
After three selection passes, the first three positions contain the three smallest items in order.
Sorting
Sorting rearranges items into key order while preserving every original item and every repeated occurrence.
An ascending result that loses one duplicate fails sorting correctness even if all neighbors are in order.
Specification
A specification states the valid-input requirements and the result an algorithm must produce, making correctness checkable.
A search specification distinguishes a matching index from a special result used only when no match exists.
Stable sort
A stable sort preserves the original relative order of records whose keys compare equal, even as other records move.
A stable score sort keeps alphabetically ordered names in alphabetical order within each score group.
Swap
A swap exchanges two stored items while preserving both original values, using a saved value or simultaneous assignment.
Without saving a value, two successive assignments can turn [2, 9] into [9, 9] instead of exchanging them.

T

Tail
A tail is the remaining portion of a list from a chosen position onward, copied directly when the other merge input empties.
If one merge input empties while [14, 20] remains on the other side, both tail items are still required.
Target
A target is the value sought by a search, compared with stored items or with a chosen field of each stored record.
In a search for the ID 630, the target is 630; the returned position is a different number.
Termination
Termination means execution eventually stops; a shrinking nonnegative whole-number measure can establish it for a loop.
Excluding the tested midpoint makes binary search’s candidate count strictly decrease after every mismatch.
Time budget
A time budget is the allowed running time, used with a cost model to estimate how large an input can be processed.
A quadratic process can fit three times the input when its available time increases by a factor of nine.
Time prediction
A time prediction estimates running time at another size by applying a growth model to a measured or supplied baseline.
Tripling the input in a quadratic model multiplies the baseline time by about nine, not three.
Timing baseline
A timing baseline is a measured running time at a known input size used as the starting point for a size-based estimate.
A baseline of 4 seconds at 20,000 items can support a quadratic estimate for a larger comparable run.
Trade-off
A trade-off is a choice where improving one useful property can cost another, such as saving comparisons while using more memory.
Merge sort improves large-input time growth but usually needs more temporary storage than selection sort.

U

Unsorted suffix
An unsorted suffix is the remaining end portion whose internal order has not yet been established by the sorting process.
Selection sort searches only the unsorted suffix so it does not select an already placed minimum again.

W

Worst case
The worst case is the valid input situation requiring the most counted operations among inputs of the same size.
An absent target forces basic linear search to inspect every position, giving its worst-case count.

About this lesson

By StudeliReport a mistake

Make your own Study Materials

Turn your notes, slides and documents into Study Guides, Practice Questions, Flashcards and 6 more tools.

Get Started