Introduction To Algorithms and Pseudo Code
Introduction To Algorithms and Pseudo Code
net/publication/309410533
CITATIONS READS
6 44,954
1 author:
Nicholas Bennett
TLG Learning
12 PUBLICATIONS 6 CITATIONS
SEE PROFILE
Some of the authors of this publication are also working on these related projects:
All content following this page was uploaded by Nicholas Bennett on 19 May 2021.
Nicholas Bennett
[email protected]
May 2021
Copyright and license
© 2021, Nicholas Bennett. This document is licensed under a Creative Commons Attribution-
Noncommercial-Share Alike 4.0 International License.
• All of the above expressed in symbolic terms, so that the algorithm can be applied to a
any specific instance of a general class of problems.
The steps in an algorithm should be sufficient to go from the initial conditions to the intended
goal, or to a condition in which it's clear that the algorithm won't produce the desired result. This
latter outcome doesn't necessarily mean that the algorithm doesn't work; however, if the rules of
the algorithm don't properly identify such a condition, then the algorithm is incomplete.
Similarly, when an algorithm doesn't solve a problem to which it is applied, that doesn't
necessarily mean the problem can't be solved—only that it can't be solved with that algorithm.
To be effective, it's important that an algorithm is unambiguous, at least in its critical elements.
For example, we probably wouldn't place much confidence in an algorithm that included the
instruction: "Step 3: Subtract 18, or sometimes 43, from the total."
For example, the following formula describes the relationship between temperatures in the
Celsius and Fahrenheit scales:
5( F −32) = 9C (1)
where
The formula defines the relationship between temperatures in Celsius and Fahrenheit, but it
doesn't give us an explicit algorithm for converting from one to the other. Fortunately, if we have
some understanding of algebra, we can easily write such an algorithm.
First, we can use the basic operations of algebra to convert the previous formula into this form:
5( F – 32)
C= (2)
9
Working from this formula, it's a relatively straightforward task to write an algorithm to convert
from Fahrenheit to Celsius:
Note that we've shifted from describing the relationship between the two scales to listing
computational steps in a specific order. Also, note that the process required to convert (1) into (2)
is itself an algorithm—one that you learned in first-year algebra—for isolating a variable in a
linear equation.
In many respects, computers are electronic idiot savants: they can perform amazing feats of
calculation and memory, but without our help they're almost totally incompetent when it comes
to applying those abilities to practical problems. Do you want to compute the sine of an angle?
How about computing the natural logarithm of a number? These tasks are easy for a computer—
in fact, in most modern computers, these operations are built in to the CPU itself. But if you want
to plot the graph of y = sin x on the screen, or balance your checkbook, or compute the area of
the region bounded by 1 ≤ x ≤ 100 and 0 ≤ y ≤ 1/ x , the computer is helpless—until someone
writes algorithms to accomplish these tasks, and “teaches” them to the computer.
Fortunately for us, many such algorithms have already been written, in languages the computer
can understand. When we write a line of Python or Java code, when we compose a formula for
the cell of an Excel spreadsheet, even when we snap a collection of Scratch blocks together,
we're using the procedures others have written as building blocks for the algorithms we create.
Pseudocode is a very useful device for specifying the logic of a computer program (or some
critical portion of a program) prior to that program actually being written, as well as for
documenting the logic of a computer program after the fact. It can be used to express the high-
level logic of a traditional program, the lower-level details of a core function in an operating
system or run-time library, the behaviors and methods in an agent-based or object-oriented
program, and everything in between. But as useful as pseudocode is, there's a catch: unlike actual
programming languages, and unlike natural languages, there's no standard vocabulary or
grammar for pseudocode. Pseudocode can be expressed in virtually any written language in
existence. It can look very much like standard (albeit very formal) prose; at the other end of the
spectrum, it can appear so close to programming code that on first glance, we might think that's
exactly what it is.
So what is pseudocode? One way to describe it is easy, but arguably not very useful: in practice,
pseudocode is simply a very precise, minimally ambiguous articulation of an algorithm—but
even more precise, and less ambiguous, than usual.
Another clue comes from the world of mathematics: pseudocode often employs algebraic
variable naming and expressions, as well as notation from set theory, linear algebra, and other
branches of mathematics. The use of these mathematical conventions can go a long way toward
eliminating, or at least reducing, ambiguity in the description of an algorithm.
Ultimately, the most important characteristic of pseudocode is not really what it is, but what it
makes possible. As noted above, when we start with well-written pseudocode, virtually any
programmer with reasonable competence in a given programming language should be able to
implement the algorithm described by the pseudocode, in the given language, with little or no
need for further instruction.
Writing pseudocode
Given the fact that there isn't a standard pseudocode language, we're mostly left to our own
devices to come up with a suitable grammar and vocabulary for the pseudocode we write (unless,
of course, we happen to be working in or for an organization which has well-established
standards or conventions for pseudocode). But others have gone before us, and we can learn
from them.
Fortunately, this isn't a difficult problem to solve—but it's also easy to solve it incorrectly [1],
[2]. There are two widely used, equally effective approaches to shuffling in computer programs:
sorting on a random value, and the Fisher‐Yates shuffle [3], [4]. The first is often used to shuffle
rows of a spreadsheet, or records in a database; however, it is less efficient than the Fisher‐Yates
shuffle, which is the one we’ll explore here.
The algorithm is described in fairly unambiguous terms, which makes for a good start. It’s also
adaptable to use with lists of practically any length, by using different values of N. However, it
probably wouldn't be considered pseudocode—not yet, anyway. For one thing, we know that
pseudocode shouldn't be tied to a specific programming language; similarly, it shouldn’t be tied
to a pencil-and-paper implementation, as the preceding description is. Also, while we have some
steps that are iterated as long as some condition holds, that structure isn’t reflected visually.
1. A uniformly distributed random integer is one sampled from a range of integers, where the sampling process is
such that each possible value has the same likelihood of occuring.
Note that the indentation (along with the sub-list numbering scheme) indicates that steps 2a and
2b will be repeated for each value i , starting with (n − 1) and counting down to 1, inclusive;
when the countdown is finished, the algorithm moves to step 3.
Which of the two forms did you find easiest to understand? Which do you think would be easiest
to explain to someone else? Which would be the clearest to work from while implementing the
algorithm in a programming language.
A prime number is a positive integer which has exactly two distinct positive integral divisors:
itself and 1. (By this definition, it's clear that 1 is not a prime number, since it has only one
positive integral divisor: itself.) We can prove that there's no limit to the number of primes, and
no largest prime, but prime numbers slowly become more sparse as they increase in value. For
example, there are 168 prime numbers between 1 and 1,000, but only 106 primes between
10,001 and 11,000, and only 81 between 100,001 and 101,000.
The Sieve of Eratosthenes is a simple and effective algorithm for identifying the primes in a
range of numbers [6]. It's based on the fact that once we find a prime number, we've also found
an infinite number of composite (non-prime) numbers—namely, all of the integral multiples of
the prime that are greater than the prime itself. So, to find all of the primes between 2 and some
upper limit, we simply remove all of the non-primes in that range, in a systematic fashion:
1. Write down all of the positive integers from 2 to the upper limit of the given range of
numbers, in order.
2. Starting with the number 2, and proceeding in order to the largest integer less than or
equal to the square root of the upper limit2, do the following with each number:
• If the current number is not crossed-out:
◦ For all integral multiples of the current number, starting with its square, but not
exceeding the upper limit:
▪ Cross the multiple off the list.
3. Every number in the list that isn't crossed-out is prime.
Algorithm 3: Pencil-and-paper form of the Sieve of Eratosthenes
2. Any composite number can be expressed as the product of at least one pair of integer factors, both of which are
greater than 1; one of the two factors in every such pair will always be less than or equal to the square root of the
composite number. Thus, we need only eliminate the multiples of prime numbers less than or equal to the square
root of the upper limit. Conversely, when we move to a new number, the lowest multiple that we need to cross out
is the square of that number.
1. Let u = upper limit of the range of numbers in which we will look for primes.
2. Let L = ordered list of numbers, initially containing the set of values {2 , 3 , 4 , … , u}.
3. Let p = 2 (the smallest value in L).
4. While p ≤ √ u :
2
a. Let m = p .
b. While m ≤ u :
i. If m is in L:
• Remove m from L.
ii. Let m = m+ p
c. Let p =the smallest value in L that is larger than the current value of p .
5. Done: The values remaining in L are the prime numbers between 2 and u , inclusive.
Algorithm 4: Sieve of Eratosthenes, mix of natural language and mathematical expressions
Note that the description of the algorithm no longer includes any details on the mechanics of
setting up or updating the list of numbers (i.e. it no longer says things like “write down all of the
positive integers …”, or “cross the multiple off the list”). But this change is a good thing: it gives
the developer flexibility in implementing those details, while still specifying the important
aspects of the algorithm unambiguously.
Elements deals primarily with geometry, but Euclid also addresses number theory in the book. In
fact, this example was initially expressed in geometric terms by Euclid, but his solution is an
important development in number theory.
Consider two line segments, of different lengths. Can we construct a third line segment, of such a
length that this third line segment will measure the other two evenly (i.e. the third will fit into
each of the other two an integral number of times, with no portion left over)? How can we find
the largest such line segment?
Another way of expressing the problem is probably more familiar to you: Given two numbers,
what's their greatest common divisor? However we state it, this is the problem solved by Euclid's
algorithm [8].
The algorithm can be applied to many different kinds of numbers and algebraic quantities,
including integers, rational numbers, real numbers, polynomials, etc. However, the most
common application is to positive integers; the pseudocode shown here assumes that's what we're
dealing with.
In this algorithm, we'll use a few symbols you might not have seen before:
∈ “Is in”, “is an element of”, or “is a member of”. For example, a ∈ B indicates that a
is a member of the set B .
ℕ The set of natural or counting numbers; the set of positive integers: {1 , 2 , 3 , …}.
← “Gets”, or “is assigned the value”. For example, a ← b means that the value of b is
assigned to a (we could also state this as “let a = b ”, as we did in the pseudocode for
the Sieve of Eratosthenes).3 A more interesting example is x ← (10 y + z ), which
means that the value of y should be multiplied by 10 and added to the value of z , and
the result then assigned to x .
3. The most commonly used symbol for the assignment operator in pseudocode is =, which is also the assignment
operator used in most programming languages. However, because = is also frequently used for equality testing in
pseudocode (as well as some programming languages), this can sometimes lead to confusion. Another alternative
to = and ← is :=, which is used for assignment in programming languages derived from Pascal (Pascal, Modula-
2, Oberon, Delphi, etc.).
Otherwise:
▪ b ' ← (b ' – a ' )
Note that the indentation is again significant. For example, the lines underneath and indented to
the right of “While (b ' ≠ 0)” should be repeated until the condition (b ' ≠ 0) is no longer true.
Also, notice that this example doesn't use any numbering of the steps. Without such numbering,
we'll assume that the algorithm proceeds from the first to the last step in order, except as
modified by conditional or iterative execution. In this case, we can see that the third top-level
step is an iterative while statement, and its dependent steps consist of an if-then-else statement.
Thus, when we get to that step, we'll repeat the if-then-else statement until the condition for
iteration with while is no longer true; then we'll move on to the fourth top-level step.
An experienced programmer (or mathematician) will probably recognize that the repeated
subtractions in Euclid's algorithm can be expressed more concisely using the modulo operation4
—with mathematically and logically equivalent results [9]. (This change also makes the
computation more efficient on most CPUs.) When writing or reading pseudocode, we shouldn't
assume that code based on the pseudocode must follow it exactly, or that pseudocode written
after-the-fact must follow the code exactly. Neither type of translation is a trivial or mechanical
process, but one that demands the application of experience, judgment, and creativity.
4. Simply put, the modulo operation, written as a mod b, is the remainder produced when a is divided by b. When
both a and b are positive, the result of the modulo operation is equal to the smallest non-negative value resulting
from zero or more subtractions of b from a.
In an undirected graph, a tree is a set of edges, connecting a subset of the nodes, so that there are
no cycles—i.e. for any two nodes connected by edges in the tree set, there's only one path
connecting those nodes that uses the edges in the tree set. A spanning tree is a tree which
connects all of the nodes in an undirected graph; any connected graph contains at least one
spanning tree. Finally, a minimum spanning tree (MST) is a spanning tree of a weighted,
connected, undirected graph, which minimizes the total weight of the edges in the tree. (There
may be more than one spanning tree with the same minimum total weight in a given graph.)
The problem of finding the MST is an example of a combinatorial optimization problem. In such
problems, the solution space consists of all the different possible combinations of decision
variables; the solution task consists of finding the combination that satisfies the problem
constraints, while minimizing or maximizing the value of some objective function. Many
combinatorial optimization problems are very difficult to solve, since the number of possible
solutions tends to grows much faster than the number of inputs. For example, the number of
spanning trees in a fully connected, undirected graph (i.e. one in which there is a direct
n−2
connection between every pair of nodes) with n nodes is n . A fully connected graph with 2
0 3
nodes has 2 = 1 spanning tree; one with 5 nodes has 5 = 125 spanning trees; one with 15 nodes
13
has 15 = 1,946,195,068,359,375 spanning trees. Imagine using brute force to find the MST for
a network with a few hundred nodes!
Fortunately, this is a problem where there are a few simple solution techniques that work much
better than an exhaustive search of the solution space. One of these is called Prim's algorithm—
named for Robert Prim, a mathematician and computer scientist who developed the algorithm in
1957 [11]. Actually, Prim independently reinvented an algorithm originally invented in 1930 by
Vojtech Jarník; because of this, the algorithm is sometimes called the Jarník-Prim (or Prim-
Jarník) algorithm [12]. The algorithm was independently reinvented once again in 1959 by
Edsger Dijkstra.
Prim's algorithm is very straightforward, but the pseudocode version that follows uses some
mathematical symbols that may be unfamiliar to many (though we used and described two of
these symbols in the pseudocode for Euclid's algorithm). The essential symbols are these:
∉ “Is not in”, “is not an element of”, or “is not a member of”. For example, a ∉ B
indicates that a is not a member of the set B .
∪ Union of sets. For example, A ∪ B is the set formed by the union of sets A and B —
that is, the set of all elements that are either in A or B , or in both (but without
duplicating any of the elements contained in both).
{…} The set containing the specified elements. For example, {a } is a set containing the
single element a , while {a , b} is a set containing the elements a and b .
• While V mst ≠ V :
◦ Find edge e uv with minimum c uv, where u ∉ V mst , v ∈ V mst , e uv ∈ E
◦ V mst ← V mst ∪ {u }
◦ E mst ← ( E mst ∪ {e uv })
Note that in the lines that start with “Randomly select …” and “Find edge …”, it's assumed that
we know how to select a random element from a set, and how to find the minimum-weight edge
with one endpoint already connected to the MST, and one not yet connected. Operations such as
these aren't unique to this algorithm, but are used in many algorithms; thus, they're not described
in detail in the pseudocode.
First, what does convex mean in this context? You probably have a common sense understanding
of the meaning, as applied to two- or three-dimensional shapes, but you might not know the
actual definition. In fact, it's quite simple:
A set of points is convex, if and only if for any pair of points in the set, all points on the
line segment connecting those two points are also in the set [13].
If you find (3) confusing, try re-reading it, after reading these definitions:
⇔ “If and only if”. This is a relationship between two logical statements, and means that
the two must either both be true, or both be false. For example, a ⇔ b means that a
and b are either both false, or both true. (Note that causation should not necessarily be
assumed from this relationship.)
∀ “For all”, or “for any”. This is used to state that the logical statement to the left of the
symbol applies for all conditions described to the right of the symbol, or that the
operation described to the left of the symbol should be applied to all combinations
described to its right. For example, 2 x ∈ ℕ , ∀ x ∈ ℕ—that is, for any value x that
is a member of ℕ (the set of natural numbers), the value 2 x is also a member of ℕ.
[a , b] The set of real numbers bounded by the specified limits, including those limits.
Another way to say this symbolically is to use the {…} set constructor notation—but
instead of listing all of the values to be included in the set within the braces (which is
impossible for real numbers), we would do it with {x ∈ ℝ ∣ a ≤ x ≤ b}, which
denotes the set of all real numbers x , such that a ≤ x ≤ b .
Now that we have a solid understanding of (3), and thus an understanding of what a convex set
is, let's return to the task of finding the convex hull.
Imagine that each point in the first set of points is a nail driven part-way into a flat surface. Now,
stretch an elastic band around the set of nails, so that all of the nails are within the perimeter of
the band. Finally, let the elastic snap into place. The polygon formed by the elastic band is the
boundary of the convex hull5 of the points.
5. Some definitions refer to this boundary, rather than the set of points it encloses, as the convex hull.
This is another combinatorial optimization problem; this time, the task is to find the subset of the
specified points, and the permutation (ordering) of the points in that subset, so that they form a
convex polygon that encloses all of the points. Fortunately, there are once again much better
ways of solving the problem than making an exhaustive search through subsets of the points.
One of the most intuitive methods, which performs well when the number of points on the
boundary is relatively small, is the gift wrapping algorithm (sometimes called a Jarvis march,
after its inventor, R. A. Jarvis) [14], [15].
1. Given a set of points on the XY Cartesian coordinate plane, and an initially empty
ordered list of vertices for the convex polygon forming the boundary of the convex hull:
2. Find the point with the minimum Y value. This point is the initial point on the boundary
polygon of the convex hull; it also becomes the current point in the algorithm.
3. Add the current point to the ordered list of vertices.
4. Find the candidate point in the set (excluding the current point from consideration)
which, if a line is drawn from the current point through the candidate point, results in all
of the other points in the set falling either on the left-hand side of the line, or exactly on
the line itself.
5. If additional points (other than the current point and the candidate point) fall on the line,
and the candidate point isn't the initial point:
◦ Select the point exactly on the line which is furthest from the current point, in the
same direction as the candidate point, to be the new candidate point.
6. Move to the candidate point, making it the new current point.
7. If this new candidate point is not the initial point, return to step #3 and proceed as before.
8. Done: The list of vertices, and the line segments connecting them (in the order in which
the vertices were added to the list) make up the completed boundary of the convex hull.
Algorithm 7: Gift wrapping algorithm
• Pick one of the natural language algorithms, and translate it into a pseudocode expression
that relies more on mathematical expressions. To see how well you did, present your
results to a colleague. Did he or she understand the algorithm as you expressed it?
It's important to note that while an algorithm should have as little ambiguity as possible, that
doesn't mean there's a single best way to express an algorithm, or a single best way to translate
that expression into a computer program.
The history, theory, and development of algorithms are rich areas of study, even when viewed
apart from the world of electronic computation. But the ever-expanding role of scientific
computing (aka computational science) makes an understanding of algorithms that much more
important, and makes algorithms an even more rewarding subject of exploration.