Chapter 2.0 Introduction To Algorithm 4th Edition

Download as docx, pdf, or txt
Download as docx, pdf, or txt
You are on page 1of 4

Giovanni Israel Alvarez Osorio

Chapter 2. Getting started


This chapter will familiarize you with the framework we’ll use throughout the book to think about design
and analysis of algorithms. It is self-contained, but it does include several references to material that will
be introduced in Chapter 3 and 4.
We’ll begin by examining the insertion sort algorithm to solve the sorting problem introduced in Chapter
1. We’ll specify algorithms using a pseudocode that should be understandable to you if you have done
computer programing. We’ll see why insertion sort correctly sorts and analyze its running time. The
analysis introduces a notation that describes how running time increases with the number of items to be
sorted. Following a discussion of insertion sort, we’ll use a method called divide-and-conquer to develop
a sorting algorithm called merge sort. We’ll end with an analysis of merge sort’s running time.
2.1 Insertion sort
Our first algorithm, insertion sort, solves the sorting problem introduced in Chapter 1:

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 n 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 course 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, Phyton, or JavaScript. (Apologies if we’ll 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 and English phrase or sentences 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.
Figure 2.1 Sorting a hand of cards using insertion sort.

Loop invariants and the correctness of insertion sort


Figure 2.2 shows how this algorithm works for an array A that starts out with the sequence (5, 2, 4, 6,
1,3).

Figure 2.2 The operation of INSERTION-SORT (A,n).


Pseudocode conventions
We use the following conventions in our pseudocode.

 Indentation indicates block structure. For example, the body of the for loop that begins on line 1
consists of line 2-8, and the body of the while loop that begins on line 5 contains lines 6-7 but not
line 8. Our indentation style applies to if-else statements as well. Using indentation instead of
textual indicators of block, such as begin and end statements or curly braces, reduces clutter while
preserving, or even enhancing, clarity.
 The symbol “//” indicates that the remainder of the line is a comment
 Variables (such as i, j, and key) are local to the given procedure. We won’t use global variables
without explicit indication.
 We access array elements by specifying the array name followed by the index in square brackets.
For example, A[i] indicates the i th element of the array A.

2.2 Analyzing algorithms


Analyzing an algorithm has come to mean predicting the resources that the algorithm requires. You might
consider resources such as memory, communication bandwidth, or energy consumption. Most often,
however, you’ll want to measure computational time. If you analyze several candidate algorithms for a
problem, you can identify the most efficient one. There might be more than just one viable candidate, but
you can often rule out several inferior algorithms in the process.
Before you can analyze an algorithm, you need a model of the technology that it runs on, including the
resources of that technology and a way to express their costs. Most of this book assumes a generic one-
processor, Random-Access Machine (RAM) model of computation as the implementation technology,
with the understanding that algorithms are implemented as computer programs. In the RAM model,
instructions execute one after another, with no concurrent operations. The RAM model assumes that each
instruction takes the same amount of time as any other instruction and that each data access-using the
value of a variable or storing into a variable-takes the same amount of time as any other data access. In
other words, in the RAM model each instruction or data access takes a constant amount of time-even
indexing into an array.
Strictly speaking, we should precisely define the instructions of the RAM model and their costs. To do so,
however, would be tedious and yield little insight into algorithm design and analysis. Yet we must be
careful not to abuse the RAM model.
Analysis of insertion sort
How long does the INSERTION-SORT procedure take? One way to tell would be for you to run it on
your computer and time how long it takes to run. Of course, you’d first have to implement it in a real
programming language, since you cannot run our pseudocode directly. What would such a timing test tell
you? You would find out how long insertion sort takes to run on your particular computer, on that
particular input, under the particular implementation that you created, with the particular compiler or
interpreter that you ran, with the particular libraries that you linked in, and with the particular background
tasks that were running on your computer concurrently with your timing test (such as checking for
incoming information over a network)
Worst-case and average -case analysis
Our analysis of insertion sort locked at both the best case, in which the input array was already sorted, and
the worst case, in which the input array was reverse sorted. For the remainder of this book, though, we’ll
usually (but not always) concentrate on finding only the worst-case running time, that is, the longest
running time for any input of size n. why? Here are three reasons:

 The worst-case running time of an algorithm gives an upper bound on the running time for any
input. If you know it, then you have a guarantee that the algorithm never takes any longer. You
need not make some educated guess about the running time and hope that it never gets much
worse. This feature is especially important for real-time computing, in which operations must
complete by deadline.
 For some algorithms, the worst case occurs fairly often. For example, in searching a date base for
a particular piece of information, the searching algorithm’s worst case often occurs when the
information is not present in the database. In some applications, searches for absent information
may be frequent.
 The “average case” is often roughly as bad as the worst case.
Order of growth

2.3 Designing algorithms


You can choose from a wide range of algorithm design techniques. Insertion sort uses the incremental
method: for each element A[i], insert it into its proper place in the subarray A[1: i], having already sorted
the subarray A[1:i-1].
This section examines another design method, known as “divide and conquer” which we explore in more
detail in Chapter 4. We’ll use divide and conquer to design a sorting algorithm whose worst-case running
time is much less than that of insertion sort. One advantage of using an algorithm that follows the divide
and conquer method is that analyzing its running time is often straightforward, using techniques that we’ll
explore in Chapter 4.
2.3.1 The divide-and-conquer method
Many useful algorithms are recursive in structure: to solve a given problem, they recurse (call
themselves) one or more times to handle closely related subproblems. These algorithms typically follow
the divide-and-conquer method: they break the problem into several subproblems that are similar to the
original problem but smaller in size, solve the subproblems recursively, and then combine these solutions
to create a solution to the original problem.
In the divide-and-conquer method, if the problem is small enough -the base case- you just solve it directly
without recursing. Otherwise -the recursive case- you perform three characteristic steps:
Divide the problem into one or more subproblems that are smaller instances of the same problem.
Conquer the subproblems by solving them recursively.
Combine the subproblem solutions to form a solution to the original problem.
The merge sort algorithm closely follows the divide-and-conquer method. In each step, it sorts a subarray
A[p:r], starting with the entire array A[1:n] and recursing down to smaller and smaller subarrays. Here is
how merge sort operates:

(PAGE 34)

You might also like