Skip to article frontmatterSkip to article content

2.1 Insertion Sort

Our first algorithm, insertion sort, solves the sorting problem introduced in Chapter 1:

Input: A sequence of nn numbers a1,a2,,an\langle a_1, a_2, \ldots, a_n \rangle

Output: A permutation (reordering) a1,a2,,an\langle a'_1, a'_2, \ldots, a'_n \rangle of the input sequence such that a1a2ana'_1 \leq a'_2 \leq \cdots \leq a'_n.

The numbers to be sorted are also known as the keys. Although the problem is conceptually about sorting a sequence, the input comes in the form of an array with nn elements. When we want to sort numbers, it’s often because they are the keys associated with other data, which we call satellite data. Together, a key and satellite data form a record. For example, consider a spreadsheet containing student records with many associated pieces of data such as age, grade-point average, and number of courses taken. Any one of these quantities could be a key, but when the spreadsheet sorts, it moves the associated record (the satellite data) with the key. When describing a sorting algorithm, we focus on the keys, but it is important to remember that there usually is associated satellite data.

In this book, we’ll typically describe algorithms as procedures written in a pseudocode that is similar in many respects to C, C++, Java, Python,[1] or JavaScript. (Apologies if we’ve omitted your favorite programming language. We can’t list them all.) If you have been introduced to any of these languages, you should have little trouble understanding algorithms “coded” in pseudocode. What separates pseudocode from real code is that in pseudocode, we employ whatever expressive method is most clear and concise to specify a given algorithm. Sometimes the clearest method is English, so do not be surprised if you come across an English phrase or sentence embedded within a section that looks more like real code. Another difference between pseudocode and real code is that pseudocode often ignores aspects of software engineering -- such as data abstraction, modularity, and error handling -- in order to convey the essence of the algorithm more concisely.

We start with insertion sort, which is an efficient algorithm for sorting a small number of elements. Insertion sort works the way you might sort a hand of playing cards. Start with an empty left hand and the cards in a pile on the table. Pick up the first card in the pile and hold it with your left hand. Then, with your right hand, remove one card at a time from the pile, and insert it into the correct position in your left hand. As Figure 1 illustrates, you find the correct position for a card by comparing it with each of the cards already in your left hand, starting at the right and moving left. As soon as you see a card in your left hand whose value is less than or equal to the card you’re holding in your right hand, insert the card that you’re holding in your right hand just to the right of this card in your left hand. If all the cards in your left hand have values greater than the card in your right hand, then place this card as the leftmost card in your left hand. At all times, the cards held in your left hand are sorted, and these cards were originally the top cards of the pile on the table.

The pseudocode for insertion sort is given as the procedure Insertion-Sort on the facing page. It takes two parameters: an array AA containing the values to be sorted and the number nn of values of sort. The values occupy positions A[1]A[1] through A[n]A[n] of the array, which we denote by A[1:n]A[1:n]. When the Insertion-Sort procedure is finished, array A[1:n]A[1:n] contains the original values, but in sorted order.

Sorting a hand of cards using insertion sort.

Figure 1:Sorting a hand of cards using insertion sort.

1
2
3
4
5
6
7
8
9
INSERTION-SORT(A, n)
  for i = 2 to n
    key = A[i]
    # Insert A[i] into the sorted subarray A[1:i-1]
    j = i - 1
    while j > 0 and A[j] > key
      A[j+1] = A[j]
      j = j - 1
    A[j + 1] = key

Loop invariants and the correctness of insertion sort

The operation of Insertion-Sort(A, n), where A initially contains the sequence \langle 5, 2, 4, 6, 1, 3 \rangle and n = 6. Array indices appear above the rectangles, and values stored in the array positions appear within the rectangles. (a)–(e) The iterations of the for loop of lines 1-8. In each iteration, the blue rectangle holds the key taken from A[i], which is compared with the values in tan rectangles to its left in the test of line 5. Orange arrows show array values moved one position to the right in line 6, and blue arrows indicate where the key moves to in line 8. (f) The final sorted
array.

Figure 2:The operation of Insertion-Sort(A,n)(A, n), where AA initially contains the sequence 5,2,4,6,1,3\langle 5, 2, 4, 6, 1, 3 \rangle and n=6n = 6. Array indices appear above the rectangles, and values stored in the array positions appear within the rectangles. (a)–(e) The iterations of the for loop of lines 1-8. In each iteration, the blue rectangle holds the key taken from A[i]A[i], which is compared with the values in tan rectangles to its left in the test of line 5. Orange arrows show array values moved one position to the right in line 6, and blue arrows indicate where the key moves to in line 8. (f) The final sorted array.

Figure 2 shows how this algorithm works for an array AA that starts out with the sequence 5,2,4,6,1,3\langle 5, 2, 4, 6, 1, 3\rangle. The index ii indicates the “current card” being inserted into the hand. At the beginning of each iteration of the for loop, which is indexed by ii, the subarray (a contiguous portion of the array) consisting of elements A[1:i1]A[1:i-1] (that is, A[1]A[1] through A[i1]A[i-1]) constitutes the currently sorted hand, and the remaining subarray A[i+1:n]A[i+1:n] (elements A[i+1]A[i+1] through A[n]A[n]) corresponds to the pile of cards still on the table. In fact, elements A[1:i1]A[1:i-1] are the elements originally in positions 1 through i1i - 1, but now in sorted order. We state these properties of A[1:i1]A[1:i-1] formally as a loop invariant:

Loop invariants help us understand why an algorithm is correct. When you’re using a loop invariant, you need to show three things:

When the first two properties hold, the loop invariant is true prior to every iteration of the loop. (Of course, you are free to use established facts other than the loop invariant itself to prove that the loop invariant remains true before each iteration.) A loop-invariant proof is a form of mathematical induction, where to prove that a property holds, you prove a base case and an inductive step. Here, showing that the invariant holds before the first iteration corresponds to the base case, and showing that the invariant holds from iteration to iteration corresponds to the inductive step.

The third property is perhaps the most important one, since you are using the loop invariant to show correctness. Typically, you use the loop invariant along with the condition that caused the loop to terminate. Mathematical induction typically applies the inductive step infinitely, but in a loop invariant the “induction” stops when the loop terminates.

Let’s see how these properties hold for insertion sort.

This method of loop invariants is used to show correctness in various places throughout this book.

Pseudocode conventions

We use the following conventions in our pseudocode.

Exercises

2.1-1

Using Figure 2 as a model, illustrate the operation of Insertion-Sort on an array initially containing the sequence 31,41,59,26,41,58\langle 31, 41, 59, 26, 41, 58 \rangle.

2.1-2

Consider the procedure Sum-Array below. It computes the sum of the nn numbers in array A[1:n]A[1:n]. State a loop invariant for this procedure, and use its initialization, maintenance, and termination properties to show that the Sum-Array procedure returns the sum of the numbers in A[1:n]A[1:n].

1
2
3
4
5
SUM-ARRAY(A, n)
  sum = 0
  for i = 1 to n
    sum = sum + A[i]
  return sum

2.1-3

Rewrite the Insertion-Sort procedure to sort into monotonically decreasing instead of monotonically increasing order.

2.1-4

Consider the searching problem:

Write pseudocode for linear search, which scans through the array from beginning to end, looking for xx. Using a loop invariant, prove that your algorithm is correct. Make sure that your loop invariant fulfills the three necessary properties.

2.1-5

Consider the problem of adding two n-bit binary integers aa and bb, stored in two nn-element arrays A[0:n1]A[0:n-1] and B[0:n1]B[0:n-1], where each element is either 0 or 1, a=i=0n1A[i]2ia = \sum_{i=0}^{n-1}A[i]\cdot 2^i, and b=i=0n1B[i]2ib = \sum_{i=0}^{n-1}B[i]\cdot 2^i.

The sum c=a+bc = a + b of the two integers should be stored in binary form in an (n+1)(n + 1)-element array C[0:n]C[0:n], where c=i=0nC[i]2ic = \sum_{i=0}^{n}C[i] \cdot 2^i.

Write a procedure Add-Binary-Integers that takes as input arrays AA and BB, along with the length nn, and returns array CC holding the sum.

Footnotes
  1. If you’re familiar with only Python, you can think of arrays as similar to Python lists.

  2. When the loop is a for loop, the loop-invariant check just prior to the first iteration occurs immediately after the initial assignment to the loop-counter variable and just before the first test in the loop header. In the case of Insertion-Sort, this time is after assigning 2 to the variable ii but before the first test of whether ini \leq n.

  3. In an if-else statement, we indent else at the same level as its matching if. The first executable line of an else clause appears on the same line as the keyword else. For multiway tests, we use elseif for tests after the first one. When it is the first line in an else clause, an if statement appears on the line following else so that you do not misconstrue it as elseif.

  4. Each pseudocode procedure in this book appears on one page so that you do not need to discern levels of indentation in pseudocode that is split across pages.

  5. Most block-structured languages have equivalent constructs, though the exact syntax may differ. Python lacks repeat-until loops, and its for loops operate differently from the for loops in this book. Think of the pseudocode line “for i=1i = 1 to nn” as equivalent to “for i in range(1, n+1)” in Python.

  6. In Python, the loop counter retains its value after the loop is exited, but the value it retains is the value it had during the final iteration of the for loop, rather than the value that exceeded the loop bound. That is because a Python for loop iterates through a list, which may contain nonnumeric values.

  7. If you’re used to programming in Python, bear in mind that in this book, the subarray A[i:j]A[i:j] includes the element A[j]A[j]. In Python, the last element of A[i:j]A[i:j] is A[j1]A[j - 1]. Python allows negative indices, which count from the back end of the list. This book does not use negative array indices.

  8. Python’s tuple notation allows return statements to return multiple values without creating objects from a programmer-deûned class.