MA0 2
Introduction to Algorithms and Numerical Analysis
Every lesson so far, in one document · 5 chapters
Lesson 1
Vectors and Groups
Taught
Vectors
Algebra deals with variables and equations, geometry with points, lines, planes and solids, and the two are closely linked. Take the equation . On its own it is an algebraic object, a relation between two unknowns; but the collection of points whose coordinates satisfy it traces out a straight line, and the two constants in it give two features of that line. The number is the slope, the change in against the change in , and is the height at which the line meets the vertical axis.
So the constants in the equation of a line, which are algebraic data, correspond to the slope and the intercept, which are geometric data. This rests on identifying the points of the plane with pairs of numbers, and pairs are objects MA01 has already built.
Coordinates
An ordered pair is a pair in which one element is marked as coming first, so that
Given two sets and , the Cartesian product is the set of all ordered pairs whose first entry comes from and whose second comes from ,
and when we write for it. More generally, for the -fold product of sets consists of the -tuples with one entry drawn from each,
written as a Cartesian power when all the factors agree. Throughout this course .
The example we care about is , whose elements are the pairs of real numbers. Fixing an origin and two perpendicular axes in the plane identifies each point with exactly one such pair.
Remark (Descartes).
The identification of the points of the plane with is due to René Descartes, and the product carries his name because of it. The points and are different unless , which is why ordered pairs are needed rather than two-element sets. Given a point of the plane matched with the pair , we call the -coordinate and the -coordinate of .
Once points are pairs, two different kinds of quantity are in play. A scalar is a quantity settled by a single number, and for us that number will be real. Temperature, mass and altitude are scalars. Force and velocity are not: a force has a direction as well as a size, and both are needed to name it.
Remark (Magnitude and direction).
A vector is often introduced as a quantity carrying both a magnitude and a direction. That description says what vectors are for, but neither magnitude nor direction has been given a meaning yet, so it cannot be calculated with.
Take a pair and the origin . Instead of drawing the single point we may draw the arrow that runs from to , reached by travelling units along the horizontal axis and units along the vertical one.
Because the arrow and the point determine one another, we treat them as the same object and use the words interchangeably.
Let . An -dimensional vector, or -vector, is an element of . It may be written as a column vector
or as a row vector . For the real number is the th component of . The set of all such vectors is called -dimensional space.
Since points and vectors are the same objects here, the words component and coordinate are used interchangeably as well. For we usually name the components and , so that
is the -plane; for we name them , and , and call the -space.
Remark (Notation for vectors).
We write vectors in bold, and , and write for the vector running from to . Other texts write or for the same thing, and a vector of length one often gets a hat, as in or . Any of these will do, provided scalars and vectors are never written the same way.
Example 1.2 (Vectors in and ).
Remark (Rows against columns).
A row vector and a column vector with the same entries are the same element of , and for everything in this chapter the shape is only a matter of how the thing is printed. It stops being a matter of printing as soon as vectors are multiplied by matrices, since the rules for that multiplication treat a row of entries and a column of entries as objects of different shapes, and the two cannot then be swapped. We will keep to columns whenever the shape could matter.
The zero vector of is the vector all of whose components are ,
also called the null vector. It is the vector representing the origin.
The vectors of the form , with every component but the th equal to zero, are exactly the points of the -axis.
Vector Algebra
Definition 1.4 (Addition and subtraction).
Let and be vectors in . Their sum and difference are formed component by component:
Two vectors can be added only when they have the same number of components, since otherwise some component of the answer has nothing to be built from. A vector in and a vector in have no sum.
Read as arrows, is what we reach by travelling along and then travelling along from wherever that leaves us. Travelling along first and second lands in the same place, which is the picture behind : the two routes are the two ways round a parallelogram.
The right-hand panel shows the difference of two vectors. The vector is the one that translates the point with position vector to the point with position vector , since adding it to returns . For two points and this is written
Definition 1.6 (Scalar multiplication).
Let and let . The scalar multiple is
We write for .
When is a positive integer, is the translation obtained by translating times by . Letting range over all of , the points trace out the straight line through the origin and the point . Translating by undoes a translation by .
Example 1.7 (A scalar multiple).
The product is written from here on, the dot being dropped as it is for the product of two reals.
With both operations available we can build one vector out of several others.
Definition 1.8 (Linear combination).
Let and . The vector
is a linear combination of with coefficients . A sum of this shape is abbreviated
the symbol instructing us to add the terms obtained as the index runs through the integers from the value below the symbol to the value above it.
Definition 1.9 (Standard basis).
Let . For let be the vector whose th component is and whose other components are , so that
These vectors form the standard basis, or canonical basis, of .
Proposition 1.10 (Expansion in the standard basis).
Let and let . There is exactly one -tuple of real numbers with
namely the tuple of components of itself.
Discussion.
The statement is an existence and uniqueness claim about the tuple of coefficients, so the proof has two halves. Both are settled by looking at one component at a time, because addition and scalar multiplication were defined component by component: the th component of is , and by the definition of the standard basis every term of that sum vanishes except the one with . So the th component of the combination is , whatever the coefficients were. Existence then follows by taking , and uniqueness follows because any tuple that works must have equal to the th component of , which leaves no freedom.
Proof.
Let be any real numbers and fix with . By the definition of scalar multiplication the th component of is , which is when and otherwise; and by the definition of addition the th component of a sum is the sum of the th components. Hence
Now write . Taking for each , the displayed identity says that has th component for every , so it equals ; this proves existence. If is any tuple with , then comparing th components in that equation and using the identity again gives for every ; this proves uniqueness.
Remark (The basis in two and three dimensions).
For the standard basis is and , commonly written and , and the proposition says that every is and is so in only one way. For the basis is written , , .
Definition 1.11 (Norm of a vector).
Let . The norm, or magnitude, or length, of is the real number
This is also called the Euclidean norm. A vector with is a unit vector.
Remark (Bars and double bars).
Many texts write for the norm. We keep the single bars for the absolute value of a real number, which appears in the last chapter of this lesson and again throughout the course, and reserve the double bars for vectors. The choice only affects legibility, but a formula such as is a good deal easier to read when the two are told apart.
Let and let . Then
Proposition 1.13 (Properties of the norm).
Let and . Then
- ;
- if and only if ;
- .
Discussion.
All three parts are about the number under the square root, which is a sum of squares of real numbers, so each is settled by a fact about real squares rather than by anything about vectors. For the first, every square is non-negative and so is the sum, and the square root of a non-negative number is non-negative by definition. The second is a biconditional; the reverse direction is a computation, and the forward one uses that a sum of non-negative terms vanishes only if every term does, so each is and hence each is. The third pulls the constant out of the sum and out of the root, where the identity supplies the absolute value; it is the reason the absolute value appears at all, since the root is the non-negative one and need not be.
Proof.
Write and .
For the first part, each , so , and since the square root of a non-negative real is taken to be non-negative.
For the second, if then every , so and . Conversely, suppose . Squaring gives ; since is a sum of terms each of which is at least , no term can be strictly positive, so and hence for every . Thus .
For the third, the definition of scalar multiplication makes the components of the numbers , so
Definition 1.14 (Euclidean distance).
Let . The Euclidean distance between and , taken as position vectors, is ; in components,
The distance is symmetric in its two arguments, since and the third part of the last proposition, with , gives the two vectors the same norm.
Let and . Prove that , that , that and , and that
Let with . Show that is a unit vector, and that it is the only unit vector of the form with .
The Scalar Product
Definition 1.15 (Scalar product).
Let and be vectors in . Their scalar product, also called the dot product or the Euclidean inner product, is the real number
It is also written .
The scalar product of two vectors is a scalar, not a vector. Comparing the definition with that of the norm gives
Example 1.16 (A scalar product in ).
Remark (A glance ahead at the transpose).
A matrix has a transpose, obtained by exchanging its rows and its columns. A column vector is a matrix of one column, so its transpose is a row, and once matrices are multiplied the product is a matrix with one row and one column whose single entry is
So the dot product is the matrix product , which is why the row and column shapes of the earlier remark are kept apart.
Remark (Inner products in general).
Later modules replace by a vector space over a field and keep the product as a map
subject to the properties proved in the next proposition, which are there taken as axioms. Fields and vector spaces are defined in the last chapter of this lesson. The proofs that follow use only those properties, so they hold for any inner product.
Proposition 1.17 (The scalar product is a symmetric bilinear form).
Let and . Then
- ;
- ;
- ;
- , with if and only if .
Discussion.
Parts 1 to 3 are identities between two real numbers, each of which is a sum over the components, so each is proved by writing both sides as such a sum and reconciling them term by term with the arithmetic of . Symmetry needs only that . Linearity in the first argument needs the definitions of the sum and the scalar multiple to compute the th component of , and then the distributive law to split the sum in two. Part 3 does not need a separate argument: once symmetry is available, the two arguments may be exchanged, part 2 applied, and the arguments exchanged back. Part 4 is not new either, since was read off the two definitions above, and the remaining claims are the first two parts of the proposition on the norm.
Proof.
Write , and .
Multiplication of reals is commutative, so
which is the first part. For the second, the th component of is , so
For the third, we use the symmetry just proved twice, with the second part in between:
For the fourth, the identity holds because both sides are . That quantity is non-negative and vanishes exactly when , by the first two parts of the proposition on the norm.
Proposition 1.18 (Cauchy–Schwarz inequality).
Let . Then
with equality if and only if one of and is a scalar multiple of the other. Two vectors standing in that relation are called linearly dependent.
Discussion.
The proof starts from a quantity known to be non-negative, by part 4 of the last proposition: for every real . Expanding that norm with bilinearity turns it into
a quadratic in whose coefficients are the three quantities the statement mentions. A quadratic with positive leading coefficient that is never negative has at most one real root, so its discriminant is at most , and that discriminant is exactly . The case has to be handled on its own, since then the leading coefficient vanishes and the expression is not a quadratic; both sides of the inequality are there. For the equality case, note that the discriminant is exactly when the quadratic has a root , and by part 4 again a root means .
Prove the Cauchy–Schwarz inequality, including the description of the case of equality.
Remark (Where the inequality lives).
The proof asked for above uses only the four properties of the previous proposition, so the inequality holds for every inner product, not only for the dot product on .
Proposition 1.19 (Triangle inequality).
Let . Then
Discussion.
Both sides are non-negative, so it is enough to compare their squares: the left-hand square is , which bilinearity expands into , while the right-hand square is . The two differ only in the middle term, so the claim reduces to , which is Cauchy–Schwarz together with the fact that a real number is at most its own absolute value.
Proof.
By the identity and bilinearity,
Every real number is at most its absolute value, so , and the Cauchy–Schwarz inequality bounds the latter by . Hence
Both and are non-negative, and for non-negative reals and the inequality gives . Therefore .
The name comes from that picture. The side from to has length , and the other two sides have lengths and , the second because . Going from to by way of cannot be shorter than going straight there, and it is exactly as long only when lies on the straight segment between the two.
Let . Prove the parallelogram law
and interpret it as a statement about the two diagonals of the parallelogram spanned by and .
Let . Prove that
Write for the Euclidean distance. Show that with equality exactly when , that , and that
for all .
Symmetries
A symmetry of a geometrical figure is a way of moving the figure so that it ends up occupying exactly the position it started in. Draw an equilateral triangle on a transparent sheet, then pick the sheet up, turn it or flip it over without tearing or stretching it, and put it back down. If the triangle lands exactly on top of where it was, the movement we performed is a symmetry of the triangle.
To watch what a movement does we number the corners, at the top, at the bottom left and at the bottom right. The numbers travel with the paper; the three corner positions stay where they are. Reading off which number sits in which position after the movement tells us which movement it was.
The first of the six, the movement which does nothing, is the identity symmetry, written .
Six is the whole list, because a symmetry has to send corners to corners, and once we know where two of the corners go the third has nowhere left to be.
The six symmetries can be combined. Performing one symmetry and then another leaves the triangle in its original position, so the result is again a symmetry.
Combining also shows that the order in which two symmetries are performed matters.
So three things are true of the symmetries of a figure, and one thing is not. There is an identity; every symmetry can be undone, so it has an inverse; combining is associative, since bracketing a list of movements only says where to pause and not what order to perform them in. But combining does not commute.
Maps
Making that precise takes a little vocabulary about functions and three facts about them. The same ground is covered at length in MA01.
Definition 1.20 (Injections, surjections, bijections).
Let be a function. It is injective if implies ; surjective if for every there is an with ; and bijective, or a bijection, if it is both.
For and the composite is the function with . The identity map is the function with .
Proposition 1.21 (Composition and inverses).
Let , and be functions.
- ;
- if and are bijections, then so is ;
- if is a bijection, there is exactly one function with and , and is itself a bijection.
Discussion.
The first part is an equality of two functions with the same domain and codomain, so it is proved by evaluating both at an arbitrary point and unfolding the definition of a composite twice on each side; the two unfoldings meet at , and nothing about , or beyond their being functions is used. The second splits along the definition of bijection: injectivity is proved by peeling the two functions off an equation of images in the order they were applied, surjectivity by producing a preimage in two steps, first under and then under . The third is a construction rather than a deduction: surjectivity of says each has at least one preimage and injectivity says it has at most one, so “the” preimage is a well-defined function of , and the two composites collapse to the identity by construction. That is again a bijection follows because is an inverse for it.
Proof.
For the first part, let . Then
and as was arbitrary the two functions are equal.
For the second, suppose and are bijections. If then because is injective, and then because is injective; so is injective. Given , surjectivity of supplies with , and surjectivity of supplies with , whence ; so is surjective.
For the third, let be a bijection and let . Surjectivity gives at least one with , and injectivity gives at most one, so there is exactly one; define to be it. Then for every , and for the element is the unique preimage of , which is . So the two composites are the identities. If also satisfies them, then by the first part, which is uniqueness. Finally, the same two equations read with the roles of and exchanged say that has an inverse, namely ; so is injective, since gives , and surjective, since for every .
Symmetries of the Square
The informal account leaves “moving without tearing or stretching” undefined. Such a movement does not change the distance between any two points of the figure, and we take that as the definition.
Let be a non-empty subset. A symmetry of is a bijection that preserves distances, meaning
The norm in this condition is the one from the first chapter of this lesson. The definition does not mention turns or flips; those are consequences.
We work with one figure throughout, the unit square centred at the origin with its edges parallel to the axes.
Definition 1.23 (The square ).
Its four corners, numbered anticlockwise from the top right, are
and we call them the vertices of .
Eight symmetries of can be written down by inspection: the movement which does nothing; the clockwise rotations through , and ; the reflections in the vertical and the horizontal axis; and the reflections in the two diagonals.
For each of the eight symmetries just listed, determine which vertex it sends to.
To show that the list is complete, we first show that a symmetry sends vertices to vertices, using distances alone.
Proposition 1.24 (Symmetries send vertices to vertices).
Let be a symmetry of . Then is a vertex of whenever is.
Discussion.
A symmetry is only known to preserve distances, so the proof describes the four vertices by distances: they are the points lying furthest apart. We therefore first show that for all , with equality exactly when and are diagonally opposite vertices; this is a computation on coordinates, since each coordinate of has absolute value at most and equality in the sum of squares forces equality in each term. That done, a point of is a vertex if and only if some point of is at distance from it, a condition stated purely in distances. Applying to such a pair preserves the distance, so the image of a vertex again has a partner at distance and is therefore a vertex.
Proof.
Let and lie in . Each of lies between and , so , and likewise . Hence
so . Equality forces , hence for ; and since both coordinates are confined to an interval of length , this happens only when one of them is and the other . So equality holds exactly when and are vertices with both coordinates opposite, that is, when they are diagonally opposite vertices.
Consequently a point is a vertex if and only if there is some with : if is a vertex, take diagonally opposite; and conversely the equality case just described makes a vertex.
Now let be a symmetry and a vertex, and choose with . Then
and lies in , so is a vertex by the criterion.
Example 1.25 (A quarter-turn on the vertices).
The anticlockwise rotation through sends to . On the vertices it acts by
which is the numbering running one step anticlockwise, as it should be.
Next, the vertices determine every other point of the square.
Proposition 1.26 (Two adjacent vertices locate a point).
Let and be vertices of with , and let satisfy
Then .
Discussion.
The claim is that two distances determine a point, so what we want is to recover each coordinate of from the two given numbers. Adjacent vertices agree in one coordinate and differ by in the other, so subtracting the two squared distances cancels the coordinate they agree in, and what survives is a multiple of the coordinate they differ in. That coordinate is therefore determined. Feeding it back into either squared distance determines the square of the remaining coordinate’s offset from the shared value, and a square leaves two candidates; the ambiguity is removed by the fact that lies in , which forces the offset to have a known sign. Since every step determines a quantity from the two given distances alone, must produce the same values, and the two points agree.
Proof.
Write and . Since and are vertices at distance , they agree in one coordinate and differ in the other. Say they agree in coordinate and differ in coordinate , where , and write
with .
Let . Expanding and cancelling the terms in coordinate ,
so is determined by the two distances. Then
is determined as well. Since we have , so ; that is, is times a non-negative number, and it is therefore the one square root of the displayed quantity carrying that sign. Hence is determined too.
Every quantity in this computation depends only on and . The point has the same two distances, so the same computation returns the same coordinates, and .
Remark (The same fact drawn).
Geometrically the proposition says that two circles centred at adjacent vertices meet in at most one point of . Two distinct circles meet in at most two points, and those two are mirror images of one another in the line through the centres. Here that line is an edge of the square, so one of the two intersections lies on the square’s side of the edge and the other lies outside altogether.
Proposition 1.27 (A symmetry is determined by the vertices).
Let and be symmetries of with for . Then .
Discussion.
This is a uniqueness claim about functions, so we fix an arbitrary and prove . The tool is the proposition above, which needs two things: a pair of adjacent vertices, and the two points and standing at equal distances from each of them. The pair to use is and , adjacent because preserves the distance and by the previous proposition sends both to vertices. The equal distances come from distance preservation applied to each of and in turn: both and equal , and the hypothesis makes and the same point, so the two distances are measured from the same place. That proposition then gives .
Proof.
The vertices and satisfy . By the previous proposition and are vertices, and
so they are adjacent. Let and let . Since preserves distances,
and since does too, together with ,
So and are points of at equal distances from each of the adjacent vertices and . The proposition above gives , and as was arbitrary, .
Remark (Not every shuffle of the corners is a symmetry).
The proposition says a symmetry is determined by its effect on the vertices, not that every map of the vertices to themselves comes from one. Consider the assignment
Here while the images satisfy , so the assignment changes a distance and cannot be the restriction of a symmetry.
Proposition 1.28 (The square has exactly eight symmetries).
There are exactly eight symmetries of , namely the eight listed above.
Discussion.
The eight are known to exist, so it remains to show that there are no others. By the last proposition a symmetry is settled once its values on the four vertices are known, so it is enough to count the possible sets of values, and that count is made one vertex at a time. There are four choices for . Given it, must be a vertex at distance from , and each vertex has exactly two neighbours, so two choices. The remaining two values are then forced rather than chosen, because is diagonally opposite and diagonally opposite , and a symmetry preserves the distance that says so. Four times two is eight, and since the eight listed symmetries are distinct, every count is attained.
Proof.
Let be a symmetry of . By the proposition on vertices, carries each to a vertex, so there are at most four possibilities for .
Suppose is fixed. Since , the vertex satisfies , so it is one of the two vertices adjacent to : two possibilities.
Now , so , and by the equality case established earlier is the vertex diagonally opposite . It is therefore determined by . The same argument determines as the vertex diagonally opposite .
So the values of on the four vertices are settled by at most combinations, and by the previous proposition is settled by those values. Hence there are at most eight symmetries. The eight movements listed act differently on the vertices, so they are eight distinct symmetries, and the count is exact.
Combining Symmetries
Proposition 1.29 (Symmetries compose and invert).
Let be non-empty and let be symmetries of . Then is a symmetry of , the identity map on is a symmetry of , and is a symmetry of .
Discussion.
Each of the three claims asks for two things, that a certain map is a bijection and that it preserves distances, and the proposition on composition and inverses supplies the first half in every case. What is left is distance, and each case is one line. For the composite, apply the hypothesis on and then the hypothesis on to the pair it produced. For the identity, there is nothing to check. For the inverse, note that an arbitrary pair of points of can be written as , because is onto, and then the condition on read backwards is the condition on .
Proof.
Let . Since and then preserve distances,
and is a bijection by the second part of the proposition on composition and inverses, so it is a symmetry. The identity map is a bijection and leaves both sides of the condition untouched.
For the inverse, is a bijection by the third part of that proposition. Let ; since is surjective there are with and , and then , . Hence
Definition 1.30 (Product of symmetries).
For symmetries and of a set we write for the composite , so that
The symmetry is performed first and second.
Composition of maps is associative, so a product of several symmetries may be written without brackets. From here on denotes the anticlockwise quarter-turn of and the reflection in the horizontal axis, that is
Example 1.31 (Powers of the quarter-turn).
Repeating gives the other rotations:
Since , the inverse of is .
Read off the action on . We have and , so
On the other hand and , so . The two symmetries disagree at , hence .
This is the first operation we have met which is not commutative.
Identify and among the eight symmetries listed earlier, describing each as a rotation or as a reflection in a named axis.
The two generators are not independent. Computing both sides on a general point,
so ; multiplying on the right by and using gives the equivalent form . The relation lets any product of ‘s and ‘s be rewritten with all the ‘s on the left, and the resulting shapes are
These are eight symmetries and they are distinct, so by the counting proposition they are exactly the eight symmetries of the square. The first four are the rotations and the last four the reflections.
Groups
The arguments about symmetries of the square used four facts: combining two of them gives a third; there is an identity; every one of them can be undone; and combining is associative. The axioms of a group are exactly those four.
Definition 1.33 (Binary operation).
A binary operation on a set is a function . We write for the value of at the ordered pair .
A group is a pair consisting of a set and a binary operation on satisfying:
- (G1) Associativity. for all ;
- (G2) Identity. there is an with for every ;
- (G3) Inverses. for every there is an with .
The element of (G2) is the identity, or neutral element, and the element of (G3) is an inverse of . Both turn out to be unique, and the inverse of is then written .
The first of the four facts about symmetries, that combining two gives a third, does not appear as an axiom because it is already in the definition of a binary operation: the codomain of is , so a product of two elements of is an element of and there is nothing further to require.
Remark (The closure axiom).
Some texts add a fourth axiom, closure: if then . Under our definition that is automatic, for the reason just given. It has to be stated when the operation is introduced as something defined on a larger set and then restricted, since one must then check that the restriction lands back inside .
Remark (Writing the operation).
The symbol is usually replaced by a dot, and the dot is usually dropped: we write for and call it the product of and , exactly as for a product of numbers. This is a convention about notation, not a claim that the operation resembles multiplication, and in particular it does not license writing .
Associativity means that a product of three elements may be written with no bracket, since the two possible bracketings agree. The same then holds for longer products: an expression such as has the same value as every other bracketing of in that order, and we write it . From here on products of any length are written without brackets.
Let be a group and let . Prove that every way of bracketing the product , keeping the terms in that order, gives the same element of .
Let be the number of ways of bracketing a product , keeping the terms in that order, so that , and , and set .
- Explain why .
- Use the recurrence to compute and .
Definition 1.35 (Abelian group).
A group is commutative, or abelian, if it satisfies the further condition
The integers under addition, , form an infinite abelian group: addition is associative, is the identity, and the inverse of is . The symmetries of the square under composition form a finite group which is not abelian, since .
Remark (Associativity is not commutativity).
Associativity says and commutativity says ; the two are easy to confuse and are not related. Associativity holds in every group by definition, while commutativity is an extra condition that a group may or may not satisfy.
For symmetries, associativity holds automatically. Both and mean: perform , then , then . The brackets only divide the calculation into stages and do not touch the order in which the movements happen. Operations that are not associative do exist, subtraction on being one, since and usually differ; but they cannot arise from composing functions, which is associative always.
Proposition 1.37 (The identity is unique).
Let be a group. If and both satisfy the condition (G2), then .
Discussion.
The statement is an equality of two elements, and the only material available is that each of them is neutral. We form the product , in which both hypotheses can be used, and evaluate it twice: treating as an identity leaves , and treating as an identity leaves . No contradiction and no case split is needed, and neither associativity nor inverses enter.
Proof.
Since satisfies (G2), we have . Since satisfies (G2), we have . Hence .
Proposition 1.38 (Inverses are unique).
Let be a group with identity , and let satisfy
Then .
Discussion.
Again the goal is an equality of two elements and the hypotheses are two equations, so we build one expression that both can act on: the triple product . Read with the brackets to the left it uses and collapses to ; read with the brackets to the right it uses and collapses to . Associativity says that the two readings are the same element, and that is the proof.
Proof.
Using associativity in the middle step,
Because of this proposition the inverse of may be named, and we write it .
The following is offered as a proof of the last proposition: “from the hypotheses, and , so .” Explain why it is not one.
Proposition 1.39 (Solving an equation in a group).
Let be a group and let .
- For we have if and only if ;
- for we have if and only if .
Discussion.
Each part is a biconditional between two equations, so each direction is proved by multiplying the given equation by on the appropriate side and simplifying with the axioms. Which side matters: the first part multiplies on the left throughout, because the unknown sits to the right of , and the second multiplies on the right for the mirror-image reason. No step exchanges the two factors, so in a group that is not abelian the two parts are different statements.
Proof.
For the first part, suppose . Multiplying on the left by gives , and associativity turns the left side into , so . Conversely, suppose . Multiplying on the left by gives .
Prove the second part of the last proposition.
Proposition 1.40 (Inverse of a product).
Let be a group and let . Then .
Discussion.
By the uniqueness of inverses it is enough to check that the proposed element does what an inverse of has to do, so we multiply by on one side and cancel from the middle outwards, then do the same on the other side. The order in which the two factors are reversed is forced by exactly this cancellation, since it is that has to meet first.
Proof.
Using associativity to bracket at will,
and symmetrically . So is an inverse of , and by uniqueness it is .
Let be a group and let . Prove that
Groups of Small Order
Definition 1.41 (Order of a group).
A group is a finite group if the set is finite. The order of a finite group is the cardinality , the number of its elements. Every group has an identity, so is never empty and its order is at least .
Remark (Infinite groups).
Not every group is finite. The unit circle in has infinitely many symmetries, one rotation for each angle, and is infinite as well.
Powers are defined as in arithmetic.
Let be a group and . Define and for , and set for .
Proposition 1.43 (Laws of exponents).
Let be a group, let and let be integers. Then
Discussion.
Both identities are statements about all integers, but the definition of a power is a recursion on the non-negative ones, so we use induction for followed by a reduction of the remaining sign cases to that one. The induction for the first identity runs on : the base case is the definition of , and the step is the recursion clause together with associativity. The second identity then follows from the first by a second induction on . For negative exponents we use that has the same structure, and that and are inverse to one another, which converts a negative exponent into a positive one on the inverse element.
Proof.
Take first and induct on . For both sides are , since . If , then
which is the claim at . A second induction on gives : the case reads , and the step is , using the first identity.
For the sign cases, note that for , by induction on using the first identity for the inverse element; so . Any instance of the two identities with negative exponents now follows by rewriting each negative power as the inverse of a positive one and applying the proposition on the inverse of a product.
Definition 1.44 (Order of an element).
Let be a group and . The order of is the smallest with , and is if there is no such .
Example 1.45 (Orders in the group of the square).
In the group of symmetries of the square the identity has order ; the quarter-turns and have order ; and the remaining five elements, the half-turn and the four reflections , , , , all have order .
Proposition 1.46 (Orders are bounded by the order of the group).
Let be a finite group of order . Then every element of has order at most .
Discussion.
We need a power with , and we use only that has elements. Listing powers of therefore forces a repetition, and a repetition with is exactly what we want: cancelling from both sides, which the group allows, leaves with the exponent between and . The definition of order then bounds it by that exponent.
Proof.
Let and consider the elements of . Since has only elements they cannot all be distinct, so there are with and . Multiplying by and using the laws of exponents gives
with . So the set of positive exponents killing is non-empty, and the least of them, which is the order of , is at most .
Remark (Lagrange).
More is true: in a finite group the order of every element divides the order of the group. That is Lagrange’s theorem, and it belongs to a course on abstract algebra rather than here. The finiteness of is what the argument above uses, not merely that orders are finite; there are infinite groups in which every element has finite order.
Proposition 1.47 (Rows and columns of the table).
Let be a group and let . Then the maps and are bijections from to . In a finite group, therefore, each element appears exactly once in every row and exactly once in every column of the multiplication table.
Discussion.
Each map is a bijection because an explicit inverse is available: multiplying by on the same side undoes it, which is the content of the proposition on solving . The statement about the table is a translation, since the row labelled lists the values of as runs over ; a bijection from a finite set to itself hits each element exactly once, so no entry is missing and none is repeated.
Proof.
Let and . Then and likewise , so is a bijection with inverse . The argument for is the same with the multiplications on the other side.
The row of the multiplication table labelled has the entry in the column labelled , so its entries are the values of . As is a bijection of the finite set onto itself, every element of occurs among them exactly once. Columns are handled by the other map.
Definition 1.48 (Isomorphism).
Let and be groups. An isomorphism from to is a bijection with
If one exists, and are isomorphic, written .
Two isomorphic groups may be built from different objects, one from movements of a figure and one from numbers, and still have the same multiplication: the isomorphism matches their elements so that products correspond.
Proposition 1.49 (What an isomorphism preserves).
Let be an isomorphism, with identities and . Then , and for every and every integer ,
Moreover and have the same order.
Discussion.
The hypothesis is a single equation, , so each claim is obtained from it by a substitution. Taking makes the equation say , which cancels to the first claim. Taking then says , which identifies the second. The power law follows by induction on , the negative case by combining the two claims already made. The statement about orders uses bijectivity: injectivity turns back into , so the positive exponents killing are exactly those killing , and two sets of positive integers that coincide have the same least element.
Proof.
Putting gives , and multiplying by gives . Putting gives , and symmetrically on the other side, so .
For induct: , and if then . For write and apply the case just proved to , using .
Finally, for we have if and only if , since is injective, and while . So if and only if . The two elements are killed by the same positive exponents, hence have the same order.
Proposition 1.50 (Cyclic groups).
Let be a group of order containing an element of order . Then
and , where is the remainder of on division by . Any two such groups are isomorphic.
Discussion.
Two things need proving: that the listed powers exhaust , and that the operation is forced. For the first, the listed powers are distinct, since an equality between two of them would produce a positive exponent smaller than killing and so contradict the order of ; being distinct elements of a set with elements they are all of it. For the second, write by division with remainder and use the laws of exponents, where makes the multiple of disappear. The last sentence is then immediate: the map between two such groups is a bijection by the first part and respects products by the second.
Proof.
Suppose with . Then with , contradicting the order of being . So the elements are distinct, and as they are all of .
Let and write with . By the laws of exponents,
Finally, let be another group of order with an element of order . Both groups are listed by their powers as above, so for is a well-defined bijection , and the displayed rule computes the product on both sides by the same remainder, so .
Definition 1.51 (Cyclic group).
The group described by the last proposition is the cyclic group of order , written . It is abelian, since and are both computed from the remainder of .
We can now work through the small orders. Throughout, the multiplication table of a finite group has a row and a column for each element, and the entry in row and column is .
Order 1. The identity is the only element and the operation is . This is the trivial group. The letter “P”, read as a subset of , has trivial symmetry group.
Assuming a reasonably symmetrical font, so that “Y” has a reflection symmetry in a vertical axis and “B” one in a horizontal axis, determine which capital letters have trivial symmetry group.
Order 2. Let . The identity axiom fills in every entry but one:
If then multiplying by gives , which is false. So , the table is forced, and has order . Hence , and is the only group of order .
Remark (One group, two pictures).
This group is the symmetry group of the letter “Z”, whose non-trivial element is the half-turn that carries the letter onto itself, and also of the letter “Y”, whose non-trivial element is a reflection. The two figures are different geometrically, but their symmetry groups are isomorphic.
Order 3. Let with . Row of the table consists of together with and , and by the proposition on rows and columns those three entries are , and in some order. So . Were we should have , and multiplying by would give . Hence and , which fills the table:
Here and , so has order and .
Order 4. Two structures occur, and we separate them by the largest order of an element. By the proposition bounding orders that largest order is , , or , and it is not , since then every element would be .
If some element has order , the cyclic proposition gives .
Suppose some has order , and put . Pick , which exists since . If for some then , contrary to the choice of ; so the three elements , , all lie outside , and they are distinct because is injective. That gives at least elements of , which is impossible. So no element has order .
The remaining case is that every element other than has order . Write . Then , since would make ; and and , since either would force one of to be . Hence , and the same argument applied to each pair fills the table:
This is a group, because it sits inside one we have already built: taking , and inside the symmetries of the square gives a set of four symmetries closed under composition, each equal to its own inverse, with exactly this table. It is the Klein four-group, written , and it is the symmetry group of the letter “H”.
So there are exactly two groups of order up to isomorphism, and . They are not isomorphic: one has an element of order and the other does not, and an isomorphism preserves the order of an element.
Remark (How the list grows).
All the groups above are abelian, and so is every group of order , though we do not prove it. The smallest non-abelian group has order . Tabulating the groups of order becomes hard quickly, especially when is a large power of a small prime: there are groups of order .
Symmetry Groups of Regular Polygons
The symmetries of the square, with composition, satisfy the group axioms by the proposition on composing and inverting symmetries. That group has eight elements and is called the dihedral group of degree four, written .
Remark ($D_4$ or $D_8$).
The subscript here counts the sides of the square, so has eight elements. Some texts write for the same group, counting its elements instead. Check which convention a source is using before comparing statements.
In terms of the quarter-turn and the reflection , the group is described by the relations
The third relation lets every be moved past every . Rewriting as moves the ‘s to the left, and any product of ‘s and ‘s collapses to one of . For instance
using at the last step, so the half-turn commutes with even though the quarter-turn does not. Similarly
so is one of the reflections: doing it twice returns the square to where it started.
An equilateral triangle has six symmetries, three rotations and three reflections. The rotations are , and , where is the rotation through , and they satisfy . Each reflection fixes one vertex and exchanges the other two; if is one of them, the other two are and . The group they form is , with
These are the relations of the square with the order of the basic rotation changed from to . The group has order and is not abelian, so it is the smallest non-abelian group.
The same account fits every regular polygon. A regular -gon with has rotational and reflectional symmetries, so its symmetry group has elements. Writing for the rotation through and for any one of the reflections,
and the elements are , which are the rotations, together with , which are the reflections.
Remark (Small $n$).
For there is no regular polygon to act on, but the three relations still make sense and still define a group of order . Neither is new: is under another name, and is the Klein four-group.
Symmetries as Permutations
A symmetry of the square is determined by where it sends the four vertices, and a symmetry of the triangle by where it sends the three. So we can study the rearrangements themselves.
Definition 1.52 (Permutation).
Let be a non-empty set. A permutation of is a bijection . The set of all permutations of is written , and for we write or .
A permutation of a finite set is recorded by a table of two rows, the points along the top and their images beneath. For ,
means , , and so on. The bottom row is a rearrangement of the top one, which is the condition that is a bijection.
There is a shorter notation. Take sending to . Following one point at a time, goes to , which goes to , which goes back to ; that is a cycle of length three. Separately and exchange, a cycle of length two. Writing each cycle in brackets gives the cycle notation
Cycle notation is not unique, since and name the same cycle, and by convention we leave out the points a permutation fixes: an index that does not appear is understood to stay where it is.
Example 1.53 (Multiplying in cycle notation).
Let as above and let in . Recall that means first, then . Following each point through both,
and . Collecting the cycles,
Example 1.54 (Inverses and conjugates).
Let and let be as above. Then
The inverse of a cycle is the same cycle traversed backwards, which is where the second of these comes from. The third combination, , is called a conjugate of .
Example 1.55 (Composing in two-row notation).
Let
Then
which disagree at , so composition of permutations is not commutative either.
Theorem 1.56 (The symmetric group).
Let be a non-empty set. Then with composition is a group, called the symmetric group on .
Discussion.
There are four things to check and the proposition on composition and inverses has done most of them. First that composition is a binary operation on , which is the statement that a composite of bijections is again one. Then the three axioms: associativity is associativity of composition of maps, which holds for all functions and not merely for bijections; the identity map is a bijection and satisfies the identity axiom by definition; and the inverse required by (G3) is the inverse function, which exists because is a bijection and is itself a bijection.
Proof.
If then is a bijection by the second part of the proposition on composition and inverses, so composition is a binary operation on .
(G1) Composition of maps is associative by the first part of that proposition, so for all ; both send to .
(G2) The identity map with is a bijection, so lies in , and for every .
(G3) If then is a bijection, so by the third part of that proposition it has an inverse function , itself a bijection, and .
As with symmetries we write for , so that acts first. Some texts write the argument on the left, rather than , and then read products in the opposite order; work one product out by hand before trusting a source’s convention.
For we have .
Discussion.
An element of is settled by its bottom row, a list in which each of appears once, so the count is a count of such lists. Building one from left to right, each entry may be any value not already used, so the number of choices falls by one at each step: for the first, for the second, and so on down to . Multiplying the numbers of choices gives the total, and that product is the factorial.
Proof.
A permutation is determined by the list , and a list arises from a permutation exactly when the are in some order.
Choose the entries left to right. There are possibilities for . Once is chosen, injectivity excludes it from the rest, leaving possibilities for ; after and , there are possibilities for ; and so on, with one possibility left for . Each choice is free of the others in the sense that the number available at each step does not depend on which values were taken earlier, so the number of lists is
For the triangle there is no constraint at all on how the vertices may be moved: every rearrangement of the three of them is produced by exactly one symmetry. There are rearrangements and six symmetries, so the symmetry group of the equilateral triangle is the group of all permutations of three objects,
The two sides describe different things, movements of a figure on one and rearrangements of three labels on the other, and the isomorphism matches them up.
The square is different. It has eight symmetries while has elements, and the remark above on shuffles of the corners exhibits a rearrangement that no symmetry produces.
Remark (Where $S_4$ does live).
No figure in the plane has symmetry group . In three dimensions it does: is the group of symmetries of a regular tetrahedron, whose four vertices may be permuted in any way at all.
Let be a finite set and let be a function. Show that is injective if and only if it is surjective, and give an example of an infinite for which this fails.
In , let
Compute , , , and , and write each in cycle notation. Then label the vertices of a regular hexagon to clockwise and identify each of these permutations with a symmetry of the hexagon.
Write out the eight symmetries of the square as permutations of in cycle notation, and use the result to exhibit an injective map carrying products to products.
Fields
A group carries one binary operation. The number systems we actually compute in carry two, addition and multiplication, and multiplication distributes over addition. A set with two such operations is a field if it satisfies the following.
Let be a set with two binary operations and . The triple is a field if
- (F1) is an abelian group, with neutral element written ;
- (F2) is an abelian group, with neutral element written ;
- (F3) multiplication distributes over addition: for all .
Condition (F2) says two things at once: every element other than has a multiplicative inverse, and is closed under multiplication, so a product of two non-zero elements is never .
Since lies in we have , so a field has at least two elements. Two is achievable: the set with and the obvious multiplication is a field.
Example 1.59 (Fields and near misses).
The rationals and the reals are fields, and so are the complex numbers.
The naturals are not: (F1) already fails, since has no additive inverse. The integers are not either, though they come closer. They satisfy (F1) and (F3), and multiplication on them is associative and commutative with neutral element ; what fails is (F2), because no integer other than and has a multiplicative inverse in .
Remark (The complex numbers).
MA01 does not build , so here is what we take it to be. Its elements are the expressions with , where is a formal symbol, added and multiplied by
the second rule being what expanding the brackets gives once is replaced by . The conjugate of is and its modulus is , so that . Every therefore has the multiplicative inverse , and is a field.
Let . Prove that
and deduce that and that has inverse .
Show that if and only if . Then show that conjugation is an isomorphism of onto itself, and also of onto itself, and that it is its own inverse in both cases.
A subtler near miss drops commutativity of multiplication rather than invertibility.
Example 1.60 (The quaternions).
Let be the set of expressions
where , , are formal symbols. Addition is componentwise, and multiplication is determined by requiring it to be associative and to distribute over addition, together with the rules
From these one finds and , and similar relations among the other pairs. So multiplication on is not commutative, and satisfies every field axiom except that one. A structure of this kind, where the non-zero elements form a group under multiplication which need not be abelian, is a skew field or division ring.
Show that and in . Suggestion: first check that , and , then read as an equation to be solved for , and use the rule for the inverse of a product to get at .
Vectors are built over a field of scalars, and the first chapter of this lesson is the case of the following.
Definition 1.61 (Vector space).
Let be a field. An -vector space is a set with an addition and a scalar multiplication such that
- is an abelian group, with neutral element ;
- for all and ;
- and for all and ;
- for every .
Elements of are vectors and elements of are scalars.
The last condition cannot be dropped: without it the rule sending every to would satisfy the other three. With the four in place, any calculation in reduces to a linear combination
which is the shape every computation in the first chapter took. That satisfies these axioms is what the first problem of that chapter asked you to check.
The Real Numbers
We take the reals as known from school: a field carrying an order relation compatible with the two operations, so that implies , and with implies .
The rationals are a field with an order as well, so the order alone does not separate the two. What separates them is a property of how the order behaves.
For we write as shorthand for ” and ”. Between any two distinct reals there is a third, namely their average.
Let with . The closed interval and the open interval are
the first a single point when and the second empty then. The half-open intervals are
again empty when . The half-infinite intervals are
together with and , defined with and in place of and .
Remark (Reading the notation).
Three things about these symbols.
The and are notation and nothing else. They do not name elements of , and there is no such set as .
The notation for an open interval collides with the notation for an ordered pair. Some authors avoid this by writing the open ends with reversed square brackets, and . We tolerate the ambiguity, since context always says whether a subset of or an element of is meant.
If then contains infinitely many reals, since the averaging remark above produces a new one from any two. The same is true of the other bounded intervals, which contain .
Finally, the notation works over any ordered number system, not only . Where it is not clear from context we write or .
What does it mean for a subset of to have a largest element? We want an with
- , and
- for every .
An satisfying the second condition alone is called an upper bound for , and is bounded above if it has one. Lower bounds and bounded below are defined the same way with the inequality reversed, and is bounded if it is both.
Proposition 1.63 (A largest element is unique).
Let . If and both satisfy conditions 1 and 2, then .
Discussion.
The conditions come in a pair, one saying the candidate belongs to and one saying it dominates , and the proof combines the membership of each with the upper-bound property of the other. This gives the two inequalities and , and antisymmetry of the order turns the pair into an equality. Nothing about beyond the order is used, so the same argument works in any ordered set.
Proof.
Since and is an upper bound for , we have . Since and is an upper bound for , we have . Hence .
We may therefore speak of the largest element of and write it . What we may not do is write into an argument before knowing it exists, because many sets have no largest element. This can fail for three separate reasons.
A set may be empty, so that no can satisfy the first condition. A set may be unbounded above, so that no satisfies the second; itself is one, and so is
And a set may be non-empty and bounded above and still have no element meeting both conditions at once.
Example 1.64 (A bounded set with no largest element).
Let and let , so . Put . Then , so and ; hence is not an upper bound for . As was arbitrary, has no largest element, even though is an upper bound for it.
Everything said about largest elements applies to smallest ones with the inequalities reversed: a smallest element is unique when it exists, is written , and need not exist. For a finite non-empty set there is no difficulty at all, which is why , meaning the larger of and , may be written down freely.
Show that every non-empty subset of has a smallest element. (Harder.)
Remark (Least upper bounds).
In the example above, clearly acts as an upper limit of , and the reason has no largest element is that was left out. The following construction makes this precise. For let
be the set of upper bounds of . If has a largest element then has a smallest one and . But can exist when does not: for we get , whose smallest element is . That number is the least upper bound, or supremum, of .
The property that separates from is that in this never fails: every non-empty subset bounded above has a least upper bound. In it does fail, as the set of rationals with square less than shows. Making that statement into a construction of is a course in itself.
Let be non-empty. Show that if exists then exists and the two are equal.
Absolute Value
Definition 1.65 (Absolute value).
The absolute value is the function given by
equivalently .
Theorem 1.66 (Properties of the absolute value).
For all :
- , and if and only if (positive definiteness);
- (homogeneity);
- (triangle inequality).
Discussion.
The first part is read straight off the definition, one case at a time. The second could also be done by cases, four of them, but there is a shorter route: both sides are non-negative by the first part, and two non-negative reals with equal squares are equal, so it is enough to check that the squares agree, which they do because for every . The third rests on the two inequalities , which hold by inspection of the definition; adding the versions for and for traps between and , which is the inequality .
Proof.
For the first part, if then , and if then ; so always, and forces the first case with . Conversely .
For the second, note that for every real , since is or . Hence
Both and are non-negative by the first part, and for non-negative reals equal squares give equal values, so .
For the third, holds for every : if the right-hand inequality is an equality and the left is clear, and if the two swap roles. Adding the inequalities for and for ,
Now is either or , and both are bounded above by by the two halves of the display. Hence .
Remark (Distance on the line).
Setting turns the absolute value into a measure of distance between points of , and the three properties above become exactly the three properties of the Euclidean distance proved in the first chapter. A function with those properties is called a metric, and on is the case of the norm on .
Show that is bounded if and only if there is an with for every . Then give examples of subsets of that are bounded above only, bounded below only, and unbounded, with bounds where they exist.
Sketch the set and write it as an interval.
Let with . Show that , and that .
Useful Inequalities
Theorem 1.67 (Reverse triangle inequality).
For all ,
Discussion.
Each claim bounds a quantity from below, and the triangle inequality bounds things from above, so we write as a sum of the pieces the statement mentions. Taking and applying the triangle inequality to that sum gives , which rearranges into the first claim. The second is the same argument with replaced by , using , which is the homogeneity part of the previous theorem with the factor .
Proof.
Write and apply the triangle inequality:
so . Replacing by throughout and using gives , hence .
Theorem 1.68 (Bernoulli's inequality).
Let with . Then for every .
Discussion.
The exponent ranges over and appears on both sides, so the proof is an induction on . The base case is an equality. In the step we multiply the inductive hypothesis by , which is legitimate precisely because makes that factor non-negative and so preserves the inequality; this is the only place the hypothesis on is used, and the inequality is false without it. Expanding the product leaves an extra term , which is non-negative and can be dropped to reach the claim at .
Proof.
For both sides equal . Suppose . Since we have , so multiplying the inductive hypothesis by preserves the inequality:
the last step because . This is the claim at .
Theorem 1.69 (Young's inequality).
Let and . Then
Discussion.
All three follow from the fact that a square is never negative. Expanding produces the first. For the second, the weights and have to appear, so the square to expand is that of , whose cross term is again while the outer terms carry the weights. The third is the first applied twice: expanding instead bounds by the same quantity, and a number bounded above together with its negative is exactly a number whose absolute value is bounded.
Proof.
From we get , which is the first inequality. Since ,
which is the second. From we get ; combined with the first inequality, both and are at most , and is one of them.
Theorem 1.70 (Arithmetic and geometric mean).
Let with . Then .
Discussion.
The statement is Young’s first inequality with and in place of and : since and are non-negative they have square roots, and substituting and for the two variables there turns the product into and the squares into and . Equivalently one may expand directly, which is the same computation written out.
Proof.
Both and are non-negative, so and exist. Then
which rearranges to .
Standard Functions
The simplest real-valued functions are built from the field operations alone. A polynomial function is one of the form , such as ; later in the course we approximate arbitrary functions by these. A rational function is a quotient of two polynomials, such as
The second is not a function on , since division by zero is undefined and the denominator vanishes at ; it is a function on the subset .
Definition 1.71 (Exponential function).
The exponential function is the unique differentiable with
We write , and for .
That such a function exists and is unique is proved in analysis; we take it, and the addition law
as given.
Proposition 1.72 (Rules for the exponential).
For and ,
and for every . The function is strictly increasing, and is a bijection from onto .
Discussion.
Every rule here is the addition law used once or repeatedly. Setting in it makes the left side , which identifies as a reciprocal and in passing shows never vanishes; the quotient rule is then the addition law applied to . The rule for is an induction whose step is one more application. Positivity needs a separate observation: is the square of , hence non-negative, and it is not zero by what has just been shown. Strict increase then comes from the derivative, which equals the function and is therefore positive; and a strictly increasing function is injective, which leaves only surjectivity onto , and that is the part we take from analysis along with existence.
Proof.
Putting in the addition law gives , so neither factor is zero and . Hence
For , induct on : the case is trivial, and .
For positivity, , and by the first paragraph, so . Consequently everywhere, so is strictly increasing and therefore injective. Its range is .
Being a bijection onto , the exponential has an inverse.
Definition 1.73 (Natural logarithm).
The natural logarithm is the inverse of , written
so that for every and for every .
Proposition 1.74 (Rules for the logarithm).
For and ,
together with and . Moreover is differentiable with .
Discussion.
An identity about becomes an identity about once is applied to both sides, because is injective and undoes . So each rule is proved by applying to the proposed right-hand side and using the addition law. The value is read backwards. The derivative is different in kind: differentiating the identity by the chain rule produces , and the left factor is , which solves for .
Proof.
For ,
and applying to both sides gives . Replacing by and using the same computation gives the quotient rule, and taking in it gives since ; and holds because . The rule follows by induction from the product rule.
Differentiating by the chain rule gives , and , so for .
Remark (Which logarithm is $\log$?).
In mathematics an unadorned always means the natural logarithm, to base . Some software takes the opposite convention, using for the base- logarithm and for the natural one; check before trusting a numerical result.
Remark (Why there is no logarithm of a negative number).
We have not defined for , and no definition is possible. The logarithm is defined by the identity , and takes only positive values, so no value of could satisfy it for . The same restriction is inherited by the power functions below.
Definition 1.75 (Powers and logarithms to a general base).
For define by
It is a bijection, and its inverse is the logarithm to base , written .
This agrees with the elementary meaning of a power whenever that meaning is available: for the definition returns multiplied by itself times when , returns when , and returns when . Its advantage is that it makes sense for every real exponent.
Proposition 1.76 (Changing the base).
For with and ,
Discussion.
The base- logarithm was defined as an inverse, so we compute it by applying the function it inverts. Writing means , which by the definition of a power is ; taking of both sides turns it into , and dividing gives the first identity. The second is then arithmetic: substituting the first identity three times, once for each of the three logarithms appearing, reduces the claim to cancelling a common factor.
Proof.
Let , so , that is . Applying gives , and since , so .
For the second identity, substituting the first three times,
Example 1.77 (Halving a fish population).
Suppose a lake holds fish and that fishing reduces the population according to with . The time at which half the fish are gone satisfies
which does not depend on .
Trigonometric Functions
Definition 1.78 (Periodic function).
A function with is -periodic for if for every with .
The trigonometric functions are read off the unit circle, the circle of radius centred at the origin . Angles are measured in radians and counted anticlockwise from the positive -axis, so that a full circuit is .
The angle may be any real number, and denotes the point of the unit circle for which the angle from the positive -axis to is . A full circuit is , so , and are the same point; it is often convenient to restrict to or to . We say in this situation that is defined modulo .
Definition 1.79 (Congruence modulo a real number).
Let . We say is equal to modulo , written , if
a set also written .
Example 1.80 (An angle modulo ).
The equation says that
Definition 1.81 (Sine, cosine and tangent).
Let and let be as above. The cosine is the -coordinate of and the sine is its -coordinate. Where , that is where , the tangent is
Thus and .
Since a full circuit returns to itself, and are -periodic; turns out to be -periodic, which the shift formulas below explain.
We take the addition formulas as known,
and derive the rest from them together with the definition.
Proposition 1.82 (Trigonometric identities).
For all real and at which the expressions are defined:
- , and ;
- , , ;
- and ;
- , , and ;
- and ;
- and ;
- and ;
- and ;
- .
Discussion.
Only the first two parts need anything beyond the addition formulas. The Pythagorean identity is the statement that lies on the unit circle, since its coordinates are and and the circle has radius ; dividing it by , where that is non-zero, gives the companion identity for the tangent. The parity statements are read off the circle as well: reflecting in the -axis carries to , which negates the second coordinate and fixes the first. Everything after that is substitution. Part 3 is the addition formulas with in place of , using part 2. Parts 4 to 7 are the addition formulas evaluated at the special angles and , whose sines and cosines are and , so most terms vanish; part 5 combines part 4 with part 2, and part 7 combines part 6 with part 2. Part 8 is the addition formulas with , and part 9 is part 8’s method applied to the quotient defining the tangent, dividing numerator and denominator by .
Proof.
The point lies on the circle of radius about the origin, so by the formula for the Euclidean norm. Where , dividing by gives . Reflecting the circle in the -axis sends to and negates the second coordinate, so and ; the statement for follows by dividing.
Substituting for in the addition formulas and using the parity just proved gives part 3. Taking , where and , gives part 4, and the statement for follows by dividing the two; part 5 is part 3 with together with parity. Taking , where and , gives part 6, and part 3 with gives part 7.
Taking in the addition formulas gives part 8. For part 9, divide
above and below by , which is non-zero wherever both tangents are defined.
With the labelling of the figure below, where , , are the angles at the three vertices and , , are the sides opposite them,
Between them they recover the unknown sides and angles of a triangle from the known ones. The law of cosines is proved once the angle between two vectors has been defined.
Back to Vectors
The angle can now be brought back to , and with it the case of equality in the triangle inequality.
Proposition 1.83 (Equality in the triangle inequality).
Let with . Then
if and only if for some real .
Discussion.
The proof of the triangle inequality used two inequalities, first replacing by and then bounding that by . Equality at the end forces equality at both, so the condition is that is non-negative and that Cauchy–Schwarz is tight. The Cauchy–Schwarz proposition already says when the second happens, namely when one vector is a multiple of the other, and lets us take the multiple in the direction . The sign condition then decides , because has the sign of . The converse is a direct computation with , where the homogeneity of the norm produces the factor without an absolute value.
Proof.
Suppose first that with . Then
using and .
Conversely, suppose the norms are equal. Squaring and expanding as in the proof of the triangle inequality,
so . In particular , which is the case of equality in the Cauchy–Schwarz inequality, so one of , is a multiple of the other; as we may write . Then is non-negative and , so .
Restricted to the cosine is a bijection onto , so it has an inverse , and the Cauchy–Schwarz inequality says exactly that the quotient below is an admissible input to it.
Definition 1.84 (Angle between vectors).
Let be non-zero. The angle between them is
The vectors are orthogonal, or perpendicular, if , that is if .
The definition is legitimate because Cauchy–Schwarz gives , so the quotient lies in . Taking the value of in measures the smaller of the two angles between the vectors. For this agrees with the angle read off the unit circle.
Corollary 1.85 (Law of cosines).
Let be non-zero, with angle between them. Then
Taking and to be two sides of a triangle issuing from the vertex where the angle is , this is the law of cosines .
Proof.
By bilinearity of the scalar product,
and the definition of the angle gives . The third side of the triangle with sides and is , whose length is , while and are and .
Let and . Their norms are
so and . Their scalar product is
so the angle between them is
Proposition 1.87 (Orthogonal projection).
Let with . There is exactly one real for which is orthogonal to , namely
and the resulting decomposition is
Discussion.
The condition to be met is a single scalar equation, , in the single unknown . Bilinearity expands its left side into , which is a linear expression in with coefficient ; that coefficient is non-zero exactly because , so the equation has exactly one solution and existence and uniqueness are settled together. The decomposition is then a matter of adding and subtracting the same vector.
Proof.
For , bilinearity of the scalar product gives
Since we have , so this vanishes for exactly one , namely . Writing is then an identity.
Definition 1.88 (Vector projection).
With as in the last proposition, the vector
is the component of in the direction of , or the vector projection of onto , and is the component of perpendicular to .
Let and in . Compute the angle between them, the projection of onto , and the component of perpendicular to , and verify that the two components are orthogonal.
Let be non-zero. Show that is unchanged when is replaced by for any real .
With as in the last proposition, prove that
and deduce that .
One more construction is available in three dimensions only.
Definition 1.89 (Vector product).
For and in , the vector product, or cross product, is
Unlike the scalar product it returns a vector rather than a number, and it exists only for .
Exercises on Vectors
Let and . Find twice, once by drawing and once by computing, and check that the two agree.
Let , and . Compute
and normalise and .
Give the coordinates of the eight corners of a cube of edge length positioned so that three of its edges lie along the -, - and -axes.
Romeo is at and Juliet is at . How far apart are they?
Find a vector in orthogonal to , and describe all of them.
Exercises on Symmetries and Groups
Let be a two-element set. Show that there are exactly binary operations on , and determine how many of them make a group. Then find a formula for the number of binary operations on a set of elements.
Prove that multiplication of complex numbers is associative.
Which of the following are groups? Justify each answer.
- The complex numbers with , under multiplication;
- under ;
- the rationals with odd denominator, under addition;
- with and , , , ;
- with and , , , ;
- under the vector product .
Let be the set of all real numbers except , and for define . Show that is a group. Check in particular that really is a binary operation on .
Let be a group and . Prove that
- if then ;
- the equation has exactly one solution ;
- .
Let be a group with identity in which for every . Show that is abelian. Then produce infinitely many groups with this property.
Let be a non-empty set and let be permutations of such that every element moved by is fixed by and every element moved by is fixed by . Prove that . (Harder.)
Let be a group and let with . Show that for every . Then exhibit with .
Prove that is abelian only for .
Exercises on Fields and Real Functions
Solve for .
Solve each of the following for .
- ;
- ;
- ;
- ;
- .
In a triangle labelled as in the figure of the last chapter, let , and . Compute .
Let be a field. Prove that and for every .
Show that with and the usual multiplication is a field, and that no field has exactly three elements in which .
Check Yourself
Fresh questions on the whole lesson — none of them is worked out above. Do each on paper first; the box only tells you whether you got there.
Answers are checked in your browser, as often as you like. Nothing is sent anywhere and
nothing is kept but your own work. A formula may be written with the symbols themselves or
with ~ & | -> <-> ^, and \and, \or, \to expand as you type.
Let in . What is ?
Which of these is a unit vector in ?
Which of these vectors is orthogonal to ?
What is the projection of onto ?
What is the angle between and in ?
How many unit vectors in are orthogonal to ?
How many elements has the symmetry group of a regular hexagon?
How many reflections are there among the symmetries of a regular pentagon?
What is the order of the permutation in ?
What is ?
In , what is the inverse of ?
Exactly one of these is not a group. Which?
In the two-element field , where , what is ?
What is ?
What is ?
What is ?
How many solutions has in ?
What is the least upper bound of ?
Which of these sets has no largest element?
For , when is ?
Lesson 2
Recurrences and Sums
Taught
Recurrences
A recurrence defines a quantity at one value of in terms of its values at smaller ones, together with enough starting values. Counting problems often lead to recurrences: removing one piece from a problem of size leaves a problem of the same kind and smaller size. Computing the millionth term from a recurrence means computing the first million, and in this chapter we replace such definitions by formulas that can be evaluated directly.
The Tower of Hanoi
Édouard Lucas put the following puzzle on sale in 1883. Three pegs stand in a row, and on the first of them sit discs of distinct sizes, stacked largest at the bottom and smallest on top. A move lifts the top disc off one peg and drops it onto another, and it is illegal to place a disc on top of a smaller one. The object is to move the whole stack onto a different peg.
Write for the least number of legal moves that carries a stack of discs from one peg to another. With one disc a single move does it, so . With two, the small disc goes to the spare peg, the large one to the target, and the small one on top of it: . With three the shortest solution takes seven moves,
and with four it takes fifteen. The counts so far run .
Proposition 2.1 (The Hanoi recurrence).
For every ,
and .
Discussion.
An equality between two counts is two inequalities, and each is argued differently. For we exhibit a strategy costing that many moves and appeal to being the least cost: shift the top discs to the spare peg, move the largest, shift the back on top of it. The two shifts are legal because the largest disc is out of the way in the first and sits below everything in the second, so neither is obstructed. For we argue about an arbitrary solution rather than a chosen one, and look at the largest disc: it has to move at some point, and at the first moment it moves, the other discs are on one peg and not on either of the two the largest disc occupies, since none of them may sit on top of it or under it in the destination. Reaching that position costs at least , the move itself costs one, and rebuilding the stack afterwards costs at least again.
Proof.
There are no discs to move when , so .
Let . For the upper bound, carry out the following. Move the top discs from the source peg to the spare peg, which is possible in moves and remains legal with the largest disc left in place, since that disc is larger than all of them and lies at the bottom of the source peg. Move the largest disc to the target peg: one move. Move the discs from the spare peg onto the target peg, again moves, legal because they all land on the largest disc. That is moves in all, so .
For the lower bound, take any legal sequence of moves carrying the stack from the source to the target. The largest disc must move at least once. Consider the first move that lifts it. Just before that move the other discs lie neither on the peg it leaves nor on the peg it arrives at: not the first, because they are all smaller and would have to be above it; not the second, because it may not land on a smaller disc. So all of them are stacked on the remaining peg, and getting them there from the source peg took at least moves. After the largest disc has reached the target for the last time, the discs must be brought from that third peg onto it, which takes at least moves more, and the moves of the largest disc themselves account for at least one. Hence .
Closed Forms
Computing from the recurrence means computing through first. What we want instead is a function with for every , evaluable on its own. The sequence sits one below the powers of two, which is enough to make a guess.
Proposition 2.2 (Closed form for the Tower of Hanoi).
For every we have .
Discussion.
The recurrence defines from , so a claim about all is proved by induction, and the induction has exactly the shape of the recurrence: one base case at , and a step that rewrites as , replaces by the inductive hypothesis, and simplifies. The formula was guessed from the first few terms, and the induction checks it.
Proof.
For we have and .
Suppose for some . By the recurrence,
which is the claim at .
Induction proves a formula but does not find one, and guessing from a few terms does not always work. Two ways of finding the formula follow.
Unrolling. Apply the recurrence to itself repeatedly:
The pattern is
and one more application confirms that it reproduces itself:
which is the same expression with in place of . Since the case is the recurrence itself, the displayed identity holds for every with by induction on . Taking leaves in terms of a value we know,
Remark (The geometric sum).
For the step from to , write and doubling gives , so , that is . The same trick evaluates as for any .
Substitution. Rather than solve the recurrence, change the unknown so that the recurrence becomes one we can already solve. Adding to both sides of gives
so if we set then and for . That recurrence doubles at every step, so , and therefore .
Show that in the strategy of the proof of the Hanoi recurrence every disc moves at least once, and that the smallest disc moves on every second move.
Suppose the three pegs are arranged in a row and a disc may only be moved between adjacent pegs, so that a move from the left peg to the right peg is forbidden. Let be the least number of moves needed to transfer a stack of discs from the left peg to the right peg. Find a recurrence for and solve it.
Lines in the Plane
Here is a second problem of the same shape. What is the largest number of regions into which straight lines can cut the plane?
With no lines there is one region, so . One line cuts the plane in two however it is drawn, so . Two lines do best when they are not parallel, giving . At this point invites the guess , and the guess fails at once: a third line meets the two existing ones in at most two points, which divide it into at most three pieces, and each piece splits one old region in two. So the third line adds at most three regions and , not .
Proposition 2.3 (The line recurrence).
For every ,
and .
Discussion.
Again the equality splits into two inequalities about the th line added to already drawn. For the upper bound, the new line gains exactly as many regions as the number of old regions it passes through, and it passes through one more region than the number of points at which it meets the old lines. Two distinct lines meet in at most one point, so the new line meets old lines in at most points and gains at most regions. For the lower bound we must place the new line so that both estimates are attained: not parallel to any old line, which forces it to meet each of them, and not through any existing intersection point, which keeps the meeting points distinct. Only finitely many directions and finitely many points have to be avoided, so such a line exists.
Proof.
With no lines drawn the plane is a single region, so .
Let and suppose lines have been drawn. Adding a line increases the number of regions by exactly the number of old regions that passes through, since each such region is cut into two and no other region is touched. The old lines meet in some set of points, and those points cut into one more piece than there are points, with each piece lying in one old region. Two distinct lines meet in at most one point, so meets the old lines in at most points and therefore passes through at most old regions. Hence .
For the reverse, take lines realising regions. The old lines have directions and finitely many pairwise intersection points, so we may choose parallel to none of them and passing through none of those points. Then meets every old line, in distinct points, so it passes through exactly old regions and adds new ones. Hence .
Unrolling this recurrence gives
using the sum of the first positive integers.
Prove by induction that for every .
Prove that for every , by pairing the first term with the last, the second with the second-last, and so on.
What is the largest number of regions into which lines can cut the plane if all lines are required to pass through a common point?
A zig is a bent line, made of two rays issuing from a common point. Find and solve a recurrence for the largest number of regions into which zigs can cut the plane.
Linear Recurrences
Guessing and unrolling only work when the recurrence is simple. The linear recurrences below can all be solved by one fixed method.
How many ways are there to climb a staircase of steps, if each stride goes up either one step or two? For four steps there are five ways:
There is one way to climb no steps, namely to do nothing, and one way to climb one step. For any ascent begins with either a stride of one, leaving an ascent of steps, or a stride of two, leaving an ascent of ; the two cases are exclusive and exhaust the possibilities. Writing for the number of ascents of steps,
This is the Fibonacci recurrence, introduced in 1202 to model rabbit populations and unsolved in closed form for nearly six centuries. We use rather than from here on, since the argument will shortly be something other than an integer.
Definition 2.4 (Linear recurrence).
A homogeneous linear recurrence of order is one of the form
where are constants with . Values of prescribed at finitely many points are its boundary conditions. Adding a further term on the right, where is a fixed function, gives an inhomogeneous linear recurrence.
The Fibonacci recurrence has order with , and the boundary conditions .
Theorem 2.5 (Solutions form a linear space).
Let and both satisfy the homogeneous linear recurrence with coefficients . Then for all the function satisfies it too.
Discussion.
The recurrence is an identity that must hold at every , so the proof evaluates the right-hand side of the recurrence at and pushes it back to . Two facts are used, both about arithmetic rather than about recurrences: the right-hand side is a sum of terms each of which is a constant times a value of the function, so it distributes over the combination ; and the hypotheses on and let each of the two resulting sums collapse to and . Regrouping the terms is the argument.
Proof.
Let . Using the definition of , then the hypotheses on and ,
Linear recurrences tend to have exponential solutions, so we look for one of the form and find which work. Substituting into gives , and dividing by , which is legitimate since does not satisfy the boundary conditions, leaves
Its roots are
so and both satisfy the recurrence, and by the theorem above so does for every and . The two boundary conditions determine the two constants.
Definition 2.6 (Characteristic equation).
The characteristic equation of the homogeneous linear recurrence is
obtained by substituting and dividing by . Its coefficients are read straight off the recurrence.
Theorem 2.7 (Binet's formula).
Let satisfy and for . Then for every ,
Discussion.
The two exponentials and satisfy the recurrence because and solve the characteristic equation, and the previous theorem then makes every combination a solution as well. What remains is to choose and so that the combination also meets the two boundary conditions, and each condition is one linear equation in the two unknowns. Solving the pair uses and . Since the recurrence and the two boundary conditions determine at every point, the combination found is .
Proof.
Both and satisfy , so multiplying by shows that and satisfy the recurrence; by the previous theorem so does for any reals .
The boundary conditions require and , that is
Substituting into the second gives . Now and , so ; and then , since . Therefore
The recurrence together with the values at and determines for every , and satisfies all three, so .
Remark (Integers out of square roots).
Every value of is a whole number, yet the formula is built entirely from ; the irrational parts cancel at every . Since the second term is smaller than in absolute value at every , so is the nearest integer to throughout. For instance , and . The same estimate shows that consecutive values have ratio tending to , the golden ratio.
Example 2.8 (A recurrence of order two).
Let , and for . The characteristic equation is , that is , with roots and . So , and the boundary conditions give and , whence and . Therefore .
When the characteristic equation has a repeated root, the powers of that root alone do not supply enough independent solutions, and the missing ones carry a factor of .
Proposition 2.9 (Repeated roots).
Let be a root of multiplicity at least of the characteristic equation of a homogeneous linear recurrence. Then satisfies the recurrence.
Discussion.
Saying that is a root of multiplicity at least two means that the characteristic polynomial and its derivative both vanish at , and differentiating a power produces the factor in the claimed solution. So the proof multiplies the characteristic polynomial by to obtain a polynomial whose vanishing at is the statement that solves the recurrence, differentiates it, and multiplies by ; the result, evaluated at , is the statement that solves the recurrence. The hypothesis ensures that is still a root of multiplicity at least after multiplying by .
Proof.
Write for the characteristic polynomial and fix . Put
Since is a root of of multiplicity at least , it is a root of of multiplicity at least , so and . Differentiating,
and multiplying by ,
Evaluating at and using gives
which says exactly that satisfies the recurrence.
More generally, a root of multiplicity contributes the solutions , , , up to , and a recurrence of order is solved by taking a linear combination of the solutions collected in this way from all the roots. If the characteristic equation of an order-four recurrence has roots , and twice, the general solution is
and four boundary conditions give four linear equations in .
Inhomogeneous Recurrences
The Hanoi recurrence is not homogeneous: the extra is a term on the right. Such recurrences are solved in five steps.
- Delete and find the roots of the characteristic equation of what is left.
- Write down the general solution of that homogeneous recurrence, leaving its constants undetermined. This is the homogeneous solution.
- Restore and find any one function satisfying the full recurrence, ignoring the boundary conditions. This is a particular solution.
- Add the two. This is the general solution.
- Use the boundary conditions to fix the constants.
Example 2.10 (Hanoi with heavy discs).
Suppose moving a disc costs its size in seconds, so that moving the th disc takes seconds rather than one. The total time obeys
Deleting the leaves , whose characteristic equation is , so the homogeneous solution is .
For a particular solution, is a polynomial of degree one, so try . Substituting,
which holds for every exactly when and . So is a particular solution and the general solution is .
The boundary condition gives , so and
Against for the original puzzle, the heavy discs cost roughly twice as long.
Remark (Guessing a particular solution).
Finding a particular solution is the one step that involves a guess, and the guess is usually shaped like itself.
If is constant, try ; if that fails, try , then , and so on. If is a polynomial, try a polynomial of the same degree first and then of higher degree. If is an exponential such as , try , then , and so on. A guess fails when substituting it leaves an equation with no constant solution, and raising the degree by one is then the next move.
Solve , , for . (The characteristic equation has a repeated root.)
Solve and for .
Let be a root of multiplicity at least of the characteristic equation of a homogeneous linear recurrence, with . Prove that satisfies the recurrence.
How many ways are there to climb stairs if a stride may go up one, two or three steps? Write down the recurrence, its characteristic equation, and compute the number of ways for .
Let satisfy the Fibonacci recurrence with . Prove that is the nearest integer to for every .
The Josephus Problem
Flavius Josephus, a first-century historian, was said to have been trapped in a cave with forty-one other rebels who preferred death to capture and agreed to stand in a circle and kill every third man. Josephus worked out where to stand.
The version we take is this. Number people to around a circle and go round eliminating every second person until one is left. Which number survives? Call it .
Proposition 2.11 (The Josephus recurrence).
For every ,
and .
Discussion.
With one person, that person survives. Otherwise, after one lap of the circle exactly the even-numbered people have gone, and what is left is a circle of half the size with the same rule about to be applied to it. So the problem of size reduces to the problem of size , and it remains to translate the numbering of the survivors back into their original numbers. The two cases differ only in where the second lap starts. If is even the lap ends by killing person and the next to die is the second of the survivors; if is odd the lap ends by killing person , then person dies immediately, and the survivors start from person . Reading off the th survivor’s original number in each case, and , converts into .
Proof.
With one person there is nobody to eliminate, so .
Suppose with . Going round once eliminates and leaves the odd-numbered people , with the next elimination falling on the second of them. So what remains is the same problem for people, counted from , and the th of those people carries the original number . The survivor is the th of them, so its original number is .
Suppose instead with . Going round once eliminates ; the count then passes from to , which is eliminated next. That leaves the people , with the next elimination falling on the second of them. Again this is the problem for people, and the th of them carries the original number . The survivor is therefore numbered .
Tabulating the first sixteen values, grouped by powers of two, makes the pattern plain:
Each block begins at when reaches a power of two and climbs through the odd numbers. That suggests measuring from the power of two below it.
Theorem 2.12 (Closed form for the Josephus problem).
Let and write where is the largest power of two with , so that and . Then
Discussion.
The recurrence takes to , so the induction hypothesis is needed at a smaller value than , and we use strong induction on . The proof follows how the decomposition changes under halving. If is even then is even too, since and , and halving gives with the remainder still in range. If is odd then is odd, and , again in range. In both cases the hypothesis supplies the survivor for the halved circle, and the two clauses of the recurrence turn it into ; both cases give the same answer.
Proof.
We induct on , assuming the claim for all smaller values.
If then and , and .
Let and write with ; since we have .
Suppose is even, say . Then is even, and
so the decomposition of has exponent and remainder . As , the inductive hypothesis gives , and the recurrence gives
Suppose instead is odd, say with . Then is odd, and
the upper bound because forces and hence . The inductive hypothesis gives , and the recurrence gives
Reading the Answer in Binary
The decomposition can be read off from the binary expansion of . Writing
with leading digit , the remainder is , so and
Since the leading digit that was dropped is a , the answer is obtained from by moving its leading digit to the end: a one-bit cyclic shift to the left.
Example 2.13 (A cyclic shift).
Take . Shifting the leading digit round to the end gives , so . Checking against the closed form, , so and .
Remark (Iterating the shift).
Applying repeatedly does not cycle back to after shifts, because always, and once a value drops it can never climb again. What happens instead is that whenever the leading digit is a it is dropped, so each application deletes a zero. After enough applications only the ones remain, and the value settles at
where is the number of ones in the binary representation of . For instance , and .
Proposition 2.14 (When the survivor is halfway round).
Let with . Then if and only if is odd and .
Discussion.
Substituting the closed form turns the condition into a linear equation in and , and solving it gives ; it remains to find the for which that number is an integer lying below . The bound is immediate. Integrality is a statement about modulo , and the powers of two alternate between and there, since doubling exchanges the two; so is divisible by exactly for odd .
Proof.
By the closed form, says , that is , that is
This satisfies automatically. It is an integer exactly when . Now leaves remainder , and doubling a number that leaves remainder gives one that leaves remainder , while doubling a number that leaves remainder gives , which leaves remainder . So the remainders alternate as , and exactly when is odd.
The first few such are
These are the for which shifting the leading bit to the end has the same effect as deleting the last bit.
The Repertoire Method
Guessing worked for the Josephus recurrence because its answers were small numbers with a visible pattern. When no guess is available, the following method finds the formula. Consider the recurrence with three undetermined constants,
of which the Josephus recurrence is the case , , . Its first few values are
Every entry is a combination of , and with coefficients depending only on , so we may write
and the task becomes finding the three functions , , . The method is to substitute values of , or functions , for which the recurrence can be solved by inspection; each substitution yields one equation relating , and , and three independent equations determine them.
Proposition 2.15 (The coefficient of ).
Let with . Then .
Discussion.
Setting and makes equal to and collapses the recurrence to with in both the even and the odd case. That recurrence doubles once per halving and never distinguishes the two cases, so it depends on only through how often can be halved, which is . The induction is the same strong induction as before, using that has exponent in its own decomposition.
Proof.
Putting and gives and
We induct on . For we have and . For write with , and put . As in the proof of the closed form for , the decomposition of has exponent , whichever parity has. Both clauses of the recurrence give , and the inductive hypothesis gives , so .
Two more substitutions finish the job, and this time we choose the function rather than the constants.
Take for every . The three clauses become , and , so this constant function is the solution for , . Substituting those values into gives
Take . The clauses become , and , so this is the solution for , , , and
Now solve. From and we get ; and then .
Theorem 2.16 (Solution of the generalised Josephus recurrence).
Let be constants and let satisfy the recurrence above. Then for with ,
Discussion.
The repertoire method produced this formula but did not prove it, since it assumed at the outset that is a combination of , and with coefficients independent of them. That assumption is easy to justify after the fact: the right-hand side is a specific function of , so it is enough to check that it satisfies the three clauses of the recurrence, and a solution of the recurrence is unique because the clauses determine from and is given. The verification is the same case split on the parity of used twice already, with the decomposition of read off as before.
Proof.
Write for . Since the clauses determine from for and fix , at most one function satisfies them; so it suffices to check that does.
At we have and .
Let , so and is even, and with . Then
which is .
Let , so and is odd, and with . Then
which is again.
Setting , , recovers , the Josephus answer.
Radix Notation
The generalised recurrence has a shorter description if the two constants added at each step are indexed by the bit that decides between them. Write and ; then the three clauses become
and is the last binary digit of . Unrolling the second clause strips one digit at a time:
The right-hand side has the shape of a binary expansion whose digits are the constants rather than and , so we write it
meaning that each entry is multiplied by the appropriate power of two and the results added. Recovering the earlier table is a matter of reading off digits: gives , and gives .
Example 2.17 (The Josephus survivor for ).
With , and , and ,
agreeing with the cyclic shift .
Nothing in the argument used the base or the multiplier separately, and separating them gives the general statement.
Theorem 2.18 (Recurrences that strip a digit).
Let and be constants, let and be constants, and let satisfy
Then for with ,
Discussion.
The second clause removes exactly one base- digit of its argument, because writing with is the same as splitting off the last digit and leaving . So each application of the clause removes a digit and multiplies what has accumulated by , and after applications the argument has been reduced to its leading digit , which lies between and and is therefore covered by the first clause. The proof is an induction on the number of digits, and the point to check is that the split of into corresponds to the split of the digit string, which is where the uniqueness of base- representation is used.
Proof.
We induct on , the number of digits after the leading one.
If then with , and the first clause gives , which is the claim.
Let and write , that is
Setting and , we have with and . The second clause and the inductive hypothesis applied to , which has digits after its leading one, give
and expanding the bracket is the claim at .
Representation in a Base
The theorem above took for granted that has a base- digit string and that the string is determined by . Both facts need proof, and the tools are the floor function and the remainder.
Definition 2.19 (Floor, ceiling and remainder).
For ,
For and the remainder of on division by is
Rearranging the last definition gives the identity we shall use repeatedly: for and ,
Example 2.20 (Floors and remainders).
and . For the remainder,
Theorem 2.21 (Representation in a base).
Let with . Every with can be written as
and the digits are uniquely determined by .
Discussion.
There are two claims, and they are proved by different means.
Existence is an induction on the number of digits , and the step is division by . Given below , split it as with the last digit and what remains. The point to check is that falls in the range the inductive hypothesis covers, namely below , and it does because dividing by shrinks the bound by a factor of . The hypothesis then supplies digits for , and multiplying the whole expansion by shifts every digit up one place, leaving room at the bottom for .
Uniqueness is an argument by contradiction that needs no induction. Suppose two different digit strings give the same value, and look at the highest place where they disagree. Above the terms are equal and cancel, so the difference of the two sums is a single term , which is at least , plus lower terms, each of which is at least . Summing those lower bounds telescopes to , so the total is at least ; but the total is .
Proof.
Existence. We induct on .
For the only in range is , represented by the empty sum.
Suppose every with has a representation with digits, and let . Put
We check that lies in the range covered by the hypothesis. From we get , and , so ; being an integer, . Also . So the hypothesis applies and gives digits in with . Then
where the third step reindexed the sum by and the last set for , with as chosen. Every digit lies in , and there are of them, which is the claim for .
Uniqueness. Suppose
with all digits in , and suppose the two strings are not identical. Let
and assume without loss of generality that . All terms with agree and cancel, so subtracting one sum from the other leaves
Now bound the two pieces from below. The digits are integers with , so and the first term is at least . For we have and , so and
the last step because the two sums share every term with . Adding the two bounds,
which is false. So no two distinct digit strings represent the same .
Corollary 2.22 (Radix notation).
Let . For a digit string with entries in write
Then every is for exactly one digit string with leading digit , and its length is .
Proof.
Let and put , so that and hence . The theorem applies with this and gives digits , unique for this length. The leading digit is not zero: if then , contradicting .
Conversely, suppose for some length with . Then , and also . So , which forces , and the theorem then makes the digits the ones already found.
Remark (Horner's scheme).
Evaluating by computing each power and multiplying costs about multiplications. Nesting the expression,
evaluates it from the inside out with multiplications and additions, and never forms a power of explicitly. This is Horner’s scheme, and the same nesting evaluates any polynomial at any point.
Compute two ways, from the closed form and from the cyclic shift, and check that they agree.
Show that if and only if for some .
For which does person survive? Show that person never survives, whatever may be.
Use the repertoire method to solve
Write for the number of ones in the binary representation of . Prove that repeated application of to any eventually reaches and stays there.
Let and . Show that the last digits of in base are the digits of , padded with leading zeros if need be.
Sums
A recurrence adds one term to what came before, so every recurrence of the form is a sum, and every sum is such a recurrence. We set up the notation for sums and the laws for rearranging them, and use the correspondence with recurrences to evaluate sums.
Sequences
A sequence of elements of a set is a function . The value is the th term and is written , and the sequence itself is written
A sequence can be given either as a function or as the list of its values. Defining by is the same as writing , and the same again as writing out
A sequence in this sense is infinite, since its domain is, and it is countably infinite: the terms can be laid out in a list indexed by with no term left out.
Definition 2.24 (Countably infinite).
A set is countably infinite if there is a bijection , and we then write .
Removing finitely many elements from leaves a countably infinite set, so the index set of a sequence need not be itself; any countably infinite set of indices will do, and the terms can then be listed in the order that a bijection with supplies.
Example 2.25 (Indexing by another set).
The even numbers are countably infinite, since is a bijection . So is a sequence, with terms listed in that order.
A formula may be undefined at some indices. For
the denominator vanishes at and , so the sequence is the function , and its index set is again countably infinite.
Definition 2.26 (Finite sequence).
A finite sequence of elements of is a function with finite. When is a non-empty set of natural numbers we take and call the length, writing . For the function is empty and is the empty sequence, written .
Sigma Notation
Let be a sequence of real numbers. For we write
Each appearing is a term, the expression after the is the summand, and is the index of summation, which is bound to the and has no meaning outside it.
The notation says: include exactly those terms whose index is an integer between the lower and upper limits, inclusive. That reading has a delimited form and a general form, and the two are interchangeable:
More generally, one or more conditions written under the specify which indices take part. The sum of the squares of the odd positive integers below is
whose delimited form is harder to read; and the sum of the reciprocals of the primes up to is
whose delimited form needs a function counting the primes before it can even be written down.
The general form also makes a change of index easier. Replacing by turns
where the substitution is made directly, while in delimited form the same change reads
and the limits have to be recomputed by hand.
Definition 2.28 (Summation over a property).
Let be a statement about integers which is true or false for each , and suppose only finitely many with true have . Then
If is false for every the sum is empty, and its value is .
Example 2.29 (Reading a condition).
Let be the property ” and is odd”. Then
so
Dropping the parity condition gives and . Taking the summand to be instead gives
Remark (Keeping limits simple).
Terms equal to zero do no harm, and it is usually simpler to leave them in. In
the terms at , and all vanish, and one is tempted to write instead. That form is worse: it is harder to manipulate, and its meaning is unclear when or .
Remark (Two conventions for empty sums).
An empty sum is , which fixes the value of when :
In particular and . With this convention the splitting rules hold without exception: for every ,
the second reading correctly at as .
Iverson Brackets
Kenneth Iverson introduced a device that removes conditions from beneath the altogether.
Definition 2.30 (Iverson bracket).
For a statement that is either true or false, write
A term multiplied by a bracket that evaluates to is taken to be even when the other factor is undefined.
With brackets, a sum over a condition becomes a sum over all integers,
since the terms failing contribute nothing. The index may then be changed freely, without keeping track of the limits. The reciprocals of the primes up to become
and the term at is rather than a division by zero, by the convention in the definition.
Proposition 2.31 (Laws of summation).
Let be a finite set of integers, let and be sequences of reals, and let . Then
- ;
- ;
- for every bijection .
Discussion.
Each law is a property of addition of reals lifted to a finite list of terms, so each is proved by induction on the size of , removing one element at a time. The first is distributivity, the second is associativity and commutativity used together to interleave two lists, and the third is commutativity alone: reordering the terms of a finite sum does not change it, and a bijection of the index set with itself is exactly a reordering. The third is the one that needs the index set to be finite, since rearranging infinitely many terms can change a sum.
Proof.
We induct on . If all three sums are empty and every claim reads .
Let be non-empty, pick and write , so that for any sequence .
For the first law, , using the inductive hypothesis and then distributivity in .
For the second, by the hypothesis, and regrouping the four terms by commutativity and associativity gives .
For the third, let be a bijection and let be arbitrary. Then restricts to a bijection , and splitting both sums so that the term is taken first reduces the claim to the same statement on a set with one fewer element.
The laws above all concern a single index set. Two different index sets combine as well.
Proposition 2.32 (Combining index sets).
Let and be finite sets of integers. Then
Discussion.
Counting elements suggests the shape of the answer, since : an element of both sets is counted twice on each side. Iverson brackets turn membership into numbers that can be added, and the proof adds them. The identity to establish is then for each single , which is checked by looking at the four possible cases; summing it over all and using the law for sums of sequences gives the claim.
Proof.
Fix an integer and compare the two sides of
If lies in both sets, both sides are . If it lies in exactly one, both sides are , the right-hand side because the intersection bracket is and the union bracket is . If it lies in neither, both sides are .
Multiplying by and summing over all integers , the second law of summation gives
and each of the four sums is the corresponding sum over its index set.
Show that and for all statements and , and use the first of these to write
as a sum over all integers with no condition beneath the sign.
Geometric Sums
Definition 2.33 (Geometric sequence).
A sequence of reals is geometric with ratio if for every , equivalently if for every .
Proposition 2.34 (Geometric sum).
Let and . Then
Discussion.
Multiplying the sum by shifts every term one place along, so the product and the original share all their terms but two: the original has where the product has nothing, and the product has where the original has nothing. Subtracting therefore cancels everything in the middle and leaves those two terms, which is a linear equation for the sum. The hypothesis is needed to divide at the end: at every term is and the sum is .
Proof.
Write . By the first law of summation,
Every term of with exponent between and appears in as well, so subtracting leaves only the extremes:
Since we may divide by .
With and ,
Starting the sum at removes the term , so
that is, repeatedly halving what remains of leaves a gap of .
Sums and Recurrences
Writing and splitting off the last term gives , so a sum is a recurrence and the methods of the previous chapter apply to it. The correspondence runs both ways.
Proposition 2.36 (A sum is a recurrence).
Let be a sequence of reals and put . Then is the unique function with
and conversely any function satisfying those two conditions is given by that sum.
Discussion.
Both halves come from the splitting rule for sums, and the convention that an empty sum is makes come out as with no separate argument. For the forward direction, split the last term off the sum defining . For the converse, the two conditions determine the value at every from the value below it, so at most one function satisfies them, and the sum has just been shown to be one.
Proof.
At the sum has the single term , so . For , splitting off the term at gives
Conversely, suppose satisfies and for . Then , and if then . By induction .
Used from left to right, this turns a sum into a recurrence to be solved by the techniques already available. The repertoire method of the previous chapter applies without change, and the recurrence to use is the general first-order one with a linear term.
Theorem 2.37 (A linear sum-recurrence).
Let and let satisfy
Then
Discussion.
Computing , and shows every value to be a combination whose coefficients do not depend on the three constants, so the repertoire method applies: choose functions that satisfy the recurrence for some values of , and read off one equation in , , from each. The constant function forces and gives ; the function forces , and gives ; and the function forces , , which involves and together and so needs the previous two results to finish. Three substitutions give three equations, and the system is triangular.
Proof.
Write , which the first few values show to be the right shape, and determine the coefficients by substitution. Each substitution takes a function , asks which make it satisfy the recurrence, and then reads the displayed identity at those values.
Take . Then forces , and the recurrence reads , that is for every , which forces . Substituting gives
Take . Then forces , and the recurrence reads , that is for every , which forces and . Substituting gives
Take . Then , and the recurrence reads , that is
for every , which forces and . Substituting gives
Assembling, . Finally this function does satisfy the recurrence, as substituting it into confirms, and the recurrence with its boundary condition has only one solution.
Example 2.38 (Summing an arithmetic progression).
Let and . By the correspondence above, and , which is the recurrence of the theorem with , and . Hence
The same answer comes out of the summation laws directly: splitting the summand and pulling out the constants,
since a sum of copies of is . The repertoire method is needed for recurrences that are not sums of anything recognisable.
The Summation Factor
Used from right to left, the correspondence turns a recurrence into a sum. A recurrence of the form is already a sum, so the problem is to bring a given recurrence into that shape, which is done by multiplying through by a suitable factor.
Theorem 2.39 (Summation factor).
Let , , be sequences of reals with and for every , and let satisfy
with given. Put and
Then for every , and
Discussion.
The obstruction to reading the recurrence as a sum is that and carry different coefficients. Multiplying the whole equation by a factor removes the obstruction provided the new coefficient of , namely , equals the coefficient that had at the previous step, namely ; that condition is a recurrence for itself, and unrolling it gives the stated product. With this factor, satisfies , which the previous proposition evaluates as a sum. Dividing by at the end is legitimate because both factors are non-zero, which the hypotheses on and guarantee.
Proof.
For , the definition of gives
with the case reading .
Multiply the recurrence by and set . For ,
and at the recurrence gives . So satisfies a sum-recurrence started at , and unrolling it gives
Since and , dividing by gives the stated formula.
Example 2.40 (The Tower of Hanoi again).
The recurrence has , and , so a summation factor is , which satisfies . Multiplying through,
so obeys and . That is a geometric sum,
and multiplying back by gives .
Remark (Where the factor comes from).
The condition is itself a recurrence, , and unrolling it from produces the product in the theorem. Any non-zero constant multiple of that product serves equally well, since multiplying every by the same constant leaves the condition and the final formula unchanged; in the Hanoi example the product gives and we used . The method needs every and every to be non-zero, and fails otherwise.
Definition 2.41 (Harmonic numbers).
For the th harmonic number is
so that . The name comes from music: the th harmonic of a vibrating string is the tone produced by a string times as long.
Harmonic numbers have no closed form in the sense of this chapter, and they appear when a summation factor is used on a recurrence whose coefficients vary with .
Example 2.42 (A recurrence with variable coefficients).
The Hanoi recurrence had constant and , so the factor was a constant power. Consider instead
with , and . The summation factor is
which agrees with . Then and , while , so the theorem gives
The remaining sum is a harmonic number with its index shifted. Shifting the index by one,
where the middle step dropped the term at and added the one at . Therefore
and checking at gives , which the recurrence confirms.
Use a summation factor to solve and for .
Prove that for every , and deduce that can be made as large as we please by taking large enough.
Exercises on Recurrences
Compute for the Tower of Hanoi for from the recurrence, and check each against .
Suppose a fourth peg is added to the Tower of Hanoi. Give a strategy for discs using the extra peg, count its moves, and compare the count with for .
Solve each recurrence by unrolling, then confirm the answer by induction.
- and for ;
- and for ;
- and for , as far as a sum in closed form allows.
Solve , for by the substitution , choosing so that the recurrence for has no constant term.
What is the largest number of regions into which circles can cut the plane? Find the recurrence and solve it.
What is the largest number of pieces into which planes can cut three-dimensional space? Argue that the answer satisfies , where is the count for lines in the plane, and solve.
For each recurrence, write down the characteristic equation, find its roots, and give the solution meeting the stated boundary conditions.
- , , ;
- , , ;
- , , , .
Solve and for .
Let satisfy the Fibonacci recurrence with . Prove that .
Compute for and check that the values run through the odd numbers below and then restart at .
Suppose the circle is counted in the other direction, so that the first person eliminated is the one before the leader rather than after. Work out the survivor for and find a recurrence.
Use the repertoire method on , , , taking and as two of the substitutions.
Convert to base , base and base , and check each answer by Horner’s scheme.
Let and . Show that and have the same number of base- digits unless is a power of , and use this to count how many numbers have exactly digits in base .
Show that when and otherwise, and that for every .
Exercises on Sums
Some of the regions cut out by lines in the plane are bounded and the rest are not. What is the largest possible number of bounded regions?
Let , with the Josephus survivor. The recurrence for gives and
for every . It therefore looks possible to prove for all by induction. Compute , and , and say exactly what is wrong with the argument.
Let and define , and
assuming and are such that no denominator ever vanishes. Compute and prove that for every .
Evaluate
as a function of and , taking care over the values of for which the sum is empty.
Prove the rule for summation by parts: for every ,
Use only the distributive, associative and commutative laws of summation together with a shift of the index.
Find a closed form for
Use a summation factor to solve
and check your answer at and .
Evaluate by writing the recurrence it satisfies and solving it, and check the answer for .
Show that , first by unrolling the corresponding recurrence and then by writing and cancelling.
Let and be finite sets of integers with . Deduce from the law for combining index sets that , and give an example showing the hypothesis is needed.
Write each of the following as a sum with the index running from , using Iverson brackets where a condition is needed.
- ;
- ;
- the sum of over those between and that are perfect squares.
Check Yourself
Fresh questions on the whole chapter — none of them is worked out above. Do each on paper first; the box only tells you whether you got there.
Answers are checked in your browser, as often as you like. Nothing is sent anywhere and
nothing is kept but your own work. A formula may be written with the symbols themselves or
with ~ & | -> <-> ^, and \and, \or, \to expand as you type.
How many moves does the Tower of Hanoi need for five discs?
What is the largest number of regions four lines can cut the plane into?
In how many ways can a staircase of five steps be climbed, one or two steps at a time?
What are the roots of the characteristic equation of ?
A root of multiplicity of the characteristic equation contributes which solutions?
To find a particular solution of , what should be tried first?
What is ?
What is ?
For which does the Josephus problem leave the last person standing, that is ?
What is in base ten?
What is ?
What is ?
How many binary digits does have?
Unrolling from , what is ?
What is ?
What is the value of ?
What is ?
What is ?
What is the harmonic number ?
Which summation factor turns into a sum?
Lesson 3
Introduction to Python
Taught
Imperative Knowledge and Computation
There are two kinds of knowledge a mathematical text can record. Declarative knowledge is a statement of fact: the square root of a positive real is the positive with . Imperative knowledge is a recipe: a sequence of instructions which, followed to the letter, produces it. A machine can carry out imperative knowledge, but not declarative knowledge.
At its lowest level a computer does two things: it performs arithmetic, and it stores the results. It does billions of operations a second and can store a great deal.
An algorithm is a finite sequence of unambiguous instructions which, given an initial state and a set of inputs, passes through well-defined successive states and after finitely many steps produces an output and stops.
Finite rules out an infinite list of instructions. Unambiguous rules out a step whose meaning depends on who is reading it. After finitely many steps rules out a recipe that never returns an answer; of the three conditions it is the hardest to check.
Writing an algorithm down needs a notation for assignment, which has no counterpart in an equation. We write
for the instruction “replace the current value of by the value of the expression ”. It is an instruction, not a statement about . The instruction makes sense, while the equation has no solutions.
Here is Heron of Alexandria’s method for the square root of a real .
- Choose any guess .
- If is close enough to , stop and return .
- Otherwise perform .
- Go back to step 2.
The procedure has the two ingredients of every algorithm: a list of operations, and control flow, the rule deciding which operation comes next. Control flow depends on tests that answer yes or no, and it lets a fixed piece of text describe a computation whose length is not fixed.
Let and , and read “close enough” as . Compute the first three values of produced by Heron’s method. Does the method stop within those three steps?
Computability
Early machines were fixed-program computers, wired to solve one problem. The stored-program computer keeps the instructions and the data they act on in the same memory, so that a single interpreter can execute any legal instruction sequence handed to it. A program counter walks the interpreter through the instructions in order, deviating only where control flow says to jump. Since the output of a computation may itself be a sequence of instructions, a machine of this kind can write its own programs.
Remark (The Church–Turing thesis).
Turing’s 1936 model, the Universal Turing Machine, is the standard formalisation of what a stored-program machine can do. The Church–Turing thesis asserts that a function is computable by any effective procedure exactly when some Turing machine computes it. It is not a theorem: “effective procedure” is an informal notion, and the thesis says that the formal model captures it.
A language is Turing complete if it can simulate a Universal Turing Machine. Python is, and so is every language in ordinary use, which is why any algorithm expressible in one of them is expressible in all of them.
There is, however, no algorithm which, given an arbitrary program and its input, decides in finitely many steps whether that program eventually stops or runs forever. This is the halting problem. Because it is unsolvable, the clause “stops after finitely many steps” in the definition of an algorithm has to be proved for each algorithm separately; it cannot be checked mechanically.
Syntax and Semantics
A program is a piece of text, and like a mathematical formula it has a meaning only if it obeys three kinds of rule.
- Primitives. The atoms of the language: numeric literals such as
3.2, strings of text, and operators such as+and*. - Syntax. The rules saying which arrangements of primitives are well formed.
3.2 + 3.2is well formed;3.2 3.2is not. Violations are caught before a single instruction runs. - Static semantics. The rules saying which well-formed arrangements have a meaning. Adding a number to a piece of text has the right shape, operand-operator-operand, and still means nothing.
A program that obeys all three has a semantics: exactly one meaning, fixed by the language and not by the reader. An English sentence, by contrast, can have several.
So when a program misbehaves the machine has not misunderstood it: the error is in what was written. It shows up in one of three ways: the program stops with an error, it runs forever, or it finishes and returns the wrong answer. The third gives no sign that anything is wrong, which is why a program needs a proof of correctness and not only a run that looked right.
Classify each of the following as a syntax error, a static semantic error, or a program that runs to completion and returns the wrong answer. Justify each answer in a sentence.
x = 5 + * 3x = "hello" + 7- An implementation of Heron’s method that stops as soon as rather than when is small.
Problems, Words and Languages
To compare algorithms for a problem, the problem has to be stated in a fixed form. A machine reads and writes finite strings of characters, so problems are stated in terms of strings.
Definition 3.2 (Alphabet, word and language).
Let be a non-empty finite set, called an alphabet. For let denote the set of functions . Such a function is written as the sequence
and is called a word, or string, of length over . The set has a single element, the empty word, of length . Writing
for the set of all words over , a language over is a subset of .
A set is finite if there is an injection for some , and infinite otherwise; the number of elements of a finite set is written . A set admitting an injection into is countable, and the infinite ones among them are the countably infinite sets of the last lesson.
Definition 3.3 (Computational problem).
A computational problem is a relation such that every has at least one with . The elements of are the instances of , and is a correct output for the instance whenever .
The problem is unique if is a function, so that every instance has exactly one correct output. It is discrete if and are languages over a finite alphabet, and numerical if and for some . A unique problem with is a decision problem.
A decision problem is one whose answer is yes or no, and we state such a problem by naming its instances and asking the question.
Example 3.4 (Primality as a decision problem).
Taking as a language over the alphabet , the relation
is a decision problem, which we write as
Primality
Input: n ∈ N.
Question: is n prime? A machine works directly only on discrete problems. A numerical problem must have its instances and its answers written as finite strings, and no finite string names an arbitrary real; rounding error is the difference between a real number and the string used for it.
Pseudocode
An algorithm does not depend on a programming language, and is usually written in pseudocode: the control flow and the assignments written out, without the details a particular language would require. An algorithm in pseudocode names its input and its output, and uses for assignment and output for the result.
Example 3.5 (The square of an integer).
Squaring a natural number takes a single instruction, and the algorithm written out in full reads
The Square of an Integer
Input: x ∈ N.
Output: the square of x.
result ≝ x · x
output resultThe variables here are the input x and the working variable result; the assignment puts the value of into result, and output yields what result holds.
We use pseudocode to describe an algorithm and Python to run it.
Objects and Types
A Python program is a sequence of definitions and commands, evaluated in order by an interpreter. A command is called a statement, and it instructs the interpreter to do something. A call to the built-in print writes to the screen: it takes any number of arguments, separates them by spaces, and follows them by a newline unless told otherwise.
print("Algorithm")
print("terminated.")
# several arguments, and a different ending
print("Algorithm", "terminated", end=".\n")
Writing an f before the opening quote of a string makes it an f-string, in which any expression placed in braces is evaluated and its value substituted:
n = 7
print(f"{n} squared is {n * n}") # 7 squared is 49
In mathematics the legality of an operation depends on what it is applied to: addition is defined for numbers, composition for maps. In Python each value is an object, and every object has a type, which fixes the operations allowed on it. The interpreter uses types to enforce static semantics.
Types divide into scalar, meaning indivisible, and non-scalar, meaning having internal structure. Python has four primitive scalar types.
int, the integers, written as usual:5,-12.float, an approximation to the reals, written with a decimal point (3.0,-28.72) or in scientific notation (1.6e-19). Memory is finite and the reals are not, so afloatholds one of finitely many values andfloatarithmetic is not the arithmetic of : the expression0.1 + 0.2 == 0.3evaluates toFalse.bool, inhabited by exactly two values,TrueandFalse.None, inhabited by one value, used for the absence of a result.
Expressions and Relational Operators
Objects combined with operators according to the syntax form an expression, and every expression evaluates to a single object of some type. The built-in type reports which type an object has:
type(5) # int
type(5.0) # float
Control flow needs tests that come out True or False, and these are built with the relational operators <, <=, >, >=, == and !=. Every one of them evaluates to a bool.
Python’s notation differs from the pseudocode here. Mathematical assignment is written in Python with a single equals sign, g = e. Testing whether two expressions have the same value is ==, and testing that they do not is !=.
3.0 + 2.0 # the float 5.0
3 != 2 # the bool True
Give the value of type(4 == 4) and of type(4.0). Then say what happens if the test in step 2 of Heron’s method is written with = in place of ==, and which of the three kinds of rule of the previous section it breaks.
Arithmetic Operators
Addition, subtraction and multiplication are +, - and *, and exponentiation is **. Division splits into three operators, two of which are the floor and the remainder of the last lesson.
/always evaluates to afloat:5 / 3gives1.6666666666666667.//is floor division, the largest integer not exceeding the quotient. So5 // 3is1and-4 // 3is-2.%is the remainder:5 % 3is2. A modern processor carries out%and//in a few clock cycles, so we count each of them as a single elementary operation.
Remark (Floors and remainders in Python).
For integers a and b > 0, a // b is and a % b is as defined in Definition 2.19. The identity therefore reads
a == b * (a // b) + a % band holds for every integer a. It holds for negative a because // rounds down, not towards zero.
Verify that a % b and a - (a // b) * b are equal, first for and , and then in general.
Suppose integer division were defined by truncation towards zero instead, so that divided by gave . What must the remainder then be for the identity to survive, and what happens to the guarantee ?
Compound expressions are read by precedence: ** binds most tightly, then *, /, // and %, then + and -.
print(2 + 3 * 4) # 14, not 20
print(5 + 4 % 3) # 6, not 0 (% binds as tightly as * and /)
print(2 ** 3 * 4) # 32, not 4096
Operators of equal precedence are read by associativity. Most associate to the left; exponentiation associates to the right.
print(5 - 4 - 3) # -2, not 4
print(4 ** 3 ** 2) # 4 ** 9 = 262144, not 64 ** 2 = 4096
print((4 ** 3) ** 2) # 4096
Logical Operators
The three logical connectives, negation, conjunction and disjunction, are implemented as the keywords not, and and or. They act on bool values and are fixed by these tables, in which T and F abbreviate True and False.
a | not a |
|---|---|
| T | F |
| F | T |
a | b | a and b | a or b |
|---|---|---|---|
| T | T | T | T |
| T | F | F | T |
| F | T | F | T |
| F | F | F | F |
So a and b holds exactly when both do, a or b when at least one does, and not a reverses the value. All three are reserved words: they are part of the syntax and cannot be reassigned or used as names.
Both and and or stop as soon as the answer is settled: in a and b the expression b is never evaluated when a is False, and in a or b it is never evaluated when a is True.
(3 != 2) and (5 % 3 == 0) # False, since 5 % 3 is 2
The Operators in One Place
| Category | Operators |
|---|---|
| Arithmetic | +, -, *, /, //, **, %, unary -, unary + |
| Relational | <, <=, >, >=, ==, != |
| Assignment | =, +=, -=, *=, /=, //=, **=, %=, <<=, >>= |
| Logical | and, or, not |
The compound assignments abbreviate an update in place: total += x means total = total + x.
The bitwise operators (<<, >>, &, |, ^, ~, &=, |=, ^=), and with them <<= and >>=, are not used in this lesson.
Variables and State
An equation such as ties to permanently: change and changes with it. Assignment does not. It creates a binding, at one moment in time, between a name and one object in memory, and the binding stands until it is replaced.
pi = 3.14159
radius = 11.0
area = pi * (radius ** 2)
radius = 14.0
The third statement evaluates pi * (radius ** 2) to the single float 380.13239 and binds area to it. The fourth rebinds radius, and area is untouched: it is bound to a number, not to a formula. This lets Heron’s method overwrite its guess at every step while the input stays fixed.
Names and Comments
A program has to be checked by a reader, and a correct program with opaque names is hard to check. The # symbol begins a comment, which the interpreter ignores.
a = 3.14159
b = 11.2
c = a * (b ** 2)
pi = 3.14159
diameter = 11.2
area = pi * ((diameter / 2.0) ** 2)
The two blocks compute different numbers, and only the second makes it visible which one is the area of a circle: the first squares a diameter where it should square a radius, and its names hide the fact.
Multiple Assignment
Several names may be bound at once. Every expression on the right of the = is evaluated in full before any name on the left is rebound, so the two sides do not interfere.
x, y = 2, 3
x, y = y, x
print("x is", x) # x is 3
print("y is", y) # y is 2
Exchanging two values therefore needs no third name to hold one of them.
Start from a, b = 1, 1 and perform a, b = b, a + b three times. Give the values of a and b after each of the three steps, and name the sequence they run through. Then say what a, b = b, a + b would produce if the right-hand side were evaluated one name at a time, left to right.
Numbers in Other Bases
The string is a representation of a number rather than the number itself. Each digit occupies a position whose value is a power of ten:
The rightmost digit sits in the units place, , and each position to its left is worth ten times the one before, so the leftmost of stands for two thousand while the rightmost stands for two. This is a positional numeral system: what a digit means depends on where it sits.
Base ten comes from counting on fingers, not from mathematics. Ten is divisible by only four numbers, and , which limits how easily fractions can be handled; twelve, divisible by and , would serve better. A digital circuit holds two voltage levels, high and low, so machines work in base two, where arithmetic is carried out directly by sequences of logic gates.
That every natural number has exactly one representation of this shape in any base is Theorem 2.21 of the last lesson, and the notation is fixed by its corollary. Read as a statement about digit strings, it says that the base- expansion of is the string satisfying three conditions:
- ;
- every digit satisfies ;
- if then the leading digit is not , and the expansion of is the string in every base.
The first condition says that the digits record how many copies of each power of are wanted; in decimal the places from the right are units, tens, hundreds, and in binary they are . The second restricts the available digits to , and without it uniqueness fails: if were a decimal digit standing for ten, then and would both represent one hundred and two. The third bans leading zeros, without which and would be different strings for one number.
Following the corollary we write
for the number with this expansion, and a string carrying no subscript is decimal.
Remark (Names of the small bases).
The base-, , , and expansions are called binary, ternary, octal, decimal and hexadecimal. Bases past ten need more than the ten digits, and the convention is to carry on with letters: , , and so on up to .
Example 3.6 (One number in six bases).
The decimal expansion of is itself. Since , every power of two from to occurs exactly once and the binary expansion is ten ones. For base thirty-six, , and is while is . Altogether
Python writes the three bases a machine uses with bin, oct and hex, each returning a string carrying a prefix that names the base:
n = 1023
bin(n) # '0b1111111111'
oct(n) # '0o1777'
hex(n) # '0x3ff'
The same prefixes may be typed directly as literals, so the conversions can be checked against each other:
a = 0b1111111111
b = 0o1777
c = 0x3FF
a == b == c == 1023 # True
For any other base, int takes the base as a second argument and reads a string written in it:
int('1101220', 3) # 1023
int('sf', 36) # 1023
Find the binary, ternary, octal, hexadecimal and base- expansions of by hand, using to as the extra hexadecimal digits and to for base thirty-six. Check each against int, and against bin, oct and hex.
A number has octal expansion . Give its decimal value and its hexadecimal expansion.
Let . Write down, in terms of , the smallest and the largest number whose base- expansion has exactly three digits.
Branching
Everything written so far is a straight-line program: the statements run in the order they appear, each exactly once. Such a program is easy to reason about, and limited. If one atomic operation takes one unit of time, a straight-line program of lines takes at most units however large its input, because no line ever runs twice.
The first way to let a program do more is branching: a test is evaluated, and the block of statements that runs next depends on the answer.
Conditional Statements
The basic branching construct is if–else. It consists of a test evaluating to True or False, a block run when the test is True, and an optional else block run when it is False.
Python marks the extent of a block by indentation. Where other languages use braces or an end keyword, Python uses the whitespace itself, so the layout of a program shows its structure.
x = 14
if x % 2 == 0:
print("The integer is even.")
else:
print("The integer is odd.")
print("Branching complete.")
The test uses x % 2, the remainder on division by two, which is the last digit of the binary expansion of x. It is 0 for even x and 1 for odd. The final print is not indented, so it belongs to neither block and runs either way; indenting it by four spaces would make it part of the else and suppress it for even x.
Because indentation carries meaning, a long expression cannot be broken across lines anywhere. A backslash at the end of a line continues it explicitly, and a line inside unclosed brackets continues implicitly.
# explicit continuation
alpha = 1.61803398875 + \
2.71828182845 + \
3.14159265359
# implicit continuation, inside parentheses
beta = (1.61803398875 +
2.71828182845 +
3.14159265359)
Nested Conditionals
A block inside a branch may itself branch, which builds a decision tree.
Where the cases are mutually exclusive, a chain of else blocks each containing one if grows an extra level of indentation per case. The keyword elif collapses the chain: its test is evaluated exactly when every test above it has come out False.
n = 15
if n % 2 == 0:
if n % 3 == 0:
print("n is a multiple of 6.")
else:
print("n is even, but not divisible by 3.")
elif n % 3 == 0:
print("n is odd and divisible by 3.")
else:
print("n is not divisible by 2 or 3.")
Compound Tests and Updating State
Tests may be combined with and, or and not. How they are combined changes how much work the program does.
Take the problem of returning the largest positive number among , and , or, if none of them is positive, the smallest of the three. Each of the three is positive or not, so there are cases, and a program with one branch per case tests the same three comparisons over and over.
Binding a provisional answer and updating it only when a later value beats it replaces the eight cases by three independent tests:
a, b, c = -5, 12, 7
# if none is positive the answer is the smallest, so start there
target = min(a, b, c)
if a > 0:
target = a
if b > 0 and b > target:
target = b
if c > 0 and c > target:
target = c
print(target) # 12
The built-in min returns the smallest of its arguments, and max the largest. Each of the three variables has its sign examined once.
Conditional Expressions
For a choice between two values rather than between two blocks of work, Python supplies the conditional expression, which packs an if–else into a single expression:
expression_if_true if test else expression_if_false
A variable may therefore be bound according to a test without a multi-line block. The absolute value of Definition 1.65 is given by cases, so there are several ways to compute it. The most direct is a standard if block:
x = -10
if x < 0:
x = -x
For a body consisting of a single short statement, Python permits writing the block on the same line as the if:
if x < 0: x = -x # only for very short statements
Branching can also be avoided entirely by using the fact that True and False behave as and in arithmetic:
y = (x < 0) * (-x) + (x >= 0) * x # works, but hard to read
This works, but it is hard to read, it evaluates both branches every time, and it hides the case split inside a multiplication. The conditional expression is clearer:
y = x if x >= 0 else -x
Python also provides the built-in abs for this purpose, so abs(-10) returns 10.
Using conditional expressions and no if statements, write single-line definitions of
- the sign of , which is , or according as is negative, zero or positive;
- the larger of and , without using
max; - the distance between two reals, without using
abs.
Constant Time
Branching does not remove the limit on straight-line programs. Each block is entered at most once on a run, so if one atomic operation takes one unit of time, a branching program of lines can never exceed units, and its maximum running time is hard-bounded by a constant .
Definition 3.7 (Constant time).
An algorithm runs in constant time if there is a constant , depending on the algorithm alone, such that the algorithm performs at most atomic operations on every input, whatever the size or magnitude of that input.
Every straight-line program runs in constant time, and so does every branching program, with the number of lines.
Remark (Beyond constant time).
Constant time is a strong restriction. Consider computing the factorial of an integer . It requires multiplications, and is not bounded, so no fixed number of multiplication statements serves for every ; a program with one hard-coded branch per value of would need infinitely many branches and would violate the finiteness in the definition of an algorithm. The same holds for reading the base- digits of a number, whose count grows like . Computations whose length grows with the input need control flow that can return to an earlier instruction.
Let a, b and c be the coefficients of . Write a program that computes the discriminant and reports whether the polynomial has two distinct real roots, one repeated real root, or none. Treat separately, where the expression is linear rather than quadratic, and say what your program should report when .
Consider the rule which replaces an integer by when is odd and by when is even. Using conditional expressions, write a program that applies the rule three times in succession to a given positive integer. Check that starting from it produces , and find a starting value below from which three applications return the starting value itself.
Iteration
Branching programs are bound by constant time: each instruction is executed at most once, so the whole computation is capped by the static length of the source. The base expansions of the first chapter show the limit. Every step applied the same pair of operations, // and %, to the current quotient, and with no way of repeating those operations automatically we performed each step by hand. Computing the factorial of , testing whether a number is prime, extracting the digits of an arbitrarily large base expansion: these tasks take longer on larger inputs, and no branching program can perform them.
Iteration sends execution back to an instruction already passed.
An iteration, or loop, is a control structure that sends execution back to an instruction it has already passed, so that a block of statements runs repeatedly. How many times it runs is decided by the state of the computation while it runs, not by the length of the program.
The While Loop
Python’s while statement has the syntax of if: a test, a colon, and an indented body. The test is evaluated; if it is True the body runs in full and control returns to the test; if it is False the body is skipped and execution continues after the loop.
The following program computes by adding to a running total times.
x = 3
ans = 0
num_iterations = 0
while num_iterations != x:
ans = ans + x
num_iterations = num_iterations + 1
print(f"{x} squared is {ans}")
Recording the values of the variables each time the test is reached, as though one were the interpreter, is called hand simulation, and it is how we check what a loop does.
| Test evaluation | x | ans | num_iterations | Test |
|---|---|---|---|---|
| 1st | 3 | 0 | 0 | True |
| 2nd | 3 | 3 | 1 | True |
| 3rd | 3 | 6 | 2 | True |
| 4th | 3 | 9 | 3 | False |
On the fourth evaluation num_iterations has reached x, and the program prints 3 squared is 9.
Whether it stops at all depends on x, and there are three cases. If , the test fails at once, the body never runs, and 0 squared is 0 is printed. If , the counter starts below x and rises by exactly one per pass, so after passes it equals x and the loop ends with the right answer. If , the counter runs through and never equals a negative number: the test is never False, and the program runs forever. This is an infinite loop.
Weakening the test to num_iterations < abs(x) stops the loop after abs(x) passes, but each pass still adds the negative number x, so the program would announce that . The body has to be corrected too:
x = -3
ans = 0
num_iterations = 0
while num_iterations < abs(x):
ans = ans + abs(x)
num_iterations = num_iterations + 1
print(f"{x} squared is {ans}") # -3 squared is 9
Example 3.9 (The leading digit).
Floor division by ten discards the last decimal digit, so applying it until one digit is left leaves the first digit. How many times it must be applied is not known before the loop starts, which is what a while loop is for.
n = 72658489290098
n = abs(n)
while n >= 10:
n = n // 10
print(n) # 7 Two further pieces of Python are needed for the problems below. Writing + between two strings joins them end to end, so 'X' + 'X' is 'XX'. And input(prompt) prints its prompt, waits for the user to type a line, and returns what was typed as a string; int converts a string of digits to the integer it denotes, so int(input('n? ')) reads a number.
The program below should print the letter X a given number of times. Replace the comment by a while loop that appends 'X' to to_print exactly num_x times, and say what your loop does when the user enters or a negative number.
num_x = int(input('How many times should I print the letter X? '))
to_print = ''
# append X to to_print num_x times
print(to_print) Leaving a Loop Early
A break statement ends the loop containing it at once and passes control to the first statement after it, without returning to the test.
# the smallest positive integer divisible by both 11 and 12
x = 1
while True:
if x % 11 == 0 and x % 12 == 0:
break
x = x + 1
print(x, 'is divisible by 11 and 12') # 132 is divisible by 11 and 12
The test while True never fails, so the break is the only way out, and the proof that the loop stops is about the break. This arrangement suits a loop whose exit condition is natural to check partway through the body rather than at the top.
Where one loop sits inside another, a break ends only the loop that immediately contains it; the outer loop carries on.
Write a program that reads ten integers, one at a time, and then prints the largest odd number among them, or a message saying that none was odd. Do not use max.
The For Loop
The loops above share a pattern: a counter is set up, tested at the top, and advanced at the bottom of the body. The for statement does that bookkeeping itself. Its form is
for variable in sequence:
body
The variable is bound to the first entry of the sequence and the body runs; then to the second, and the body runs again; and so on until the sequence is exhausted or a break intervenes.
total = 0
for num in (77, 11, 3):
total = total + num
print(total) # 91
The object (77, 11, 3) is a tuple, an ordered finite sequence written in parentheses.
The Range Function
The sequence is most often produced by range, which generates a progression of integers and takes one, two or three arguments.
With one argument, range(stop) gives .
for i in range(4):
print(i) # 0, then 1, then 2, then 3
With two, range(start, stop) gives . The lower end is included and the upper end is not.
total = 0
for x in range(5, 11):
total = total + x
print(total == 5 + 6 + 7 + 8 + 9 + 10) # True
With three, range(start, stop, step) gives , and stops before reaching stop. For positive step the last entry is the largest below stop; a negative step descends instead, so range(40, 5, -10) gives .
total = 0
for x in range(10, 3, -1):
if x % 2 == 1:
total = total + x
print(total) # 9 + 7 + 5 = 21
Choosing the step to land on the right numbers is often more trouble than testing them inside the body. The following sums the odd numbers between m and n whatever the parity of m:
m, n = 4, 10
total = 0
for x in range(m, n + 1):
if x % 2 == 1:
total = total + x
print(total) # 5 + 7 + 9 = 21
The two-argument form covers the one-argument form: range(0, 3) produces the same sequence as range(3). The entries are produced one at a time as the loop asks for them rather than stored all at once, so range(1000000) costs no more memory than range(3).
Remark (Which loop to use).
A for loop is the right choice when the number of passes is settled before the loop begins, since range then states that number and no counter can be mismanaged. A while loop is the right choice when it is not: the leading-digit program above stops when the number falls below ten, and how many divisions that takes is a fact about the input rather than about the program.
Example 3.10 (Squaring by repeated addition again).
The squaring program loses its counter entirely when written with for:
x = -3
ans = 0
for num_iterations in range(abs(x)):
ans = ans + abs(x)
print(f"{x} squared is {ans}") # -3 squared is 9There is no explicit test and no explicit increment; range supplies both.
Reassigning the Loop Variable
Assigning to the loop variable inside the body does not disturb the loop.
for i in range(2):
print(i)
i = 0
print(i)
This prints 0, 0, 1, 0 and stops. The sequence is fixed when the for statement is first reached, and at the start of each pass the variable is rebound to the next entry of it, whatever happened to the variable in between. The loop is equivalent to
index = 0
last_index = 1
while index <= last_index:
i = index
print(i)
i = 0
print(i)
index = index + 1
For the same reason, changing a variable that was used in the range call has no effect, because the call is evaluated once:
x = 1
for i in range(x):
print(i)
x = 4
# prints 0, and nothing else
An inner for is a different matter: its range call is reached afresh on every pass of the outer loop and so is evaluated again.
x = 3
for j in range(x):
print('Outer')
for i in range(x):
print(' Inner')
x = 2
The outer range(x) is evaluated once, with , so the outer loop makes three passes. The inner range(x) sees on the first pass and afterwards, giving inner passes in total.
Iterating Over a String
A string is a sequence of characters. Its positions are numbered from , so a string of length occupies positions : len(s) gives the length, s[i] gives the character at position i, and the slice s[i:j] gives the characters at positions i up to but not including j. That upper end is excluded for the same reason it is excluded from range, and the two conventions agree: s[0:len(s)] is the whole of s, and the positions it covers are exactly those produced by range(len(s)).
Combined with in, the for statement walks a string directly, binding the variable to one character at a time and dispensing with the positions altogether.
total = 0
for c in '12345678':
total = total + int(c)
print(total) # 36
Write a program that computes with a for loop, and check the result against the closed form for an arithmetic progression obtained in the last lesson. Then do the same for , once by choosing the step of the range and once by testing inside the body.
Nested Loops
A loop inside a loop runs the inner loop to completion on every pass of the outer one.
An block of asterisks needs one loop for the rows and one for the columns:
n = 5
for row in range(n):
for col in range(n):
print('*', end='')
print()*****
*****
*****
*****
*****The bare print() after the inner loop ends the line. Letting the inner range depend on the outer variable changes the shape:
n = 5
for row in range(n):
print(row, end=' ')
for col in range(row):
print('*', end=' ')
print()0
1 *
2 * *
3 * * *
4 * * * *Row carries asterisks, so the block becomes a triangle.
Continue and Pass
Two keywords sit alongside break. A continue abandons the rest of the current pass and goes straight to the next one: back to the test for a while, on to the next entry for a for. A pass does nothing, and exists because Python’s syntax requires a statement in places where no action is wanted.
for n in range(200):
if n % 3 == 0:
continue
elif n == 8:
break
else:
pass
print(n, end=' ')
# 1 2 4 5 7
The multiples of three never reach the print, the loop ends when n reaches , and the remaining values pass through the else branch and are printed.
Divisibility and Primality
Definition 3.12 (Divisibility).
Let . We say divides , written , if for some ; equivalently, for , if . In Python this is the test n % d == 0.
Definition 3.13 (Prime and composite).
An integer is prime if its only positive divisors are and , and composite otherwise. The integers and and the negative integers are neither.
Checking that is composite takes a single operation once the divisor is known, since 91 % 7 == 0 settles it; finding the divisor is where the work lies. Read as an instruction, the definition says to test every candidate between and and to stop at the first one that divides.
n = 91
is_prime = n >= 2
for factor in range(2, n):
if n % factor == 0:
is_prime = False
break
if is_prime:
print(f'{n} is prime')
else:
print(f'{n} is not prime')
# 91 is not prime
One divisor settles the question, so the loop stops at the first one it finds. Wrapping the whole thing in a second loop lists the primes below a bound:
for n in range(2, 100):
is_prime = True
for factor in range(2, n):
if n % factor == 0:
is_prime = False
break
if is_prime:
print(n, end=' ')
# 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97
Trial Division to the Square Root
Testing every candidate up to is more than necessary.
Proposition 3.14 (A composite number has a small divisor).
Let . Then is composite if and only if some integer with divides .
Discussion.
Divisors come in pairs: if then and are both divisors and their product is . A product of two numbers both exceeding exceeds , so the two members of a pair cannot both lie above , and one of them is at or below it. The proof takes the least divisor above ; its partner is then the larger of the two, and the inequality can be squared. The converse direction is immediate: a divisor in that range is neither nor , because once .
Proof.
Suppose is composite. The set contains , so it is non-empty; let be its least element and write with , so that and .
We first rule out . If then . But is composite, so it has a divisor with ; that lies in and is smaller than , contradicting the minimality of . Hence , so and therefore . Consequently
so .
Conversely, suppose and . From we get , so , and by hypothesis. Thus has a positive divisor other than and , so it is composite.
Only the integers up to need be tested, and can be dealt with separately so that the loop skips the even candidates:
n = 1010809
is_prime = n >= 2
if n > 2 and n % 2 == 0:
is_prime = False
else:
max_factor = round(n ** 0.5)
for factor in range(3, max_factor + 1, 2):
if n % factor == 0:
is_prime = False
break
print(f'{n} is prime: {is_prime}') # 1010809 is prime: True
round(n ** 0.5) returns either or one more, never less, so the loop may test one candidate too many and never one too few.
The first program tests up to candidates and the second about : for , about a million tests against about five hundred. Python’s time module measures it. The statement import time makes the module’s tools available, and time.time() returns the current time in seconds. A colon inside the braces of an f-string says how to format the value: :.2f prints it with two digits after the decimal point.
import time
n = 1010809
time0 = time.time()
is_prime_basic = True
for factor in range(2, n):
if n % factor == 0:
is_prime_basic = False
break
time1 = time.time()
print(f'Basic: {(time1 - time0) * 1000:.2f} ms')
time0 = time.time()
is_prime_fast = n >= 2
if n > 2 and n % 2 == 0:
is_prime_fast = False
else:
max_factor = round(n ** 0.5)
for factor in range(3, max_factor + 1, 2):
if n % factor == 0:
is_prime_fast = False
break
time1 = time.time()
print(f'Optimised: {(time1 - time0) * 1000:.2f} ms')
print(is_prime_basic == is_prime_fast) # True
The two agree, and on an ordinary machine the first takes tens of milliseconds where the second takes a small fraction of one.
Searching for the th Object
Finding the th number with a given property is a different kind of problem: the answer is not known in advance, so there is no range to run over. A counter of successes and a candidate that advances by one at a time turn it into a while loop.
target = 5
found = 0
guess = 0
while found <= target:
guess = guess + 1
if guess % 4 == 0 or guess % 7 == 0:
found = found + 1
print(f'Counting from zero, entry {target} of the multiples of 4 or 7 is {guess}')
# entry 5 is 16
The multiples of four or seven begin , and counting from zero the entry numbered is . Substituting the primality test for the divisibility test finds the th prime:
target = 10
found = 0
guess = 1
while found <= target:
guess = guess + 1
is_prime = guess >= 2
if guess > 2 and guess % 2 == 0:
is_prime = False
else:
max_factor = round(guess ** 0.5)
for factor in range(3, max_factor + 1, 2):
if guess % factor == 0:
is_prime = False
break
if is_prime:
found = found + 1
print(f'Counting from zero, prime {target} is {guess}') # prime 10 is 31
The primes are , and counting from zero the one numbered is .
Write a program that prints the sum of the primes strictly between and , using a primality test nested inside a loop over the odd integers from to . Then say how many candidate divisors your program tests in total, and how many the version without the square-root bound would test.
Digits in an Arbitrary Base
In the first chapter we read off base- expansions by hand, dividing by , recording the remainder as the next digit from the right, and continuing with the quotient until it reached zero. Automating that needs a way of repeating instructions until a condition is met, which the while loop provides. The number of repetitions is the number of digits, and it is not known before the divisions are done.
Proposition 3.15 (Digit extraction).
Let and . Define and . Then for some , the least such is , and for
where is the base- expansion of .
Discussion.
One pass of the loop performs the split , which removes the last digit. The proof identifies explicitly, as the number whose digits are the top digits of , and proves this by induction on . The step is the split applied to , and the remainder is the digit by the uniqueness of quotient and remainder, as in the uniqueness half of Theorem 2.21. The number of steps then follows: the sum defining is empty exactly when , and is at least before that, because its leading digit is not zero.
Proof.
By Corollary 2.22 the number has a unique expansion
We claim that
For this is the expansion itself. Assume it for some and split off the term :
The sum on the right is an integer and , so this is the division of by with quotient and remainder, and by the uniqueness of that division
which is the claim at together with the stated identity for the digits.
At the sum is empty, so . For every term is non-negative and the term is , so . Hence is the least index at which the sequence vanishes.
The loop below carries out the proposition. Digits are produced from the last to the first, so each new one is put in front of what has been built so far, and digits above nine are turned into letters: str(d) turns the number d into the string of its decimal digits, ord(c) gives the code number of the character c, chr(k) gives the character with code k, and the codes of A to Z run consecutively.
n = 12345
b = 17
digits = ''
while n > 0:
remainder = n % b
if remainder < 10:
digits = str(remainder) + digits
else:
digits = chr(ord('A') + remainder - 10) + digits
n = n // b
print(digits) # 28C3
Example 3.16 (Checking the expansion).
Reading back gives
as the proposition says.
The loop above prints nothing when . Say why, in terms of the hypotheses of the proposition, and repair it so that it prints '0'. Then use it to find the base- and hexadecimal expansions of , and check them against the by-hand method of the first chapter.
Write a program that repeatedly asks the user for a string and prints it back, stopping when the user enters 'done'. It should then print 'Bye!' followed by the number of strings entered, not counting 'done'.
Running Time
Timing the two primality tests measured one machine at one time. The number of instructions a program performs depends on the compiler and on the hardware, and we do not in any case know it exactly. We want a statement about the algorithm instead: how the amount of work grows with the input.
Definition 3.17 (Elementary operation and running time).
An elementary operation is one the machine performs in a bounded number of clock cycles independently of the values involved: an arithmetic operation or a comparison on numbers of bounded size, a read or a write of one stored value. Both a // b and a % b are elementary in this sense.
The running time of an algorithm is the number of elementary operations it performs, counted as a function of its input.
Two things remain to be fixed: how the size of an input is measured, and how precisely we count.
Landau Notation
Let . Then is the set of functions for which there exist and with
The constant must not depend on ; were it allowed to, every function would lie in and the definition would say nothing. Nor does the inequality have to hold everywhere: it may fail for finitely many , so changing at the first million values leaves the statement untouched.
Instead of one usually writes
read ” is big-Oh of ”. The equals sign here is not the symmetric one, since is a set and is a member of it; the notation is standard nonetheless.
Take and . For we have and , so
and with meets the definition. Hence .
Take and . For we have and , so
and with serves. Hence .
Here the bound grows at the same rate as itself, where the previous example bounded a cubic by a quartic. Both statements are true, but the cubic-by-quartic one loses information, since grows strictly faster than ; the same argument with gives the sharper . A function lies in for many different , and the useful statement uses the slowest-growing available.
Example 3.21 (When the definition fails).
Not every pair of functions is related this way: is not . Suppose it were, so that for some and all . Dividing by , which is positive, gives for all . But
satisfies and , which contradicts it. So no constant serves, and the direction of an statement is not reversible: holds while does not.
The argument of the first two examples works for any polynomial.
Proposition 3.22 (Polynomials).
Let with every and . Then .
Discussion.
The proof uses one inequality: for and we have , because raising a number at least to a larger exponent cannot decrease it. Applying it to each term replaces every power by the top power, and what is left is the sum of the coefficients, which serves as the constant of the definition. Non-negative coefficients let each term be bounded separately; with negative coefficients the same bound holds after taking absolute values of the coefficients.
Proof.
Let . For each with we have , so since . Adding these inequalities,
Taking , which is a positive constant unless every is , and , the definition is satisfied.
Remark (The limit form).
For a reader who has met limits there is a shorter route to most statements. Suppose from some point on and the ratio tends to a finite limit,
Then : beyond some the ratio stays below , so meets the definition. The cubic example is settled in one line this way, since , and so is the tight bound, since .
The converse fails only because the ratio need not converge at all, and replacing the limit by the limit superior repairs it: holds exactly when
A limit of says more than does. It says that is negligible against rather than merely bounded by a multiple of it, and that stronger relation is written ; so , while is not .
Remark (What $O$ does and does not see).
The notation is insensitive to scaling: if then for any non-zero constants and . It is equally insensitive to any finite initial stretch of the two functions. What it describes is therefore the asymptotic behaviour of and , their behaviour for large inputs rather than at any one input. Constant factors depend on the machine and the compiler, which we do not model, so this is the level of precision we want; for the same reason an bound alone does not decide which of two programs to run on a given machine.
The Cost of Schoolbook Arithmetic
We all learned at school how to add and multiply natural numbers written in decimal: once and are known for the single digits , sums and products of arbitrary numbers follow by working column by column and carrying. These schoolbook methods are algorithms in the sense of the first chapter, and we can count their cost.
Remark (The base does not matter).
There is nothing special about ten here. The same algorithms work, with minor changes, in any base, binary included. Binary is convenient for multiplication, because the table of single-digit products that has to be known in advance is very much smaller.
Throughout this section the size of the input is the number of digits, which for written in base is by Corollary 2.22.
Proposition 3.23 (Cost of schoolbook addition).
Fix a base . Computing by the schoolbook algorithm takes elementary operations, where is the larger of the numbers of base- digits of and of .
Discussion.
The algorithm treats one column at a time, and each column costs at most a fixed amount, however large the numbers are. A single column adds three quantities: the digit of there, the digit of there, and the carry coming in from the column to its right. The first two lie in and the third is or , so there are at most possible columns to deal with, a number fixed once is fixed and independent of . So each column costs at most some constant , there are columns and at most one extra step to write a final carry, and the count is . Padding the shorter number with leading zeros makes both numbers digits long.
Proof.
Write and in base , padding the shorter expansion with leading zeros so that both have digits.
The algorithm works through the columns from right to left. At each column it adds three quantities: the digit of in that column, the digit of in that column, and the carry from the preceding column. The two digits lie in and the carry is or , so there are finitely many possible single-column computations, their number depending on alone. Since is fixed, there is a constant such that every single-column computation takes at most elementary operations.
The algorithm performs one such computation for each of the columns and at most one further step to write a final carry, so its running time is at most . By the proposition on polynomials this is .
No algorithm does better than here, since the output has about digits and writing it down takes that long. Schoolbook multiplication costs more.
Proposition 3.24 (Cost of schoolbook multiplication).
Fix a base . Computing by the schoolbook algorithm takes elementary operations, with as above.
Discussion.
The algorithm has two stages, bounded separately. In the first it forms one partial product for each digit of : multiplying the whole of by a single digit is a column-by-column pass of the kind already costed, so it is , and there are digits of , giving . Shifting a partial product left only decides where its digits are written. In the second stage the partial products are added, and each addition involves numbers of at most digits, so by the previous proposition each costs and the of them cost . Two stages of make .
Proof.
Write and in base , padded to digits each.
The algorithm forms partial products, one for each digit of . Forming the one belonging to means multiplying by each of the digits of , one column at a time and carrying where needed, and then shifting the result places to the left to obtain . Each single-digit product with a carry is one of finitely many computations depending on alone, so each partial product costs and all of them cost .
It then adds the partial products. Each is at most digits long, so by the previous proposition each addition costs , and of them cost .
Both stages are , so there are constants bounding each by a multiple of beyond some point; adding the two bounds gives a constant bounding the total, and the running time is .
Remark (Faster multiplication).
Multiplication algorithms asymptotically better than the schoolbook one do exist, though they are a great deal more complicated. The first was found by Karatsuba in 1960 and multiplies two -digit numbers in operations with . More recently Harvey and van der Hoeven gave an algorithm, which is believed to be the best possible.
This last result also shows a limitation of the notation. The running time is bounded by for some constant , but is very large: the algorithm beats its rivals only on enormous inputs, and at the sizes that arise in practice methods with worse asymptotic behaviour and smaller constants are faster.
The Cost of Trial Division
Remark (The cost of the obvious method).
The definition of primality suggests testing, for a given , every pair with to see whether . That is up to multiplications. Today’s machines perform several billion operations a second, and even so an eight-digit input on this method would occupy one of them for several hours.
Trial division does much better.
Proposition 3.25 (Cost of trial division).
Deciding whether is prime by testing every candidate divisor from to takes elementary operations.
Discussion.
Each pass of the loop performs a fixed amount of work, one remainder and one comparison, both elementary, so the running time is a constant multiple of the number of passes. The loop runs over the integers from to and may stop early at a divisor, so the count is at most ; the floor is at most , and the constants are absorbed by the . That stopping at rather than at is correct is the proposition on small divisors.
Proof.
By the proposition on small divisors, is composite exactly when some with divides it, so the loop over decides the question. It makes at most passes, and each pass computes one remainder and one comparison, which is a bounded number of elementary operations. The total is at most , which is .
Remark (Avoiding the square root).
It is not obvious that can itself be computed with an elementary operation, and the algorithm need not compute it. Increasing the candidate by and stopping as soon as tests exactly the same candidates and uses only multiplication and comparison. Every step of the algorithm is then well defined for every admissible input, the algorithm stops after finitely many steps, and it returns the right answer, so it computes the function taking the value yes exactly at the primes. We write or for the two values as well.
Remark (Fast in $n$, slow in the input).
An bound looks good, but is not the size of the input. The instance handed to the algorithm is the digit string of , whose length is , so is about and is about . Measured against the length of its input, trial division takes exponentially many steps, so a three-hundred-digit number is out of its reach at any realistic speed.
Give the running time of each of the following in notation, as a function of , and justify each answer.
- Summing the integers from to with a loop.
- Summing the integers from to with the closed form.
- Printing every pair with .
- Extracting the base- digits of by repeated division.
A product may be computed by adding to a running total times, using no multiplication at all. Let and have digits in base .
- Give the running time of this method as a function of and .
- Evaluate that count and the of the schoolbook algorithm at for , and . On a machine performing operations a second, say for which of the three the repeated-addition method is still usable.
- Both methods perform additions of -digit numbers, and only one of them is out of reach for a twenty-digit input. Say which quantity in your answer to the first part is responsible, and why measuring the input by rather than by is what makes the difference visible.
Two programs settle the same problem on inputs of size . The first performs elementary operations, the second .
- Find every at which the second is the faster, and the size at which the first overtakes it.
- Give the running time of each in notation, and say what those two statements do and do not tell you about which program to run.
- A machine performs operations a second and no input ever exceeds . Which program should be run, and how long does it take?
Search and Approximation
The loops of the previous chapters mostly checked something: whether any candidate divides , whether every character of a string is a digit. The loops of this chapter search for a value: they try candidates until one works.
Exhaustive Enumeration
The simplest search strategy is the one the primality test already used: try every candidate in some collection until one works. When the collection is finite, or can be made finite by bounding the range, this is exhaustive enumeration, and it finds a solution whenever one exists.
Cube Roots
Suppose we want the integer cube root of a perfect cube. Given an integer , we seek an integer with , or a report that no such integer exists.
The strategy is direct: test in order until either , a success, or , a failure, the latter being conclusive because implies for non-negative integers. For negative the cube root is the negative of the cube root of .
n = 27
x = 0
while x ** 3 < abs(n):
x = x + 1
if x ** 3 != abs(n):
print(f'{n} is not a perfect cube')
else:
if n < 0:
x = -x
print(f'Cube root of {n} is {x}')
# Cube root of 27 is 3
Hand simulating for :
| Test evaluation | x | x ** 3 | x ** 3 < 27 |
|---|---|---|---|
| 1st | 0 | 0 | True |
| 2nd | 1 | 1 | True |
| 3rd | 2 | 8 | True |
| 4th | 3 | 27 | False |
The loop stops with , and since the program reports a cube.
Trace the program above for , and . In each case build the hand-simulation table, state the value of x when the loop stops, and say whether the program reports a perfect cube.
Example 3.26 (The speed of exhaustive search).
Take . Its cube root is , so the loop makes passes and finishes at once. Take instead , whose cube root is : the loop makes nearly two million passes and still finishes in well under a second. At billions of instructions a second, a million passes take a fraction of a second.
Termination and Decrementing Functions
Every loop in a correct program must stop, unless it is deliberately endless. To prove that one does, we exhibit a quantity that strictly decreases at every pass and cannot go below zero.
Definition 3.27 (Decrementing function).
A decrementing function for a loop is an integer-valued expression in the program’s variables such that
- whenever the loop test holds, and
- strictly decreases at every pass of the body.
A loop admitting a decrementing function stops after at most passes, where is the initial value of .
With it, “the loop eventually stops” can be proved. For the cube-root search a suitable choice is , written with the ceiling of the last lesson, which may also be had from the floor as .
Theorem 3.28 (Termination of the cube-root search).
The exhaustive cube-root search stops for every integer .
Discussion.
The variable increases rather than decreases, so it is not itself a decrementing function; what decreases is the distance from to the value at which the loop stops, and the loop stops once reaches , that is, once reaches . Since is an integer this value is rounded up, which is where the ceiling enters. The two conditions of the definition then have to be checked separately: non-negativity comes from the loop test, since the test holding means has not yet reached the target, and the strict decrease comes from the body, since the body adds exactly to and does not change the target.
Proof.
Put . Initially , so .
Suppose the loop test holds. Then , and both sides being integers gives , so in particular .
Each pass replaces by and changes nothing else, so falls by exactly . Being a non-negative integer that falls by each pass, can do so at most times, and the loop stops after at most that many passes.
Note (Decrementing functions as a diagnostic).
When a program appears to run forever, the definition suggests where to look. Identify the quantity that ought to be decreasing; if there is none, the loop has no decrementing function, which suggests that the loop may never end. Otherwise print the candidate at every pass, and if the printed values fail to fall, the fault is in the body. Deleting x = x + 1 from the cube-root search, for instance, leaves at every pass, constant instead of decreasing.
Remark (The cost of exhaustive enumeration).
Exhaustive enumeration is correct and simple, but it can be slow. The cube-root search tests candidates, so its running time is . For that is passes, which is quick; for it is passes, minutes or hours. Bisection search needs about a hundred steps for the same .
Approximate Solutions
The cube-root search demands an exact integer answer, so it works only on perfect cubes. Many numerical problems have no exact answer in the integers, or even in the rationals: is irrational, and no finite program returns it. We ask instead for an approximate answer, within a stated tolerance.
Definition 3.29 (-approximation).
Let be a function, a target value and a prescribed tolerance. An -approximation to a solution of is a value with
The tolerance is chosen by whoever writes the program. A smaller demands a more accurate answer, and can take much more computation.
Exhaustive Search for Square Roots
Exhaustive enumeration adapts to the approximate setting. Rather than the integers we test the values for a small step , and accept the first with .
n = 25
epsilon = 0.01
step = 0.0001
guess = 0.0
num_guesses = 0
while abs(guess ** 2 - n) >= epsilon and guess <= n:
guess = guess + step
num_guesses = num_guesses + 1
if abs(guess ** 2 - n) >= epsilon:
print(f'Failed to find sqrt({n})')
else:
print(f'{guess} is close to sqrt({n})')
print(f'Number of guesses: {num_guesses}')
# 4.999000000001688 is close to sqrt(25)
# Number of guesses: 49990
Roughly candidates are tested before the neighbourhood of is reached, here of them. The answer is not : is within of , which is all that was asked.
Example 3.30 (When the search space misses the answer).
Run the same program with :
n = 0.25
epsilon = 0.01
step = 0.0001
guess = 0.0
while abs(guess ** 2 - n) >= epsilon and guess <= n:
guess = guess + step
if abs(guess ** 2 - n) >= epsilon:
print(f'Failed to find sqrt({n})')
# Failed to find sqrt(0.25)The search fails because , while the guard guess <= n stops it at . The guard was written for , where ; for we have and the upper bound has to be raised.
Example 3.31 (When the step is too large).
Now take with the same . The program runs a long time and then reports failure: the step carries it over every value within of without ever landing on one. Shrinking to repairs that and obliges the program to test some candidates. Starting nearer the answer would help, and presumes we already know roughly where the answer is.
The step controls both the accuracy and the running time, in opposite directions: a smaller step is more accurate and slower.
Bisection Search
Exhaustive enumeration does not use whether was too small or too large. Bisection uses that comparison to discard half the remaining candidates at every step.
Looking up a word in a dictionary works the same way: open it near the middle, and if the word comes later than the page shown, discard the first half and open the second half near its middle.
We state the algorithm on an interval, in the notation of the first lesson.
Definition 3.32 (Bisection search).
Let preserve order on , so that implies throughout. Bisection search solves by maintaining the invariant that a solution lies in and halving the interval:
- compute the midpoint ;
- if is too large, replace by ; if is too small, replace by ;
- repeat until .
After steps the interval has width , where is its initial width.
Implementation
n = 25
epsilon = 0.01
low = 0.0
high = max(1.0, n)
guess = (low + high) / 2.0
num_guesses = 0
while abs(guess ** 2 - n) >= epsilon:
num_guesses = num_guesses + 1
if guess ** 2 < n:
low = guess
else:
high = guess
guess = (low + high) / 2.0
print(f'{guess} is close to sqrt({n})')
print(f'Number of guesses: {num_guesses}')
# 5.00030517578125 is close to sqrt(25)
# Number of guesses: 13
Exhaustive enumeration needed about fifty thousand guesses; bisection needs thirteen. The upper end is max(1.0, n) rather than n, so that lies in whether or , which repairs the failure on .
Example 3.33 (Hand simulating the bisection).
The first four steps for on :
| Step | low | high | guess | guess ** 2 | Action |
|---|---|---|---|---|---|
| 0 | 0.0 | 25.0 | 12.5 | 156.25 | too high, high = 12.5 |
| 1 | 0.0 | 12.5 | 6.25 | 39.0625 | too high, high = 6.25 |
| 2 | 0.0 | 6.25 | 3.125 | 9.765625 | too low, low = 3.125 |
| 3 | 3.125 | 6.25 | 4.6875 | 21.972656 | too low, low = 4.6875 |
Four steps have taken the interval from width to width , and eight more bring it below .
Example 3.34 (Bisection on a larger input).
Exhaustive approximation failed on because no step served: too large and it skipped the root, too small and it needed hundreds of millions of guesses. Bisection starts from and, by the theorem below, needs at most guesses to narrow the interval that far. Run, it stops after , the excess coming from the test being on rather than on the width of the interval.
Convergence
Bisection produces guesses , and each step halves the interval holding the root, so the distance from the guess to the root falls by a factor of two every time. The bound this gives is stated with the logarithm to base , written : the power to which must be raised to give , so that and , and for an that is not a power of two.
Theorem 3.35 (Convergence of bisection search).
Let be the initial interval and the tolerance. Bisection search narrows the interval below after at most steps.
Discussion.
The proof follows the width of the interval. One step replaces the interval by one of its two halves, so the width is halved whichever half survives, and after steps it is the initial width divided by . It remains to find the least for which this is below , by taking of both sides; the ceiling appears because counts steps and must be an integer. The argument resembles the one for decrementing functions, with a width halved at each step in place of a counter decreased by one, and so the number of steps is logarithmic rather than linear.
Proof.
Each step replaces by either or with the midpoint, so the new width is half the old one. After steps the width is therefore , and since the midpoint of an interval of width is within of every point of it, the guess is within of the root.
We need , that is , that is
The least integer meeting this is .
Example 3.36 (Checking the bound).
For on with the theorem gives
and the program used : the test is on rather than on the width, and floating-point rounding can add a step.
Remark (Logarithmic against linear).
Exhaustive enumeration with step tests about candidates; bisection tests about . For and that is of the order of guesses against roughly . Doubling the range doubles the work of the first and adds a single step to the second.
Adapt the bisection program to approximate to within . How many guesses does it need? Compare that with the number exhaustive enumeration with step would need, and with the bound of the theorem.
Machine Arithmetic
The methods above assume that arithmetic on reals is exact: that is , that halving an interval halves it, and that the only error is the tolerance we chose. On a machine none of these holds exactly.
Binary Fractions
A computer stores numbers in binary. Just as the decimal system writes fractions with negative powers of ten, so that , the binary system uses negative powers of two:
Example 3.37 (Exact binary fractions).
The decimal has an exact binary form:
Likewise and . These are exact because their denominators are powers of two: , and .
Not every decimal fraction has a finite binary form.
Theorem 3.38 (One tenth is not a finite binary fraction).
The number has no finite binary representation.
Discussion.
A finite binary fraction has a power of two as its denominator, and the proof compares that with the factor in . Suppose the representation existed with bits. Multiplying it through by clears every denominator at once and leaves an integer on the right, so the supposed identity becomes for an integer . The right-hand side is divisible by and the left is a power of two, and no power of two has among its factors. The same argument gives the general case: a fraction in lowest terms is a finite binary fraction exactly when its denominator is a power of two.
Proof.
Suppose for some bits . Then
Cross-multiplying gives , so divides . But is a product of factors of , and is a prime different from , so divides no power of . The supposition is therefore false.
The same holds in general: a rational in lowest terms has a finite binary expansion exactly when is a power of two, and since , the fraction needs an infinite repeating binary expansion, just as repeats for ever in decimal.
What This Costs in Practice
Python’s float uses the IEEE 754 double-precision format: bits carrying a sign, an -bit exponent and a -bit significand with one further bit implied. That is about to significant decimal digits.
The consequence is that a decimal constant which looks exact in the source is silently rounded to the nearest representable binary fraction. Putting the format specifier :.20f inside an f-string prints twenty digits after the point and exposes it:
print(0.1) # 0.1 (the display is rounded)
print(f'{0.1:.20f}') # 0.10000000000000000555
print(0.1 + 0.1 + 0.1 == 0.3) # False
print(f'{0.1 + 0.1 + 0.1:.20f}') # 0.30000000000000004441
print(f'{0.3:.20f}') # 0.29999999999999998890
The sum overshoots by about , while the literal 0.3 is itself rounded downwards from three tenths. The two errors go opposite ways, so == returns False.
Example 3.39 (A loop that never lands).
Counting from to in steps of a tenth:
x = 0.0
count = 0
while x != 1.0:
x = x + 0.1
count = count + 1
if count > 20:
print('Gave up')
breakThe loop never stops of its own accord. After ten additions x is 0.9999999999999999, not 1.0; the eleventh takes it to 1.0999999999999999, and it has stepped over 1.0 without touching it. Replacing != by < 1.0, or by abs(x - 1.0) >= epsilon, repairs it.
Remark (Never compare floats with $==$).
Do not test floating-point numbers for exact equality. The expression x == 0.3 is almost certainly wrong even when x was computed by a formula mathematically equal to three tenths. Test instead that the difference is small:
x = 0.1 + 0.1 + 0.1
print(abs(x - 0.3) < 1e-9) # TrueThis is an -approximation applied to equality; the tolerance should match the precision of the computation, not of the display.
Example 3.40 (Rounding error accumulates).
Adding a tenth a thousand times:
total = 0.0
for i in range(1000):
total = total + 0.1
print(f'{total:.20f}') # 99.99999999999859312538
print(total == 100.0) # False
print(abs(total - 100.0)) # about 1.4e-12Each addition contributes a rounding error of the order of , and over a thousand additions they accumulate to about : small, but enough to make an exact test fail.
Remark (When it matters).
For the bisection search this error is harmless: an approximate answer was wanted, and is much larger than the rounding. The problem arises in code that assumes exact arithmetic, by testing x == 0.0 where it should test abs(x) < epsilon, or by expecting a counter incremented by 0.1 to arrive exactly at 1.0 after ten steps.
The theorem above rules out , and the paragraph following it makes the general claim: a rational in lowest terms has a finite binary expansion exactly when is a power of two. Prove both directions, following the argument of the theorem. Then say how many bits the expansion of needs when is odd, and give the expansions of and as far as each can be written.
The loop of the example above adds a thousand times and lands about short of .
- Write a second program that adds the integer a thousand times and divides by ten at the end, and compare its result with
100.0using==. - Say why the second is exact where the first is not, in terms of which numbers have a finite binary expansion.
- A sum of money is to be accumulated over many transactions, each an exact number of pounds and pence. Say which of the two arrangements should be used, and what the other would cost after a million transactions.
Exercises on Types and Expressions
Give the type and the value of each expression.
7 / 2;7 // 2;7 % 2;7.0 // 2;2 ** 0.5;1 == 1.0.
State the value of 2 ** 3 ** 2, (2 ** 3) ** 2, -2 ** 2 and (-2) ** 2, and say which rule of precedence or associativity settles each one.
Predict the value of 0.1 + 0.2 == 0.3 and of 0.5 + 0.25 == 0.75, then check both. Explain why the two come out differently, and give another pair of float values whose sum can be tested exactly.
Let n be a positive integer.
- Write an expression for its last two decimal digits.
- Write an expression for the digit in its hundreds place.
- Write an expression that is
Trueexactly whennis a multiple of but not of .
Give the value of int('101', b) for and , and find the base for which it equals .
Explain why x != 0 and 100 % x == 0 may be evaluated for any integer x, while 100 % x == 0 and x != 0 may not.
Explain what a, b = b, a does, and why performing a = b and then b = a does not do the same. Say what the second pair leaves in a and b.
Exercises on Branching
Write a program that reads three integers and prints them in increasing order, using conditional statements only and no built-in sorting.
A year is a leap year when it is divisible by , except that centuries are not, except that those divisible by are. Write a program that reads a year and reports whether it is a leap year, and check it on , , and .
Write a program that reads a real number x and prints which of the intervals , , and contains it.
Write a program that reads three numbers a, b and c and prints how many of them are strictly positive. Use branching only, with no loop and no arithmetic on the results of the tests.
The expressions 1/x if x != 0 else 0 and (x != 0) * (1/x) agree for every non-zero x. Say what each does when x is 0, and explain the difference in terms of which arms of a case split get evaluated.
Explain why a branching program of statements performs at most atomic operations on any input, and give a three-statement program whose count of operations depends on its input.
Write a program that reads three positive reals and reports whether they can be the side lengths of a triangle, that is, whether each of them is smaller than the sum of the other two. Use conditional statements only.
Exercises on Iteration
Write a program that computes for a given with a for loop, and a second that does the same with a while loop. State how many multiplications each performs, and say what each returns for .
Write a program that counts the digits of in base with a loop, for and . Check its count against Corollary 2.22 for several and .
Write a program that sums the decimal digits of a positive integer using // and % only, with no strings. Extend it to repeat the process on the result until a single digit is left, and compare that digit with .
Write a program that prints the first Fibonacci numbers, using a single multiple assignment inside the loop to advance the pair.
Write a program that takes positive integers and and repeatedly replaces the pair by the smaller number and the remainder of the larger on division by it, stopping when the remainder is . Hand simulate the loop on , and identify the surviving number as the largest integer dividing both and .
Write a program that prints every pair with and , for a given . Say how many divisibility tests it performs as a function of .
Modify the trial-division program so that, when is composite, it also prints the least divisor of above . Run it on three numbers near one million of your own choosing, and say which of them are prime.
Write a program that converts a string of base- digits to an integer with a loop, for a given , using Horner’s scheme rather than forming any power of . Count the multiplications, and check the result against int.
Write a program that runs the loop replacing by when is even and by when is odd, stopping when reaches , and counts its passes. Run it on each starting value from to , and report which takes the most.
Exercises on Running Time
Show directly from the definition that , giving an explicit and , and that as well, so that each of the two is of the other. Then do both again with the limit form.
Decide which of the following hold, with a proof or a counterexample in each case.
- ;
- ;
- ;
- .
Suppose and . Prove that and that for every constant . Then give functions with and for which fails, so that is closed under sums and constant multiples but not under products.
An input of decimal digits denotes a number of size about . Express the running time of trial division as a function of the number of digits of its input rather than of the number itself, and say how many digits a number may have before an algorithm performing operations a second needs more than an hour.
Count the elementary operations performed by the schoolbook algorithms on two -digit numbers exactly, rather than up to : give the number of single-column additions performed by the addition, and the number of single-digit multiplications performed by the multiplication.
Exercises on Search and Approximation
Give a decrementing function for each of the following loops and state the bound on the number of passes it yields.
while n >= 10: n = n // 10, for ;while a != b: a, b = (a - b, b) if a > b else (a, b - a), for positive integers and ;- the exhaustive square-root search of this chapter, whose body is
guess = guess + step.
The positions of a string are numbered from . Write a program that prints the characters of a string my_str at the even positions, so that 'abcdefg' produces aceg. Write it twice, once with range and indexing, and once with a for loop over the characters and a counter.
Let be an integer with . Write a program that finds by bisection search, printing the number of guesses it needed and the value found. When the midpoint of the current interval falls between two integers, take the smaller.
A positive integer is a perfect power if for integers and . Write a program that reads and prints integers root and pwr with and , or reports that no such pair exists. Test it on , which admits , and , on , and on .
Write a program that approximates for a positive real to within by bisection. It must first find an interval containing : note that , that grows without bound, and that becomes arbitrarily small, so may have to be negative. Test it on , and .
Run the exhaustive square-root search and the bisection search on the same for and , recording the number of guesses each makes. Say which of the two counts grows with and which with , and check the readings against the two bounds.
Find two float values a and b for which (a + b) + c and a + (b + c) differ for some c, and explain the difference in terms of rounding. What does this say about summing a list of numbers in different orders?
Which of the decimals , , , and are exact as float values? Predict the answer from the theorem on binary fractions before checking each with :.20f.
An Applied Exercise
A band puts tickets on sale for one night and prices them dynamically. The first ticket costs . The tickets are sold in blocks of , and after each block the price is raised by of the current price. The band will not charge more than a cap of : once a rise would take the price above the cap, the price is set to the cap and stays there for every remaining block.
- Write a program that sells all tickets under this rule and prints the price of each block together with the total revenue. Report the revenue.
- Give, in terms of , and , the number of rises after which the uncapped price would first exceed the cap, and hence the number of the first block sold at the cap. Check your formula against the output of your program.
- Consider the sequence of price increases between consecutive blocks. Show that before the cap binds these form a geometric sequence and give its ratio. Exactly one increase belongs to neither the geometric stretch nor the capped stretch: identify it, give its value, and say what it would have been without the cap.
- Show that once the cap binds the total revenue is a linear function of the number of tickets sold, and give its slope. Say what the revenue would look like as a function of if the cap were removed.
- Give the running time of your program in notation as a function of and , and say which of the two the running time really depends on.
- Prices in pounds are not exact
floatvalues. Rewrite the simulation in integer pence, rounding each new price down to the nearest penny, and compare the two totals. The two disagree for two separate reasons; say which reason accounts for most of the gap, and which of the two totals the band should quote.
Check Yourself
Fresh questions on the whole lesson — none of them is worked out above. Work each one out on paper or in your head before opening Python; the box only tells you whether you got there.
Answers are checked in your browser, as often as you like. Nothing is sent anywhere and
nothing is kept but your own work. A formula may be written with the symbols themselves or
with ~ & | -> <-> ^, and \and, \or, \to expand as you type.
What is the type of 5 % 2 == 1?
What is the value of 7 // -2?
What is the value of -7 % 3?
What is the value of 9 // 2 ** 2?
After total = 0 and then total += 5 twice, what is total?
What is the value of 2 + 3 * 4 ** 2?
After x, y = 1, 2 and then x, y = y, x + y, what is y?
After x = 5, y = x and x = 7, what is y?
How many values does range(5, 20, 4) produce?
What is the last value produced by range(10, 3, -2)?
In a for i in range(3) loop whose body is a for j in range(i) loop, how many times does the inner body run in total?
Starting from n = 4096, how many times does the body of while n >= 10: n = n // 10 run?
Testing for primality by trial division, what is the largest candidate divisor that has to be tried?
What is the value of int('ff', 16)?
What are the binary digits of ?
Testing whether is prime by trial division up to has which running time?
Multiplying two -digit numbers by the schoolbook algorithm has which running time?
How many bisection steps are needed to narrow to a width below ?
Starting from x = 0, how many times does the body of while x ** 3 < 64: x = x + 1 run?
What is the value of (3 > 2) or (1 / 0 == 0)?
Lesson 4
Data Structures and Graphs
Taught
Data Structures
Every program of the last lesson held a bounded number of values at once: a guess, a counter, a running total, the string being built up. A program that has to keep many values, for example to sort a list of numbers or to remember which candidates have been ruled out, needs somewhere to store them and a way to reach any one of them. A data structure is such a place together with the operations for using it.
We compare algorithms by explicit models of cost, and refine an implementation only once the broad algorithmic choice has been made. For every algorithm there are two things to prove: that it terminates, and that it is correct, meaning that the output it returns is a correct output for its input in the sense of a computational problem.
Functions
From here on an algorithm is usually written as a function: a named piece of code that takes inputs, called its arguments, and returns an output.
def square(x):
return x * x
print(square(7)) # 49
print(square(-3) + 1) # 10
The line def square(x): names the function and its parameter x; the indented body is its code. A call square(7) binds the parameter x to the argument 7, runs the body, and evaluates to the value named by return, which ends the call at once. A function whose body finishes without reaching a return returns None. Names bound inside the body are local: they exist only while that call runs and do not disturb names of the same spelling elsewhere.
def smallest(a, b, c):
target = a
if b < target:
target = b
if c < target:
target = c
return target
target = 100
print(smallest(4, -2, 9)) # -2
print(target) # 100, untouched by the call
A function may return several values at once as a tuple, and the caller may unpack them by multiple assignment: return p, q and then p, q = f(p, q).
In pseudocode we give a function a name and write its arguments in brackets, as in Fact(n), and return has its Python meaning.
Recursion
A function may call itself. The call f(n) then waits while f(n - 1) runs, and that call has its own local names, separate from those of the call that made it. The part of memory that keeps track of this is organised as a stack: each call places a frame holding its local names on top, and returning removes the top frame, so the most recent call is always the first to finish. The number of frames present at once is the depth of the recursion.
Recursion needs a case that does not call the function again, reached after finitely many calls; otherwise the stack grows until Python stops the program with a RecursionError.
def count_down(n):
if n == 0:
print('done')
else:
print(n)
count_down(n - 1)
count_down(3) # 3, 2, 1, done
Write a function digit_sum(n) that returns the sum of the decimal digits of a positive integer , once with a while loop and once recursively, using // and % only. State the depth of the recursion in terms of .
Induction and Loop Invariants
For a statement about every nonnegative integer, proof by induction has two parts: prove , then assume for an arbitrary and use that assumption to prove . If the statement begins at , start with . The base case gives , and the step then gives , and so on. The last lesson used this pattern for the closed forms of recurrences; we now use it for recursive algorithms and for loops.
Definition 4.1 (Loop invariant).
A loop invariant is a statement about the program state that
- holds before the first iteration (initialisation),
- is preserved by every iteration (maintenance), and
- gives the desired conclusion when the loop stops (use at termination).
An invariant plays the part of the induction hypothesis: initialisation is the base case, and maintenance is the step. It says nothing about whether the loop stops. That is proved separately by a decrementing function, often called a variant in this context.
Theorem 4.2 (Chocolate-bar strategy).
A rectangular chocolate bar has a marked corner square. A move removes a nonempty strip by one horizontal or vertical cut, retaining the piece containing the marked square. The player who receives the bar loses. The first player has a winning strategy exactly when the starting rectangle is not a square.
Discussion.
Write for the current numbers of rows and columns. The strategy is to hand the opponent a square every time: from a nonsquare, cut the longer side down to the shorter. The invariant is the pair of facts that the strategy user always receives a nonsquare and always hands over a square; initialisation is the starting position, and maintenance holds because every legal move from a square produces a nonsquare. The product falls with every move, so it serves as the variant and play reaches . Since is a square, the strategy user never receives it. The two cases of the statement say which player can use the strategy.
Proof.
Write for the current positive numbers of rows and columns. If , the player to move can cut the larger coordinate down to the smaller one and hand the other player a square. Every legal move from a square changes exactly one coordinate, so it makes the coordinates unequal; the next player can therefore make a square again. The invariant is that the player using this strategy always receives a nonsquare rectangle and always hands over a square.
Every move strictly reduces the positive integer , so play eventually reaches . The strategy user cannot receive , since that state is square. If the initial rectangle is nonsquare, Player One uses the strategy; if it is square, Player Two uses it after Player One’s first cut.
Starting from , Player One cuts to . If the opponent cuts to , Player One cuts to and the opponent loses.
One full round can be implemented as a loop: check whether Player One has received , let Player One move, check whether the opponent has received , then let the opponent move. Player One’s move is the square-producing cut:
Player One's Move
Input: the numbers p, q of rows and columns of the retained rectangle.
Output: the rectangle after the move.
if p > q then p ≝ q
else if q > p then q ≝ p
else p ≝ p − 1
return p, q
def player(p, q):
if p > q:
p = q
elif q > p:
q = p
else:
p = p - 1 # reached only from a square
return p, q
For a nonsquare starting position, the last branch is never reached under the winning strategy. The opponent may choose any legal cut; the proof above covers all such choices.
Recursive and Iterative Factorials
Recursive and iterative factorial functions show the connection between induction and invariants. Define and for .
Recursive Factorial Iterative Factorial
Input: n ∈ N₀. Input: n ∈ N₀.
Output: n!. Output: n!.
Fact(n): r ≝ 1
if n = 0 then return 1 for j ≝ 1 to n do r ≝ r · j
else return n · Fact(n − 1) return r
The recursive version returns at and otherwise returns ; induction on proves it correct, the base case being the first branch and the step the second. The iterative version begins with and multiplies by for . Before iteration the invariant is ; after the multiplication it becomes , so at exit . The variant decreases to zero.
def fact_recursive(n):
if n == 0:
return 1
return n * fact_recursive(n - 1)
def fact_iterative(n):
r = 1
for j in range(1, n + 1):
r = r * j
return r
print(fact_recursive(10), fact_iterative(10)) # 3628800 3628800
The recursive version has depth : the call for waits on the call for , down to . The iterative version uses a single frame. Python integers have no fixed size, so both return exactly however large it is. A product of large integers is then not an elementary operation, and costs what the schoolbook bound says.
The loop below is meant to compute for an integer and with about multiplications.
def power(a, n):
result, base, e = 1, a, n
while e > 0:
if e % 2 == 1:
result = result * base
base = base * base
e = e // 2
return resultShow that is a loop invariant, give a variant, and deduce that power is correct. How many multiplications does it perform, in terms of the binary expansion of ?
In the chocolate-bar game a move may instead remove a strip of width one or two only. Decide, for each starting rectangle with , which player has a winning strategy, and state and prove a rule covering every .
Measuring Cost
A program can spend most of its running time in a small part of its code. Choosing a better algorithm for that part usually does more than rewriting individual instructions, and much of the low-level rewriting is done automatically in any case by the software that translates a program into machine instructions. Costs other than time and memory can matter too, energy and money among them. For example, a “Penny Sort” measure asks how many externally stored records can be sorted for a US cent, after the purchase cost of a fixed machine is spread over an assumed working life. That measure combines hardware cost and algorithm throughput.
Example 4.4 (Which method is faster depends on ).
Suppose method A uses exactly operations on an input of size , while method B uses exactly . At , A uses operations and B uses ; at , A uses and B uses . The method with fewer operations depends on the input size, and the two cross at .
The running time of the last lesson counted elementary operations. We also measure memory.
Definition 4.5 (Time cost and auxiliary space).
The time cost of an algorithm counts operations in a stated model. Its auxiliary space, or memory footprint, is the maximum amount of extra storage present at any instant of a run, excluding the input.
Storage may be released and reused later, which is why the footprint is a maximum and not a cumulative total. In a conventional model allocating or touching a memory cell costs at least one operation, so an algorithm’s footprint cannot exceed a constant multiple of its running time. This claim depends on the model, and it does not say that every algorithm uses as much space as time.
The unit-cost assumption has to be stated with care. A comparison of two strings is not necessarily a single operation: it may inspect many characters. A comparison of two integers of bounded size is commonly counted as one operation, but for arbitrarily large integers or variable-length records the cost must be stated.
Lower and Two-Sided Bounds
Landau’s from the last lesson is an upper bound. It has a lower counterpart, and the two together give a two-sided bound.
Let . We write
As with , the equals sign is shorthand for membership of a set of functions, not equality of functions. The last lesson wrote , read ” grows strictly more slowly than ”, when tends to zero. Written out without limits: from some point on, and for every there is an with for every . This is stronger than , since the constant multiplier can be made as small as we wish by taking large enough.
The growth rates met most often are, from slowest to fastest,
Logarithm bases differ only by a constant factor, since by the change of base; so and are the same class and the base is usually left off.
Show that by exhibiting the constants, and that .
Lower Bounds
The comparison model of computation acts on a set of comparable objects. The objects are treated as black boxes supporting only binary tests called comparisons, namely , , , , and : each takes two objects and returns True or False according to their relative order. Nothing else about the objects may be inspected, so an algorithm in this model learns about its input only through the answers to comparisons, and its cost is counted as the number of comparisons it makes.
Definition 4.7 (Problem lower bound and optimality).
A problem lower bound is a bound that applies to every algorithm solving the problem in a specified model. An algorithm is asymptotically optimal for a cost measure when its upper bound matches the problem’s lower bound up to constants.
An upper bound is about one algorithm, and a lower bound is about every algorithm for the problem. A lower bound therefore has to fix a model, which lists the operations an algorithm may use.
Proposition 4.8 (Finding a maximum).
Finding the position of the maximum among pairwise distinct, otherwise unordered comparable elements requires at least comparisons in the comparison model, and comparisons suffice.
Discussion.
For the lower bound we count losses: an element can be ruled out as the maximum only once it has been seen to be smaller than something, and one comparison produces exactly one loser. All non-maximal elements must be ruled out, so at least comparisons are needed. For the upper bound a single left-to-right scan with a running maximum makes one comparison per element after the first, and its invariant says that the stored index is maximal in the prefix scanned so far.
Proof.
Before an element can be ruled out as the maximum, it must lose a comparison against a larger element. One comparison can give a first loss to at most one candidate. All but the true maximum, namely candidates, must be ruled out, so at least comparisons are necessary.
A scan keeps the index of the largest element seen and compares each of the remaining elements with it once; its invariant is that the stored index is maximal in the scanned prefix. Thus it meets the lower bound.
A second proof uses an adversary, an imagined opponent who may change the input as long as every answer already given stays true.
Proof.
Fix an input of distinct values and a nonmaximum element . Suppose is never compared with an element larger than itself. Change only its value, to one larger than every original value. Every comparison involving previously had a smaller other operand, so its outcome stays the same; all other comparisons are unchanged. A deterministic comparison algorithm therefore follows the same sequence of branches and returns the same index. But is now the true maximum, a contradiction. Thus each nonmaximum element must lose a comparison, which gives losses.
The scan in pseudocode and in Python:
Position of the Maximum
Input: a sequence T[0], …, T[n − 1] of comparable elements, n ⩾ 1.
Output: an index k with T[k] maximal.
k ≝ 0
for i ≝ 1 to n − 1 do
if T[i] > T[k] then k ≝ i
return k
def arg_max(T):
k = 0
for i in range(1, len(T)):
if T[i] > T[k]:
k = i
return k
print(arg_max((3, 9, 2, 9, 4))) # 1
Python’s len(T) gives the length of a tuple, as it does for a string, and T[i] its entry at position i.
Give an algorithm that finds both the maximum and the minimum of distinct elements with at most comparisons. Then use an adversary to show that at least comparisons are necessary.
The Word-RAM Model
To calculate the resources an algorithm uses we need to say how long a computer takes to perform basic operations. Fixing such a set of operations gives a model of computation, on which the analysis is then based. We use the -bit Word-RAM model, which treats a computer as a random-access array of machine words called memory, together with a processor that performs operations on that memory.
A machine word is a sequence of bits, read as an integer in . A Word-RAM processor performs each of the following in constant time:
- addition, subtraction, multiplication, integer division, remainder, bitwise operations and comparisons of two machine words;
- given a word , reading or writing the word stored in memory at address .
A machine word of bits can name at most addresses, so the processor can read and write at most locations of memory. When a problem’s input occupies machine words we therefore always assume a word size of bits, or the machine could not reach all of its input. For comparison, a Word-RAM model of a byte-addressable -bit machine allows inputs of up to about gigabytes.
Arrays
An array is a fixed number of storage slots in a row, numbered from , any of which may be read or written in a single elementary operation. The th slot of an array is written . In the Word-RAM an array of words is a block of consecutive addresses, and reaching means reading the address of plus : one addition and one read, whatever is and whatever the length of .
Python Lists
Python’s counterpart of an array is a list, written in square brackets. A list is a sequence like a tuple, but its entries may be changed.
p = [3, 1, 4, 1, 5]
print(len(p), p[0], p[4]) # 5 3 5
p[1] = 9
print(p) # [3, 9, 4, 1, 5]
q = [True] * 4 # [True, True, True, True]
r = [None] * 3 # [None, None, None]
Positions are numbered from , as they are for strings, and p[i] = x writes to position i. The expression [x] * n builds a list of n copies of x. Several further operations will be used below.
p.append(x)addsxat the end, andp.pop()removes the last entry and returns it;p.pop(i)removes and returns the entry at positioni.- The slice
p[i:j]is a new list holding the entries at positionsiup to but not includingj, with the same convention as for strings;p[i:]runs to the end andp[:j]starts at the beginning. Building a slice copies its entries. p + qis a new list holding the entries ofpfollowed by those ofq.- The comprehension
[f(a) for a in X]builds the list of valuesf(a)asaruns throughX. x is Nonetests whetherxis the objectNone.
Unlike a tuple, a list can grow and shrink; how Python does this is described under dynamic arrays below. Used with a fixed length it behaves as an array.
Listing the Primes
Deciding whether one number is prime was a decision problem; listing the primes up to a bound is a general discrete computational problem. It is the first problem here whose best algorithm uses an array, and it has a much faster algorithm than testing each number for primality.
List of Prime Numbers
Input: n ∈ N.
Task: compute all prime numbers p with p ⩽ n.
Running the trial-division test on each of in turn settles it in operations. The sieve of Eratosthenes does better by never testing divisibility at all: it writes down every candidate, then crosses out the multiples of each survivor in turn. It uses an array indexed by .
Example 4.10 (The sieve of Eratosthenes).
The algorithm marks every index as a candidate and then strikes out the multiples of each index that is still marked.
The Sieve of Eratosthenes
Input: n ∈ N.
Output: all prime numbers less than or equal to n.
for i ≝ 2 to n do p[i] ≝ "yes"
for i ≝ 2 to n do
if p[i] = "yes" then
output i
for j ≝ i to ⌊n / i⌋ do p[i · j] ≝ "no"The inner loop starts at rather than at : the multiples have a factor smaller than and were struck out already.
The running time depends on the total work of the inner loops, which is a sum of harmonic numbers, which have no closed form. What we need instead is a bound on them. The lower bound is not needed for the sieve; it is used for quicksort in the next lesson.
Proposition 4.11 (Bounds on the harmonic numbers).
For every ,
Discussion.
We group the terms into blocks between consecutive powers of two. The block running from to has terms, each at most and more than , so the block contributes between and whatever is. The number of blocks needed to cover is about , and both bounds follow. At the top the last block may be incomplete: for the upper bound we enlarge the sum to the end of that block, and for the lower bound we drop it.
Proof.
Let , so that . For the block of indices has terms, and each has , so
Every term is positive, so enlarging the range of summation increases the sum and shrinking it decreases the sum. The blocks cover , and the blocks cover . Hence
Theorem 4.12 (The sieve is correct and runs in ).
The sieve of Eratosthenes outputs exactly the primes less than or equal to , and performs elementary operations.
Discussion.
Correctness and running time are proved separately.
Correctness has two directions. No prime is ever struck out, because an entry is written to only as with both and at least , and such an index is composite by definition. In the other direction every composite must be struck before the outer loop reaches it, and the index that strikes it is its least divisor above : that divisor is itself prime, so it still carries “yes” when the outer loop arrives at it, and its partner divided by it is large enough to fall in the range the inner loop covers. That the partner is at least the divisor is Proposition 3.14, which is why the inner loop may start at .
For the running time, the outer loop costs by itself, and the inner loop belonging to runs at most times. Summing over gives times a harmonic sum, and the upper bound just proved turns that into .
Proof.
Correctness. An entry of is set to “no” only in the inner loop, where the index written to is with and . Such an index is a product of two integers greater than and so is composite; hence no prime is ever struck out, and every prime still carries “yes” when the outer loop reaches it and is output.
Conversely let be composite, and let be its least divisor with . Then is prime: a divisor of with would divide as well and contradict minimality. By Proposition 3.14, , so writing we have
and is an integer, so . Since is prime it is not struck out, so when the outer loop reaches the test succeeds and the inner loop runs, setting to “no”. Finally , so this happens before the outer loop reaches , and is not output. The algorithm therefore outputs the primes and nothing else.
Running time. The first loop performs assignments. In the second loop, each of the values of costs a bounded amount for the test and the output, contributing in total. The inner loop belonging to runs only when is “yes”, and then makes at most passes, each of bounded cost. Summing over ,
by the previous proposition. Adding the three contributions, the running time is .
In Python the array is a list of n + 1 entries, True for “yes” and False for “no”; positions and are never read.
def sieve(n):
p = [True] * (n + 1)
primes = []
for i in range(2, n + 1):
if p[i]:
primes.append(i)
for j in range(i, n // i + 1):
p[i * j] = False
return primes
print(sieve(50))
# [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47]
Count the assignments p[i * j] = False performed by the sieve for , and compare the count with the bound of the theorem. Then count how many of them write “no” to an entry that was already “no”, and say which composite numbers are struck more than once.
Modify the sieve so that the inner loop starts at rather than . Show that the output is unchanged, and say how the count of assignments changes for .
Show that the sieve may stop its outer loop at provided the surviving indices above that point are output afterwards. Which part of the proof of the theorem does this rely on?
Data Structures and Interfaces
A data structure is a way to store a non-constant amount of data, supporting a set of operations to interact with that data. The set of operations a data structure supports is its interface. Many data structures may support the same interface and differ in the cost of each operation, and many problems become easy once the data are stored in a suitable structure.
The most primitive data structure native to the Word-RAM is the static array: a contiguous sequence of words reserved in memory, supporting the static sequence interface.
StaticArray(n): allocate a new static array of size , every entry initialised to , in time.get_at(i): return the word stored at index , in time.set_at(i, x): write the word to index , in time.
The operations get_at(i) and set_at(i, x) run in constant time because every item of the array has the same size, one machine word. To store a larger object at an index, the machine word there is read as the memory address of a larger piece of memory holding the object. A Python tuple is like a static array without set_at(i, x).
Classes
Python writes a data structure as a class: a bundle of stored values, called attributes, together with the functions that act on them, called methods.
class Counter:
def __init__(self, start):
self.value = start
def increment(self):
self.value = self.value + 1
def __len__(self):
return self.value
c = Counter(5)
c.increment()
print(c.value, len(c)) # 6 6
Calling Counter(5) creates a new object of the class and runs __init__ on it, with self bound to the new object and start to 5; the assignment self.value = start creates the attribute value. A call c.increment() runs the method with self bound to c. A few method names are special: len(c) calls c.__len__(), and a for loop over c calls c.__iter__().
Inside a method, yield x hands x to the for loop that is running over the object, then carries on from the same point when the loop asks for the next value; yield from X hands on every value of X in turn. This is how range produces its entries one at a time. The statement raise IndexError stops the program with an error naming a bad index, and assert test stops it with an error if test is False.
A class may be built on another: class B(A): gives B every method of A, and any method B defines with the same name replaces the one from A. Inside B, super().__init__() runs the __init__ of A.
A Static Array in Python
Python has no static array, so we imitate one with a list whose length we never change.
class StaticArray:
def __init__(self, n):
self.data = [None] * n
def get_at(self, i):
if not (0 <= i < len(self.data)): raise IndexError
return self.data[i]
def set_at(self, i, x):
if not (0 <= i < len(self.data)): raise IndexError
self.data[i] = x
Birthday Matching
Given the students of a class, each with a name and a birthday, we want two students who share a birthday, or a report that there are none. The algorithm keeps a record of the students seen so far, and checks each new student against it before adding them.
Birthday Match
Input: n students, each a pair (name, birthday).
Output: the names of two students with the same birthday, or None.
record ≝ a static array of length n
for k ≝ 0 to n − 1 do
(name₁, bday₁) ≝ student k
for i ≝ 0 to k − 1 do
(name₂, bday₂) ≝ record[i]
if bday₁ = bday₂ then return (name₁, name₂)
record[k] ≝ (name₁, bday₁)
return None
def birthday_match(students):
# students: tuple of (name, bday) tuples
n = len(students) # O(1)
record = StaticArray(n) # O(n)
for k in range(n): # n passes
(name1, bday1) = students[k] # O(1)
for i in range(k): # k passes: check the record
(name2, bday2) = record.get_at(i) # O(1)
if bday1 == bday2: # O(1)
return (name1, name2) # O(1)
record.set_at(k, (name1, bday1)) # O(1)
return None # O(1)
print(birthday_match((('Ada', 'Dec 10'), ('Alan', 'Jun 23'), ('Emmy', 'Mar 23'), ('Kurt', 'Jun 23'))))
# ('Kurt', 'Alan')
We assume that each name and each birthday fits into a constant number of machine words, so that one student’s information can be read and compared in constant time. This allows names and birthdays of characters from a fixed alphabet, and since it still allows every student’s information to be distinct.
Every line then takes constant time except three. Building record takes time; the outer loop makes at most passes; and the inner loop on pass runs through the entries already in the record. The running time is therefore at most
using the sum of an arithmetic progression. This is quadratic in . A different data structure for the record does better, and the hashing section at the end of this chapter gives one.
Suppose birthdays are given as integers (with for 29 February). Rewrite birthday_match so that it runs in time, using a static array of length indexed by birthday. Where does your running-time argument use the fact that the number of possible birthdays does not grow with ?
Sequences and Sets
We use two interfaces, which differ in what decides the order of the stored items.
Sequences maintain a collection of items in an extrinsic order: each stored item has a rank in the sequence, including a first item and a last item. Extrinsic means that the first item is first not because of what the item is, but because some external party put it there. An iterable below is anything a for loop can run over, such as a tuple, a string, a list or a range.
| Operation | Meaning | |
|---|---|---|
| Container | build(X) | given an iterable X, build a sequence from the items of X |
len() | return the number of stored items | |
| Static | iter_seq() | return the stored items one by one in sequence order |
get_at(i) | return the item of rank i | |
set_at(i, x) | replace the item of rank i with x | |
| Dynamic | insert_at(i, x) | add x as the item of rank i |
delete_at(i) | remove and return the item of rank i | |
insert_first(x) | add x as the first item | |
delete_first() | remove and return the first item | |
insert_last(x) | add x as the last item | |
delete_last() | remove and return the last item |
The insert and delete operations change the rank of every item after the one inserted or deleted. Two restricted forms of a sequence have names of their own.
Definition 4.13 (Stack and queue).
A stack is a sequence used only through insert_last and delete_last: the item removed is always the one most recently added, “last in, first out”. A queue is a sequence used only through insert_last and delete_first: the item removed is always the one added longest ago, “first in, first out”.
The call stack of the functions section is a stack in this sense. With a Python list, append and pop() are insert_last and delete_last.
Sets, by contrast, maintain a collection of items based on an intrinsic property of what the items are, usually a unique key x.key attached to each item x. Sets generalise dictionaries and other databases queried by content.
| Operation | Meaning | |
|---|---|---|
| Container | build(X) | given an iterable X, build a set from the items of X |
len() | return the number of stored items | |
| Static | find(k) | return the stored item with key k |
| Dynamic | insert(x) | add x to the set, replacing the item with key x.key if there is one |
delete(k) | remove and return the stored item with key k | |
| Order | iter_ord() | return the stored items one by one in key order |
find_min() | return the stored item with smallest key | |
find_max() | return the stored item with largest key | |
find_next(k) | return the stored item with smallest key larger than k | |
find_prev(k) | return the stored item with largest key smaller than k |
The find operations return None if no qualifying item exists. In Python an item with a key can be an object of a small class:
class Item:
def __init__(self, key, value):
self.key = key
self.value = value
We now give three data structures for the sequence interface. None of them supports insertion or deletion at an arbitrary rank in less than linear time.
Array Sequences
Computer memory is a finite resource. On a modern computer many running programs share the same main memory, so the operating system assigns a fixed range of memory addresses to each of them. When a program asks to store a variable it must say how much memory, how many bits, the variable needs; the operating system finds that much free memory in the program’s assigned range and reserves it, or allocates it, until it is no longer needed. Python hides memory management from the programmer, but whenever Python is asked to store something it makes such a request, for a fixed amount of memory, behind the scenes.
Now suppose a program wants to store two arrays, each of ten -bit words. It makes two requests, for bits each, and the operating system might reserve the first ten words of the program’s range for the first array and the next ten for the second array . Later an eleventh word has to be added to , and there is no room next to : the start of the range is to its left, and is to its right. One could shift right to make room, but much other data may already be reserved beyond and would have to move too. It is better to request eleven new words, copy into the start of the new allocation, store at the end, and release the old ten words for later requests.
Memory itself is one large fixed-length array, from which the operating system allocates. Implementing a sequence with an array, so that index of the array holds the item of rank , makes get_at and set_at take time by random access. Inserting or deleting, however, means moving items and resizing the array, and these operations take linear time in the worst case.
class Array_Seq:
def __init__(self): # O(1)
self.A = []
self.size = 0
def __len__(self): return self.size # O(1)
def __iter__(self): yield from self.A # O(n) iter_seq
def build(self, X): # O(n)
self.A = [a for a in X] # stands in for a static array
self.size = len(self.A)
def get_at(self, i): return self.A[i] # O(1)
def set_at(self, i, x): self.A[i] = x # O(1)
def _copy_forward(self, i, n, A, j): # O(n)
for k in range(n):
A[j + k] = self.A[i + k]
def _copy_backward(self, i, n, A, j): # O(n)
for k in range(n - 1, -1, -1):
A[j + k] = self.A[i + k]
def insert_at(self, i, x): # O(n)
n = len(self)
A = [None] * (n + 1)
self._copy_forward(0, i, A, 0)
A[i] = x
self._copy_forward(i, n - i, A, i + 1)
self.build(A)
def delete_at(self, i): # O(n)
n = len(self)
A = [None] * (n - 1)
self._copy_forward(0, i, A, 0)
x = self.A[i]
self._copy_forward(i + 1, n - i - 1, A, i)
self.build(A)
return x
def insert_first(self, x): self.insert_at(0, x) # O(n)
def delete_first(self): return self.delete_at(0) # O(n)
def insert_last(self, x): self.insert_at(len(self), x) # O(n)
def delete_last(self): return self.delete_at(len(self) - 1) # O(n)
_copy_forward(i, n, A, j) copies the items starting at index into the array A starting at index , from left to right; _copy_backward copies the same items from right to left, which matters when source and destination overlap. A method name starting with an underscore is a convention for “used only inside the class”.
In a sorted array, whose items are arranged in the order of their keys, an item can be found far faster than by scanning, by the bisection of the last lesson; binary search in the next lesson makes this precise. Inserting an object while preserving the order is then harder, and costs time. Deleting an element, even when its index is known, also costs time if the array is to have no gaps and keep the order of the remaining elements.
Trace Array_Seq on build((5, 7, 9)), then insert_at(1, 6), then delete_at(0), giving the list self.A after each operation. Count the item copies each of the two dynamic operations makes, and give that count for insert_at(i, x) on a sequence of length .
Linked Lists
In a linked list, inserting or deleting an item does not move the others. Its items are kept in a certain order, as in an array, but they can be stored anywhere in memory, in places independent of one another. With each item we store a reference to the place of the next item, its successor; for the last item this reference is None, which marks the end of the list. One further reference to the first item, the head, is needed to reach the list at all.
Linked lists can be singly or doubly linked. In a doubly linked list each item also stores a reference to its predecessor, and the list keeps a reference to its last item, the tail. This allows an item whose place is known to be deleted with a number of steps bounded by a constant independent of the length of the list: the running time is .
None. The names first and last refer to the two ends. Deleting the accented parts leaves a singly linked list.The Python below is a singly linked list. A node holds an item and a reference next; later_node(i) walks steps along the list.
class Linked_List_Node:
def __init__(self, x): # O(1)
self.item = x
self.next = None
def later_node(self, i): # O(i)
if i == 0: return self
assert self.next
return self.next.later_node(i - 1)
class Linked_List_Seq:
def __init__(self): # O(1)
self.head = None
self.size = 0
def __len__(self): return self.size # O(1)
def __iter__(self): # O(n) iter_seq
node = self.head
while node:
yield node.item
node = node.next
def build(self, X): # O(n)
for a in reversed(X):
self.insert_first(a)
def get_at(self, i): # O(i)
node = self.head.later_node(i)
return node.item
def set_at(self, i, x): # O(i)
node = self.head.later_node(i)
node.item = x
def insert_first(self, x): # O(1)
new_node = Linked_List_Node(x)
new_node.next = self.head
self.head = new_node
self.size += 1
def delete_first(self): # O(1)
x = self.head.item
self.head = self.head.next
self.size -= 1
return x
def insert_at(self, i, x): # O(i)
if i == 0:
self.insert_first(x)
return
new_node = Linked_List_Node(x)
node = self.head.later_node(i - 1)
new_node.next = node.next
node.next = new_node
self.size += 1
def delete_at(self, i): # O(i)
if i == 0:
return self.delete_first()
node = self.head.later_node(i - 1)
x = node.next.item
node.next = node.next.next
self.size -= 1
return x
def insert_last(self, x): self.insert_at(len(self), x) # O(n)
def delete_last(self): return self.delete_at(len(self) - 1) # O(n)
reversed(X) runs through X from its last entry to its first, so that inserting each at the front leaves them in their original order; a test while node: holds as long as node is not None.
Linked lists have a disadvantage: bisection cannot be applied to them, since reaching the middle item means walking to it. Scanning a linked list is also slower than scanning an array, by a constant factor only, because today’s computers reach consecutive storage places substantially faster than places far apart.
Add a method reverse() to Linked_List_Seq that reverses the order of the items in time and auxiliary space, by changing the next references rather than the items. State the invariant your loop maintains.
Dynamic Arrays
The array sequence’s dynamic operations take time linear in the length of the array. One way to add items without paying a linear transfer cost every time is to over-allocate: request more space than the array currently needs, so that inserting an item means writing it into the next empty slot. This trades a little extra space for constant-time insertion. Any extra allocation is bounded, however; repeated insertions eventually fill it, and the array must be reallocated and copied again. Extra space reserved also means less space for the rest of the program.
Python does not append to the end of a list in worst-case time. Sometimes appending to a Python list requires time to transfer the array to a larger allocation, so sometimes appending takes linear time. Allocating extra space in the right way guarantees that any sequence of insertions takes time in total, because the linear-time transfers happen rarely, so insertion takes time per insertion on average over the sequence.
Definition 4.14 (Amortized cost).
An operation has amortized cost if every sequence of operations, starting from an empty data structure, takes at most time in total, where is the largest size the structure reaches.
The cost of an expensive operation is amortized, that is spread, across the many cheap ones. To achieve amortized constant-time insertion at the end of an array, the strategy is to allocate extra space in proportion to the size of the array stored. Allocating extra space ensures that a linear number of insertions must occur before an insertion overflows the allocation. A typical implementation allocates double the space needed for the current array, which is called table doubling; any constant fraction of extra space achieves the same bound. The list implementation of CPython, the standard Python interpreter, has used the rule
new_allocated = newsize // 8 + (3 if newsize < 9 else 6) # extra slots
when a list must grow to newsize items, translated here from C; recent versions use a rule of the same shape. The extra allocation is modest, about one eighth of the size of the array, but it is still linear in that size, so on average insertions are performed for every linear-time reallocation: amortized constant time.
Now consider removing items from the end. Popping the last item can be done in constant time by decrementing a stored length, which Python does. But if many items are removed from a large list, the unused allocation can hold a large amount of memory that is not available for other purposes. Once the array is small enough we transfer its contents to a smaller allocation and release the larger one. The new allocation cannot be exactly the size of the array, since an immediate insertion would then trigger another reallocation. For constant amortized time over any sequence of appends and pops, there must remain a linear fraction of unused space whenever we rebuild into a smaller array, which guarantees that further operations must occur before the next reallocation.
The implementation below does both with table-doubling proportions. When an append would pass the end of the allocation, the contents move to an allocation twice as large. When removals bring the array down to a quarter of its allocation, the contents move to an allocation half as large. Python lists already work this way; the code shows how amortized constant-time append and pop can be implemented.
class Dynamic_Array_Seq(Array_Seq):
def __init__(self, r = 2): # O(1)
super().__init__()
self.size = 0
self.r = r
self._compute_bounds()
self._resize(0)
def __len__(self): return self.size # O(1)
def __iter__(self): # O(n)
for i in range(len(self)): yield self.A[i]
def build(self, X): # O(n)
for a in X: self.insert_last(a)
def _compute_bounds(self): # O(1)
self.upper = len(self.A)
self.lower = len(self.A) // (self.r * self.r)
def _resize(self, n): # O(1) or O(n)
if (self.lower < n < self.upper): return
m = max(n, 1) * self.r
A = [None] * m
self._copy_forward(0, self.size, A, 0)
self.A = A
self._compute_bounds()
def insert_last(self, x): # O(1) amortized
self._resize(self.size + 1)
self.A[self.size] = x
self.size += 1
def delete_last(self): # O(1) amortized
self.A[self.size - 1] = None
self.size -= 1
self._resize(self.size)
def insert_at(self, i, x): # O(n)
self.insert_last(None)
self._copy_backward(i, self.size - (i + 1), self.A, i + 1)
self.A[i] = x
def delete_at(self, i): # O(n)
x = self.A[i]
self._copy_forward(i + 1, self.size - (i + 1), self.A, i)
self.delete_last()
return x
def insert_first(self, x): self.insert_at(0, x) # O(n)
def delete_first(self): return self.delete_at(0) # O(n)
def __init__(self, r = 2) gives the parameter r a default value: Dynamic_Array_Seq() uses r = 2, and Dynamic_Array_Seq(3) uses r = 3. The class inherits get_at, set_at and the two copying methods from Array_Seq.
Proposition 4.15 (Appending is amortized constant time).
Starting from an empty Dynamic_Array_Seq with , any sequence of calls of insert_last takes time in total.
Discussion.
Each call does a constant amount of work apart from reallocations, so we bound the total cost of the reallocations. A reallocation at size allocates about slots and copies items, so it costs . The sizes at which reallocations happen are the points where the allocation fills up, and because the allocation doubles each time these sizes grow geometrically. Their sum is therefore dominated by the last one, which is less than , and a geometric sum bounds the total by a constant times .
Proof.
Without removals lower never exceeds a quarter of the allocation, so _resize(s + 1) reallocates exactly when reaches the current allocation upper. The initial call _resize(0) allocates slots. After a reallocation triggered at size the allocation becomes . So the allocations are , and the insertions that reallocate are those that bring the size to ; the one bringing the size to copies items and allocates slots, at cost at most for a constant .
Over insertions these are the with , that is with . Their total cost is at most
by the geometric sum. Every other part of every call costs , contributing . The total is .
The worst-case costs of the three sequence structures are collected below, with (a) marking an amortized bound.
| Data structure | build(X) | get_at(i), set_at(i, x) | insert_first(x), delete_first() | insert_last(x), delete_last() | insert_at(i, x), delete_at(i) |
|---|---|---|---|---|---|
| Array | |||||
| Linked list | |||||
| Dynamic array | (a) |
Each entry is the bound as a function of the number of stored items.
Take and start from an empty Dynamic_Array_Seq. Show that any sequence of operations, each an insert_last or a delete_last on a nonempty array, takes time in total. Then show that if the array were halved as soon as it fell to half full, rather than a quarter, some sequence of operations would take time.
Extend Linked_List_Seq with a reference to its last node so that insert_last takes time, and extend Dynamic_Array_Seq so that insert_first and delete_first take amortized time. Which operation cannot be made in a singly linked list with a tail reference, and why?
Hashing
The set interface asks for find(k). Stored in an array in no particular order, a set answers find(k) by scanning, in time. With comparisons alone we cannot do much better: the lower bound for searching in the next lesson shows that comparisons are needed for a search among items. Reading memory at an address computed from the key is not a comparison, and it reaches any memory cell in one step. Hashing uses this.
Direct Access Arrays
A direct access array is a static array with a meaning attached to each index: an item with key is stored at index . This makes sense only when keys are integers. Anything stored in a computer can be associated with an integer, for example its sequence of bits read as a binary number, or its address in memory, so from now on keys are integers.
Suppose we want to store a set of items whose unique integer keys lie in the range . We store them in a direct access array of length , whose slot holds the item with key if there is one. To find the item with key , look in slot : worst-case constant time. The order operations are slow: the first, last or next item could be in any slot, so they may take time.
class DirectAccessArray:
def __init__(self, u): self.A = [None] * u # O(u)
def find(self, k): return self.A[k] # O(1)
def insert(self, x): self.A[x.key] = x # O(1)
def delete(self, k): self.A[k] = None # O(1)
def find_next(self, k): # O(u)
for i in range(k + 1, len(self.A)):
if self.A[i] is not None:
return self.A[i]
def find_max(self): # O(u)
for i in range(len(self.A) - 1, -1, -1):
if self.A[i] is not None:
return self.A[i]
def delete_max(self): # O(u)
for i in range(len(self.A) - 1, -1, -1):
x = self.A[i]
if x is not None:
self.A[i] = None
return x
A direct access array needs a slot for every possible key in the range. When is very large compared with the number of items stored, the array is wasteful, or impossible to store at all. Suppose we wanted find(k) on ten-letter names with a direct access array. There are possible names, and even an array of one bit per name would need terabytes.
The following counting principle is used below to show that collisions cannot be avoided.
Proposition 4.16 (The pigeonhole principle).
If objects are placed in boxes, some box contains at least objects.
Discussion.
The argument is by contradiction on the total. If every box held fewer than objects, each would hold at most , and the boxes together would hold fewer than . It remains to check the arithmetic step that , which is the defining property of the ceiling.
Proof.
Suppose every box contains at most objects. By the definition of the ceiling, , so the total number of objects is at most , a contradiction.
Hash Functions
To keep fast search while using only space when is much smaller than , we store the items in a smaller direct access array of slots, growing and shrinking it like a dynamic array according to the number of items stored. This needs a way to send each key to one of the slots.
Definition 4.17 (Hash function and hash table).
A hash function is a function
and is the hash of the key . The smaller direct access array of slots in which an item with key is stored at slot is a hash table. Two keys collide if .
If happens to be injective on the keys being stored, so that no two of them collide, the hash table acts as a direct access array over the smaller range and supports worst-case constant-time search. When , however, the pigeonhole principle puts at least two of the possible keys in some slot, and when the keys to be stored are not known in advance it is very unlikely that a chosen hash function avoids collisions among them. (If all the keys are known in advance, a scheme called perfect hashing can be designed to avoid collisions between them.)
A slot can hold one item, so colliding items must be stored somewhere. Either they are stored elsewhere in the same array, which is called open addressing and is how most hash tables are implemented in practice, though it is harder to analyse; or they are stored in a separate structure, which is called chaining and is the strategy we adopt.
Chaining
In chaining each slot of the hash table holds a reference to a chain, a separate data structure supporting the dynamic set operations find(k), insert(x) and delete(k). A chain is usually a linked list or a dynamic array, and any implementation will do provided each operation takes at most linear time in the length of the chain. To insert an item , insert it into the chain at slot ; to find or delete a key , find or delete it in the chain at slot .
A chain in Python can be a list of items searched from the front:
class Chain:
def __init__(self): self.items = [] # O(1)
def __iter__(self): yield from self.items # O(length)
def find(self, k): # O(length)
for x in self.items:
if x.key == k: return x
return None
def insert(self, x): # O(length)
for i in range(len(self.items)):
if self.items[i].key == x.key:
self.items[i] = x # replace, nothing added
return False
self.items.append(x)
return True
def delete(self, k): # O(length)
for i in range(len(self.items)):
if self.items[i].key == k:
return self.items.pop(i)
return None
We want chains to be short: if every chain holds a constant number of items, the dynamic set operations run in constant time. If instead the hash function sends every stored key to the same slot, one chain has linear length and the operations can take linear time. A good hash function keeps collisions rare, so that no chain grows long.
Choosing a Hash Function
The simplest map from keys in to is the division method: , in Python k % m. If the keys stored are spread evenly over the range, it spreads them roughly evenly among the slots and the chains stay short. But if all of them happen to leave the same remainder on division by , every one lands in one chain. We want performance that does not depend on which keys are stored, and no single hash function gives that.
Remark (Every fixed hash function has bad inputs).
If , then every hash function from to sends some keys to the same slot. By the pigeonhole principle some slot receives at least keys, and .
Instead we choose the hash function at random, from a large family, after the keys are fixed. Then no set of keys is bad for most of the family, and we can bound the cost on average over the choice of function. The averages we need are over a finite set of equally likely choices.
Definition 4.18 (Uniform choice from a finite family).
Let be a finite nonempty set, and let be chosen from with every member equally likely. For a property of members of , and a function , the probability of and the expectation of are
Two facts follow from the laws of summation. Expectation is linear: and , because the sum defining the left side splits into the sums defining the right. And the expectation of an Iverson bracket is a probability: , since the bracket contributes for each with and otherwise. The expectation here is over the choice of hash function, which is made independently of the input. It is not an average over possible input keys.
Definition 4.19 (Universal family).
A finite family of hash functions from to is universal if for any two keys in ,
A family that performs well is
where is a prime larger than . A single function of the family is specified by choosing concrete values of and . This family is universal. The proof uses arithmetic modulo a prime, which these notes have not developed, and we take it as given.
Proposition 4.20 (Expected chain length).
Let be a universal family, and let distinct keys be stored in a hash table of slots with chaining, using chosen uniformly from . For each , the expected number of stored keys in the chain at slot is at most .
Discussion.
The chain holding contains exactly the stored keys that collide with , together with itself. So its length is a sum of Iverson brackets, one for each stored key, and linearity turns the expectation of the sum into a sum of expectations. Each bracket’s expectation is a collision probability: the one for is , and each of the others is at most by universality.
Proof.
For each let , which is if and collide under and otherwise. The number of stored keys in the chain at slot is , and for every . By linearity and universality,
If the table is at least linear in the number of items stored, , the expected length of any chain is . A hash table with chaining and a hash function chosen at random from a universal family therefore performs the dynamic set operations in expected constant time, the expectation being over the choice of hash function and not over the input keys. To keep , insertions and deletions may have to rebuild the table at a different size and reinsert every item, as a dynamic array does; this makes the bounds for the dynamic operations amortized as well.
A Hash Table in Python
The statement from random import randint makes the function randint available: randint(a, b) returns an integer chosen uniformly from . The keys are assumed to be integers below the prime . In [Chain() for _ in range(m)] the name _ is the usual name for a loop variable whose value is not used.
from random import randint
class Hash_Table_Set:
def __init__(self): # O(1)
self.A = []
self.size = 0
self.p = 2**31 - 1 # a prime larger than every key
self.a = randint(1, self.p - 1)
self.b = randint(0, self.p - 1)
self._compute_bounds()
self._resize(0)
def __len__(self): return self.size # O(1)
def __iter__(self): # O(n)
for chain in self.A:
yield from chain
def build(self, X): # O(n) expected
for x in X: self.insert(x)
def _hash(self, k, m): # O(1)
return ((self.a * k + self.b) % self.p) % m
def _compute_bounds(self): # O(1)
self.upper = len(self.A)
self.lower = len(self.A) // 4
def _resize(self, n): # O(n)
if self.lower < n < self.upper: return
m = max(n, 1) * 2
A = [Chain() for _ in range(m)]
for x in self:
A[self._hash(x.key, m)].insert(x)
self.A = A
self._compute_bounds()
def find(self, k): # O(1) expected
h = self._hash(k, len(self.A))
return self.A[h].find(k)
def insert(self, x): # O(1) amortized expected
self._resize(self.size + 1)
h = self._hash(x.key, len(self.A))
added = self.A[h].insert(x)
if added: self.size += 1
return added
def delete(self, k): # O(1) amortized expected
assert len(self) > 0
h = self._hash(k, len(self.A))
x = self.A[h].delete(k)
if x is not None:
self.size -= 1
self._resize(self.size)
return x
def find_min(self): # O(n)
out = None
for x in self:
if (out is None) or (x.key < out.key):
out = x
return out
def find_max(self): # O(n)
out = None
for x in self:
if (out is None) or (x.key > out.key):
out = x
return out
def find_next(self, k): # O(n)
out = None
for x in self:
if x.key > k:
if (out is None) or (x.key < out.key):
out = x
return out
def find_prev(self, k): # O(n)
out = None
for x in self:
if x.key < k:
if (out is None) or (x.key > out.key):
out = x
return out
def iter_ord(self): # O(n^2)
x = self.find_min()
while x:
yield x
x = self.find_next(x.key)
The number of items stays strictly between a quarter of the number of slots and the number of slots; when it leaves that range the table is rebuilt with twice as many slots as items, which is the rule of Dynamic_Array_Seq with . So throughout, and by the proposition every chain has expected constant length.
Insert the keys in that order into a hash table of slots with chaining and the division method , and draw the table. Then do the same with . Describe every set of keys that makes the division method with put all keys into one chain.
Rewrite birthday_match using a Hash_Table_Set keyed by birthday, with birthdays given as integers, so that it runs in expected time. Explain why the bound is an expectation and what it is taken over.
Graphs
A graph records which pairs of objects are related: towns joined by roads, people who know each other, states of a computation that lead to one another. The objects are the vertices and the related pairs are the edges.
Undirected Graphs
Definition 4.21 (Undirected graph).
An undirected graph is a pair , where is a finite set of vertices and is a set of unordered pairs of vertices. Such a pair is an edge. Unless stated otherwise, the two vertices of an edge are distinct.
The vertices joined by an edge are adjacent, and we write
for the set of vertices adjacent to . We draw an edge as a line between its endpoints.
Here and . Vertex is isolated: it belongs to but to no edge. For example, and .
Definition 4.22 (Loops, simple graphs and multigraphs).
A loop joins a vertex to itself. A graph is simple if it has no loops and at most one edge between any two vertices. If repeated edges are allowed, the edge collection is a multiset and the graph is a multigraph. We normally work with simple graphs.
Definition 4.23 (Order and size).
The order of a graph is its number of vertices. Its size is its number of edges, counted with multiplicity for a multigraph.
The graph of Figure 4.3 has order and size , even though one vertex is isolated. The pair is the same unordered edge as ; listing both would not add an edge to a simple graph.
Running times of graph algorithms are functions of the graph, not of a single number, and the notation of the last chapter is extended to cover them. For a graph we write and for its vertex set and edge set.
Definition 4.24 (Landau notation on graphs).
Let be the set of all graphs, and let . We say that if there exist and such that
The notations and are extended in the same way.
In other words, if is greater than , then by at most a constant factor, with exceptions allowed only among graphs with fewer than vertices and edges together. The same definition can be made on any countable set of inputs, with a measure of size in place of . The function usually describes the running time of an algorithm or some memory requirement, and often depends only on the numbers of vertices and edges, which we write and throughout. Thus means at most a constant times the number of vertices plus edges.
Directed Graphs
Definition 4.25 (Directed graph).
A directed graph is a pair with finite vertex set and arc set . An arc starts at and ends at , and is drawn . The vertex is a successor of , and a predecessor of .
We write
In Figure 4.4,
so for example and .
Definition 4.26 (Directed loops and -graphs).
A directed loop is an arc . A directed graph without loops is sometimes called elementary. A directed -graph permits at most parallel arcs with any given ordered endpoints; in a -graph the arcs form a set, not a multiset.
The pair and the pair are different arcs, and removing one changes the graph. Reversing every arc of a drawing exchanges each successor set with the corresponding predecessor set.
Let and put an arc whenever and divides . List , and every vertex with no successors, and count the arcs.
Degree
In an undirected graph, the degree of a vertex is the number of edge-ends at , a loop counting twice. In a directed graph, the out-degree counts the arcs beginning at , the in-degree counts the arcs ending at , and . A directed loop contributes one to each of and .
We write for the set of edges with an end at in an undirected graph, and and for the sets of arcs beginning and ending at in a directed graph.
In a graph without loops , and in a directed graph and , counting parallel arcs separately. In a simple graph , and in a directed -graph and . For multigraphs, arcs are counted with multiplicity rather than by taking the sizes of these sets.
In Figure 4.3 the degrees of the vertices are . Their sum is . In Figure 4.4, and : the loop counts once in each.
Proposition 4.28 (Counting edge-ends).
For an undirected graph and for a directed graph respectively,
Discussion.
Both sides of each identity count the same thing in two ways. The left side of the first sums, vertex by vertex, the edge-ends at that vertex; the right side counts the same edge-ends edge by edge, and every edge has exactly two ends, a loop included. For a directed graph every arc has one tail and one head, so summing out-degrees counts each arc once by its tail, and summing in-degrees counts it once by its head.
Proof.
Count the pairs in which is an end of the edge , a loop at giving two such pairs. Grouping by gives , by the definition of degree. Grouping by gives for every edge, so . For a directed graph, count the pairs in which the arc begins at : grouping by gives , and grouping by gives for every arc, so . Counting the pairs in which ends at gives in the same way.
Example 4.29 (Odd degrees and repeated degrees).
The degree sum of an undirected graph is , an even number. A sum of integers is even exactly when it contains an even number of odd terms: each even degree contributes zero modulo , and each odd degree contributes one. Thus the number of vertices of odd degree is even.
Also, among vertices of a simple graph, two have the same degree. Every degree lies in . However, degree means a vertex is adjacent to every other vertex, so no vertex can have degree at the same time. At most of the listed values can occur, and putting vertices into at most degree classes forces two into one class by the pigeonhole principle, Proposition 4.16 .
Kinds of Graphs
We may attach numbers to edges or arcs, to express distance, cost or capacity.
Definition 4.30 (Weighted graph).
A weighted graph is a graph together with a function , the weight. An undirected weight belongs to the unordered edge .
Definition 4.31 (Complete graph).
A simple undirected graph is complete if every pair of distinct vertices is joined; it is written when it has vertices. A loop-free directed -graph is complete when both and are arcs for every .
To count the edges of , choose two distinct vertices without regard to order. There are choices for the first vertex and for the second. This counts each unordered pair twice, once in each order, so has edges. We write
read ” choose ”, for this number of unordered pairs among objects. In the complete directed graph both directions are arcs, so it has arcs.
Let be a directed or undirected graph. For , the induced subgraph has vertex set and contains all edges or arcs of whose endpoints lie in . A subgraph may keep only some of these: in the directed case, with the analogous rule for unordered edges. A spanning subgraph, also called a partial subgraph, keeps every vertex but possibly deletes edges.
For a simple undirected graph, the possible edges of a subgraph on are the unordered pairs of distinct vertices of that were already edges of . Figure 4.6 shows both operations on a four-vertex directed graph.
Here is the spanning subgraph obtained by deleting , and . In contrast, the induced subgraph retains the arcs , , and : vertex disappears, while every arc of joining the remaining vertices stays. An arc between retained vertices cannot be omitted from an induced subgraph.
Definition 4.33 (Clique and independent set).
In a simple undirected graph, a clique is a vertex set for which is complete, and an independent set, also called a stable set, is a vertex set for which has no edges. For a loop-free directed -graph, a directed clique contains both arcs between each pair of its vertices, while a directed stable set has no arcs at all.
In Figure 4.7, is a clique of maximum size and is an independent set of maximum size. To test whether is a clique, check the three pairs , , ; to test whether is independent, check that none of its three pairs is an edge. Checking a particular set requires only pairwise tests. Proving that it is of maximum size also requires ruling out every larger set, and finding maximum cliques in arbitrary graphs is much harder than checking whether a proposed set is a clique.
Definition 4.34 (Bipartite and regular graphs).
An undirected graph is bipartite if its vertices can be divided into disjoint sets so that every edge has one endpoint in each. It is complete bipartite, written , if and every pair with one vertex in each set is an edge. A graph is -regular if every vertex has degree .
Example 4.35 ( and the four-cycle).
In , each of the two vertices of is joined to each of the three vertices of , giving edges. Its degrees are on and on , so it is not regular. The graph with vertices and edges , the cycle on four vertices, is -regular and bipartite, with classes and .
In general has edges. Fix a vertex of ; it is joined to each of the vertices of . There are such vertices, and every edge has exactly one endpoint in , so this counts each edge exactly once.
Example 4.36 (Counting the same thing twice).
Let a bipartite graph with parts be -regular, where , and let it have edges. Sum the degrees of the vertices in . Every edge has exactly one endpoint there, so this sum counts every edge once and equals . Each of the vertices has degree , so the same sum is . Therefore . Repeating the count over gives . The two expressions for are equal, and dividing by the positive number gives .
Example 4.37 (A monochromatic triangle).
Colour each edge of red or blue. Then there is a triangle whose three edges have the same colour. Pick a vertex . Five edges leave , so by the pigeonhole principle at least of them, say , share a colour; suppose it is red. If any edge among is red, that edge and the two red edges to make a red triangle. If none is red, all three edges among are blue, making a blue triangle. These cases cover every colouring, so the argument works for all of them.
Show that has a red and blue colouring of its edges with no triangle all of one colour, so that in the last example cannot be replaced by .
Show that a graph is bipartite if it has no cycle of odd length, in the sense of the next section, by colouring each vertex according to the parity of its distance from a fixed vertex in its component. Show conversely that a bipartite graph has no cycle of odd length.
Walks, Paths, Cycles and Distance
Definition 4.38 (Walks and paths in a directed graph).
In a directed graph, a walk from to is a sequence of vertices with , and for every . Its length is . The vertex is reachable from if such a walk exists. A simple path is a walk with no repeated vertex. A closed walk has and ; a simple directed cycle is a closed walk with no further repeated vertices. A loop is a cycle of length one.
A walk may repeat vertices; a simple path may not. We allow the walk of length zero from to itself, so that every vertex is reachable from itself. This convention is used for matrix powers below. When a positive number of arcs is required we say positive-length reachability explicitly.
In Figure 4.8, is a simple path, is a walk with the repeated vertex , is a simple directed cycle, and is a closed walk that is not a simple cycle.
Definition 4.39 (Walks, trails and cycles in an undirected graph).
In an undirected graph, consecutive vertices of a walk are joined by an edge, and walks, length, reachability and simple paths are as in the directed case. A trail is a walk that uses no edge twice. A cycle is a closed trail of positive length; a simple cycle additionally has no repeated vertices except its first and last. A graph with no cycles is acyclic. In a simple undirected graph a simple cycle has length at least three.
Proposition 4.40 (A walk contains a simple path).
If there is a walk from to , there is a simple path from to .
Discussion.
If a walk visits some vertex twice, the part between the two visits can be cut out, leaving a shorter walk between the same endpoints. So a walk with the fewest edges can have no repeated vertex. The proof takes such a shortest walk, which exists because walk lengths are nonnegative integers and at least one walk exists.
Proof.
Among all walks from to , choose one, , with the fewest edges. If it repeated a vertex, say with , then would be a walk from to with edges, since is an arc, or edge, of the graph. Hence no vertex repeats.
Definition 4.41 (Distance and diameter).
The distance is the length of a shortest walk from to , or if is not reachable from . The diameter of a nonempty graph is , which may be . In directed graphs, and may differ.
The two-argument is a distance and the one-argument a degree; the number of arguments tells them apart. A shortest walk to a different vertex is a simple path, by the proof of the last proposition, so it has at most arcs.
In Figure 4.8, via , and via . The arc gives , but there is no directed route from to , so .
Give the distance for every ordered pair of vertices of Figure 4.8, as a table, and find the diameter. Which vertices can reach every other vertex?
Representing a Graph
To run an algorithm on a graph we must store it. Let have its vertices numbered .
Adjacency Lists
An adjacency list lists the vertices for which is an arc, or is an edge. For an undirected edge with , appears in and appears in . The order within a list is arbitrary.
More generally, an adjacency list representation keeps for every vertex a list of all the edges incident with it, the set ; for simple graphs a list of the adjacent vertices, as above, is often enough. For a directed graph one keeps two lists for each vertex, one of the outgoing arcs and one of the incoming arcs .
Example 4.42 (Adjacency lists of a directed graph).
The directed graph of Figure 4.6 has
Its arc set is exactly the nine ordered pairs described by these lists. The loop at contributes the entry to , and the loop at contributes the entry to . There are entries, hence nine arcs; counting list entries is a direct way to check the arc count.
Storage. There are list headers. The total number of entries is for a directed graph with arcs and for an undirected graph with edges, a loop also taking two entries if loops are allowed. Thus storage is .
Operations. Reading gives the successors and the out-degree of quickly. Testing for one particular arc may require scanning . Finding all predecessors of requires scanning all the lists, unless reverse lists are stored as well. Visiting all arcs takes time, not merely , when isolated vertices must also be visited.
Adjacency Lists in Two Arrays
We can store the adjacency lists one after another in a single array . A second array gives the final position of each list, and gives the starting boundary of the first. The successors of then occupy
and an empty list has equal consecutive boundaries. For the graph of the last example,
The empty first list is encoded by ; the second list ends at position , and so on. These boundaries are cumulative counts: if is the length of list , then .
To find the predecessors of , scan each list and record its owner whenever an entry equals ; the result is . This costs even though only three names are recorded. The same cumulative-count idea produces all the predecessor lists in : count how many times each vertex occurs in , which gives the in-degrees; take cumulative sums to allocate a contiguous block to each vertex; then scan the arcs once more to fill the blocks.
Predecessor Lists
Input: n, and the arrays Head[0..n] and Succ[1..m] of a directed graph.
Output: arrays PHead[0..n] and Pred[1..m] storing the predecessor lists
in the same way.
for v ≝ 1 to n do c[v] ≝ 0
for i ≝ 1 to m do c[Succ[i]] ≝ c[Succ[i]] + 1
PHead[0] ≝ 0
for v ≝ 1 to n do
PHead[v] ≝ PHead[v − 1] + c[v]
free[v] ≝ PHead[v − 1] + 1
for u ≝ 1 to n do
for i ≝ Head[u − 1] + 1 to Head[u] do
w ≝ Succ[i]
Pred[free[w]] ≝ u
free[w] ≝ free[w] + 1
Python numbers list positions from , so the successors of are the slice Succ[Head[v - 1]:Head[v]] and the positions of the blocks shift down by one.
def predecessor_lists(n, Head, Succ):
count = [0] * (n + 1)
for w in Succ:
count[w] += 1
PHead = [0] * (n + 1)
free = [0] * (n + 1)
for v in range(1, n + 1):
PHead[v] = PHead[v - 1] + count[v]
free[v] = PHead[v - 1]
Pred = [None] * len(Succ)
for u in range(1, n + 1):
for w in Succ[Head[u - 1]:Head[u]]:
Pred[free[w]] = u
free[w] += 1
return PHead, Pred
PHead, Pred = predecessor_lists(4, [0, 0, 2, 6, 9], [2, 1, 1, 4, 3, 2, 1, 2, 3])
print(PHead, Pred) # [0, 3, 6, 8, 9] [2, 3, 4, 2, 3, 4, 3, 4, 3]
print(Pred[PHead[1]:PHead[2]]) # [2, 3, 4], the predecessors of 2
Each of the loops runs over the vertices or over the arcs once, so the running time is .
Matrices
An matrix is a table of numbers with rows and columns. Its entry in row and column is written ; rows run across and columns down. An matrix is square, and the identity matrix is the square matrix with on its diagonal and elsewhere.
Two matrices of the same shape are added entry by entry. If is and is , their product is the matrix with
and the powers of a square matrix are and . This is a definition, not ordinary entrywise multiplication. For example,
because the top-right entry is . We use only this rule and ordinary arithmetic below. Computing one entry of the product of two matrices takes multiplications, so the whole product takes arithmetic operations.
Adjacency Matrices
For a directed -graph with vertices , the adjacency matrix is the matrix with
For a simple undirected graph we put when is an edge. Then , so is symmetric, and one can store only the entries on and above the diagonal and recover the rest by reflection; this uses about half as many entries, but still space. For a directed multigraph one may instead put the number of parallel arcs into .
Example 4.44 (An adjacency matrix).
The graph of Figure 4.6 has
The rows match the adjacency lists: the third row has four ones, one for each successor of .
A matrix stores entries. Testing whether an arc exists takes one entry lookup; scanning the successors of a vertex takes a row scan of entries; and scanning all arcs takes time, however few arcs there are.
A second matrix records which vertices lie on which edges.
Definition 4.45 (Incidence matrix).
Let be a graph without loops, with vertices and edges . Its incidence matrix is the matrix with, for an undirected graph,
and for a directed graph if the arc begins at , if it ends at , and otherwise.
Example 4.46 (An incidence matrix).
Number the edges of the graph of Figure 4.3 as , , , . Its incidence matrix, with rows for the vertices , is
Every column has exactly two ones, the two ends of its edge, and the sum of the entries of row is the degree . Adding all the entries both ways is the count of edge-ends.
The memory requirements of the adjacency matrix and the incidence matrix are therefore and . For graphs with edges, which is often the case, this is far more than required: adjacency lists need memory proportional to .
For most purposes an adjacency list is the preferred data structure. It allows , or and , to be scanned for every vertex in time linear in their size, and its memory requirement is proportional to the number of vertices and edges, if we assume, as usual, that references, vertex numbers and edge numbers each need only a constant amount of memory. In the Word-RAM this is the assumption that one machine word holds any of them, which allows for vertex numbers. Graph algorithms are therefore stated for this representation, and moving to the next edge in a list of edges, or reading an endpoint of an edge, is counted as an elementary operation. An adjacency matrix remains the better choice for a graph with very many edges, or when many single-arc tests are needed.
Proposition 4.47 (Powers of the adjacency matrix count walks).
Let be the adjacency matrix of a directed -graph, and . Then is the number of walks of length from to .
Discussion.
The definition of the power is a recursion on , so the proof is an induction on . For the step, a walk of length from to is a walk of length from to some vertex followed by one arc from to , and , the second-to-last vertex, is determined by the walk. So the walks can be sorted by , and the number through is the number of -walks from to times , which is or according as the last arc exists. Summing over is exactly the formula for the entry of . Walks, not paths, are counted: vertices may repeat.
Proof.
For , has in position and elsewhere, and there is exactly one walk of length from each vertex to itself and none to any other vertex.
Suppose the claim holds for . Every walk of length has a unique second-to-last vertex , and consists of a walk of length from to followed by the arc . For fixed there are choices of the first part, by the hypothesis, and choices of the last arc, so walks pass through last. Summing over ,
The directed graph has adjacency matrix . Its square is : there is one walk of length two from each vertex back to itself, and none to the other vertex.
Reachability from Matrix Powers
To turn walk counts into yes-or-no answers, let when and when , applied to every entry of a matrix.
Definition 4.48 (Transitive closure).
For a directed graph on vertices with adjacency matrix , the reflexive transitive closure is
and the transitive closure is
By the proposition, exactly when is reachable from , the walk of length zero included: a shortest walk to a different vertex is a simple path and uses at most arcs. Likewise exactly when there is a walk of positive length from to , and . The power matters on the diagonal, since a shortest closed walk of positive length may be a cycle of length . Thus need not be , while always.
In the two-vertex graph with no return arc,
and the difference on the diagonal is exactly the zero-length walk.
Computing from its definition takes matrix products; repeated squaring needs far fewer.
Proposition 4.49 (Closure by repeated squaring).
For every ,
Discussion.
Multiplying out the product, each term chooses from every factor either or , and the chosen powers multiply to raised to a sum of distinct powers of two. The claim is that every exponent arises exactly once, which is the existence and uniqueness of the binary expansion of a number below . The proof is an induction on : multiplying the sum up to by keeps the sum and adds a copy shifted up by , which fills in the exponents .
Proof.
For both sides are . Suppose the identity holds for . Since powers of commute with one another,
Let , so that . Powers beyond reveal no new reachable vertex, so , and we may replace every positive entry by after each product. The same result follows by working throughout with Boolean matrix addition (OR) and multiplication (AND followed by OR). For there are squarings to form and products to combine the factors: at most matrix products in total. One more multiplication by gives . For , and no product is needed. For comparison, takes products by repeated multiplication when , and at most by squaring, where is the number of digits in the binary expansion of . These counts are of matrix products; each product of two matrices takes arithmetic operations.
Reflexive Transitive Closure
Input: the adjacency matrix M of a directed graph on n vertices.
Output: the matrix R with R[i][j] = 1 exactly when j is reachable from i.
R ≝ sgn(I + M); P ≝ M; k ≝ 2
while k < n do
P ≝ sgn(P · P)
R ≝ sgn(R · (I + P))
k ≝ 2k
return R
At each test of the loop, and , by the proposition; the loop stops once , when covers every power up to . In Python a matrix is a list of rows, each row a list, and M[i][j] is the entry in row i and column j, numbered from .
def product(X, Y): # Boolean product, O(n^3)
n = len(X)
Z = [[0] * n for _ in range(n)]
for i in range(n):
for j in range(n):
for l in range(n):
if X[i][l] == 1 and Y[l][j] == 1:
Z[i][j] = 1
return Z
def plus_identity(X): # sgn(I + X)
n = len(X)
return [[1 if i == j else X[i][j] for j in range(n)] for i in range(n)]
def closure(M):
n = len(M)
R = plus_identity(M)
P = M
k = 2
while k < n:
P = product(P, P)
R = product(R, plus_identity(P))
k = 2 * k
return R
M = [[0, 1, 0], [0, 0, 1], [0, 0, 0]] # 1 -> 2 -> 3, numbered 0, 1, 2
print(closure(M)) # [[1, 1, 1], [0, 1, 1], [0, 0, 1]]
The inner [0] * n must be built afresh for each row, which is what the comprehension does: [[0] * n] * n would make references to one and the same row.
Weight Matrices
For a weighted directed graph with vertices , the weight matrix is
The value means that there is no direct arc; it is a convention of computation, not an arc of the graph, and has entries in . A zero-weight arc is present and must not be confused with an absent arc.
The graph of Figure 4.9 has, with rows and columns in the order ,
Reading row : the arcs , and have weights , and . The entry says that there is no arc ; it says nothing about longer routes. For an undirected weighted graph the weight matrix is symmetric. A diagonal entry is unless a loop is present; a distance matrix, which puts on the diagonal, is a different object.
For the graph of Figure 4.8, write down the adjacency lists, the arrays and , and the adjacency matrix . Compute and , and check the entries and against walks you can list.
In a directed graph a universal sink is a vertex of in-degree and out-degree . Give an algorithm that decides whether a graph given by its adjacency matrix has a universal sink, using matrix lookups.
Exercises on Data Structures
An increasing subarray of an array of integers is a run of consecutive entries whose values strictly increase. Write a Python function count_long_subarrays(A) which takes a tuple of positive integers and returns the number of longest increasing subarrays of , that is, the number of increasing subarrays whose length is at least that of every other. For it should return , since the longest increasing subarrays have length three and there are two of them, and . Your function should run in time.
Order the following functions so that if appears before then , and indicate which pairs satisfy both and . Here means .
Let and be functions . Using the definition of , prove that .
A data structure supports the sequence operations D.build(X) in time, and D.insert_at(i, x) and D.delete_at(i) each in time, where is the number of items stored at the time of the operation. Using only these operations, describe algorithms for the following, each running in time. Recall that delete_at returns the deleted item.
reverse(D, i, k): reverse the order of the items of starting at index , that is, those at indices to .move(D, i, k, j): move the items of starting at index , in order, to be in front of the item at index , where is false.
Each node x of a doubly linked list keeps a reference x.prev to the node before it as well as x.next to the node after it, and the list L keeps L.head and L.tail, its first and last nodes. The list does not store its length.
- Describe algorithms for
insert_first(x),insert_last(x),delete_first()anddelete_last(), each in time. - Given two nodes
x1andx2of a listL, withx1beforex2, describe a constant-time algorithm that removes all nodes fromx1tox2inclusive fromLand returns them as a new doubly linked list. - Given a node
xof a listL1and a second listL2, describe a constant-time algorithm that splicesL2intoL1afterx, leavingL2empty. - Implement these operations in Python, in a class built like
Linked_List_Seq.
A student keeps pages of notes in a binder, the first at index and the last at index , and has two bookmarks and . Describe a data structure supporting the following operations, where is the number of pages at the time of the operation. Assume both bookmarks are placed before any shift or move, and that is always at a lower index than . For each operation, say whether your running time is worst-case or amortized.
| Operation | Effect | Time |
|---|---|---|
build(X) | initialise with the pages of the iterable X | |
place_mark(i, m) | place bookmark between the pages at indices and | |
read_page(i) | return the page at index | |
shift_mark(m, d) | move bookmark , in front of the page at index , to be in front of the page at index , for | |
move_page(m) | move the page in front of bookmark to be in front of the other bookmark |
- Insert the integer keys in this order into a hash table of size using the hash function . Each slot stores a linked list of the keys hashing to it, later insertions being appended at the end. Draw the table after all keys have been inserted.
- Suppose instead for a positive integer . Find the smallest for which no collisions occur when inserting these keys.
A university assigns new students to rooms, numbered to , by hashing their IDs. Each ID is a positive integer less than , with much larger than ; no two students have the same ID, and students choose their own IDs. The university publishes a family of hash functions before IDs are chosen, and afterwards chooses the rooming function uniformly from . Two students want to be roommates. For each family below, either show that they can choose IDs that guarantee it, or prove that no choice guarantees it and find the highest probability of being roommates they can achieve.
- .
- .
A wall is lined with boxes of paper, box standing feet from the left end and containing reams, where the are distinct positive integers. A pair of boxes is close if , and it fulfils an order of reams if . Given and , describe an algorithm running in expected time that decides whether contains a close pair fulfilling the order.
Imagine inserting the keys , in that order, into a hash table of size that resolves collisions by chaining, with . Draw the table after the insertion of the keys up to . Explain how the table evolves for arbitrary , and derive the worst-case time for the whole operation of inserting the keys.
Exercises on Graphs
Construct a -regular graph on vertices. Is there a -regular graph on vertices?
Show that every graph whose average degree is has a subgraph in which every vertex has degree at least .
Let be a graph with no loops in which every vertex has the same odd degree . Show that the number of edges is a multiple of and that the number of vertices is even.
- Can line segments be drawn in the plane so that each intersects exactly others? Prove your answer.
- Suppose a simple graph has exactly two vertices of odd degree. Prove that there is a path between them.
Check Yourself
Fresh questions on the whole lesson — none of them is worked out above. Work each one out on paper before opening Python; the box only tells you whether you got there.
Answers are checked in your browser, as often as you like. Nothing is sent anywhere and
nothing is kept but your own work. A formula may be written with the symbols themselves or
with ~ & | -> <-> ^, and \and, \or, \to expand as you type.
Which class contains ?
What is the least number of comparisons that finds the maximum of distinct numbers in the worst case?
How many numbers does the sieve of Eratosthenes output for ?
Starting from an empty Dynamic_Array_Seq with , how many slots are allocated after calls of insert_last?
What is the worst-case cost of get_at(i) in Linked_List_Seq with items?
Twenty-five objects are placed in seven boxes. What is the largest number that is certain to be in some single box?
Ten distinct keys are stored with chaining in a table of slots, the hash function drawn from a universal family. What bound does the proposition on chain length give for the expected length of the chain holding a given stored key?
A simple graph has edges. What is the sum of its vertex degrees?
How many edges does have?
How many edges does have?
For the directed graph with arcs and and adjacency matrix , what is the entry ?
Lesson 5
Trees, Searching and Sorting
Taught
Connectivity and Trees
Connectivity
Definition 5.1 (Connected graph).
An undirected graph is connected if every vertex is reachable from every other vertex. By the proposition on walks and simple paths, equivalently, every pair of distinct vertices is joined by a simple path.
The graph of Figure 5.1 is disconnected: no path leads from to . Its vertices split into and .
Definition 5.2 (Connected component).
A connected component is a maximal connected induced subgraph: no further vertex of the graph can be included while keeping it connected. A graph is connected exactly when it has one component, and an isolated vertex forms a component by itself.
Theorem 5.3 (Edges of a connected graph).
A connected simple undirected graph with vertices and edges has .
Discussion.
The first proof grows the graph from one vertex. At each stage connectivity supplies an edge leaving the part reached so far, and that edge brings in exactly one new vertex, so reaching all vertices uses distinct edges of the graph.
The second proof is an induction on that removes a vertex of smallest degree . If , counting edge-ends gives directly and no induction is needed. If , removing a vertex of degree one loses exactly one edge, and what is left is still connected, because a vertex of degree one cannot lie in the middle of a path. The hypothesis then applies to the smaller graph. The hypothesis has to be stated for every connected graph on vertices, since it is applied to a graph that the proof constructs.
Proof.
Choose one vertex and mark it reached. If fewer than vertices are reached, connectivity guarantees an edge from some reached vertex to an unreached one; otherwise no path could lead out of the reached set. Mark that new vertex and keep the connecting edge. Each step reaches exactly one new vertex and keeps an edge not kept before. After steps all vertices are reached, and we have found distinct edges of . Therefore .
A second proof, by induction, removes a vertex at each step.
Proof.
For each integer , let be the statement: every connected simple undirected graph with vertices has at least edges.
Base case, . A simple graph with one vertex has no pair of distinct vertices to join, so it has edges. It is connected, and .
Step. Fix and assume . Take an arbitrary connected simple graph with and , and let be its smallest degree. Because , a vertex of degree zero would have no path to any other vertex, contradicting connectivity, so . Either or .
Case . Every one of the vertices has degree at least , so . Counting edge-ends gives . Therefore , so . This case needs neither the removal of a vertex nor .
Case . Some vertex has exactly one edge, say . Remove and that edge, and call the result . No other edge is removed, because no other edge touches , so has vertices and edges.
Before applying we check that is connected. Take distinct vertices of . Since is connected, a simple path joins to in . Neither endpoint is , because and remain. Nor can be an internal vertex of the path: the path would have to enter along one edge and leave along a second, distinct edge, and has only the edge . So the path uses neither nor its edge, and it is a path in . This holds for every pair , so is connected; when , has a single vertex and is connected by definition.
Now satisfies the hypotheses of : it is simple, connected, and has vertices. Hence , and adding to both sides gives . Both cases prove from , and with induction proves the theorem for every .
A connected graph with five vertices needs at least four edges, and a path through all five vertices attains the bound. Adding one edge to that path gives five edges and creates a cycle; the theorem does not claim that every connected graph has exactly edges.
Definition 5.4 (Articulation vertex, bridge and vertex cut).
An articulation vertex is a vertex whose deletion, together with its edges, increases the number of connected components. A bridge, also called an isthmus, is an edge whose deletion increases that number. A vertex cut of a connected graph is a set of vertices whose deletion disconnects it, provided at least two vertices remain afterwards.
In Figure 5.1, the edge is a bridge: deleting it separates from and . The vertex is an articulation vertex: deleting it leaves and in separate components. All three edges of the left component are bridges too, since that component is a path through four vertices.
Strong Connectivity
For a directed graph, weak connectivity means connectivity once the directions of the arcs are ignored. Requiring directed routes both ways is a stronger condition.
Definition 5.5 (Strong connectivity and the reduced graph).
A directed graph is strongly connected if for every there is a directed walk from to and one from to . A strongly connected component is a maximal strongly connected induced subgraph. The reduced graph, or condensation, has one vertex for each strongly connected component, and an arc whenever and some arc of the original graph goes from a vertex of to a vertex of .
In Figure 5.2, the left graph is strongly connected. In the right one every arc points away from and towards , so each of its four vertices is a strongly connected component by itself.
In Figure 5.3, and are each strongly connected, and the only arcs between them point from the first set to the second. The reduced graph has two vertices and one arc . There cannot be an arc back: that would give reachability both ways and merge the two into one component.
Proposition 5.6 (The reduced graph has no directed cycle).
The reduced graph of any directed graph contains no simple directed cycle.
Discussion.
A cycle through several components would let every vertex in any one of them reach every vertex in the others and come back, so all of them would lie in a single strongly connected component. That contradicts maximality. The proof replaces each arc of the reduced graph by a walk in the original graph.
Proof.
The reduced graph has no loops, since its arcs join distinct components. Suppose were a simple directed cycle of it with . Each arc comes from an arc of the original graph from some vertex of to some vertex of , and within each component any two vertices are joined by walks both ways. Concatenating, any vertex of can reach any vertex of , and any vertex of can reach any vertex of around the rest of the cycle. So induces a strongly connected subgraph larger than , contradicting the maximality of .
Show that a simple undirected graph on vertices with more than edges is connected, and give a disconnected graph with exactly edges.
Find the strongly connected components and the reduced graph of the graph in Figure 4.8 of Lesson 4.
Trees
Definition 5.7 (Tree and forest).
A tree is a connected acyclic undirected graph. A forest is an undirected graph whose components are trees.
The tree on the left of Figure 5.4 has seven vertices and six edges.
Proposition 5.8 (Counting the edges of a forest).
An acyclic undirected graph with vertices, edges and components satisfies .
Discussion.
The proof is an induction on the number of edges, and the step removes one. In an acyclic graph every edge is a bridge: if its endpoints were still joined after deleting it, that route together with the edge would be a cycle. So deleting an edge lowers by one and raises by one, and is unchanged. The base case, a graph with no edges, has every vertex as its own component.
Proof.
For every vertex is its own component, so and . Assume the formula for all acyclic graphs with edges, and take one with edges. Delete one edge. Its endpoints cannot remain joined by another path, or that path together with the deleted edge would be a cycle. Hence the component containing the edge splits in two and the number of components rises from to . The remaining graph is acyclic with edges, so the hypothesis gives .
In particular a forest with vertices and components has edges.
Theorem 5.9 (Six characterisations of a tree).
Let be a finite simple undirected graph with vertices. The following are equivalent.
- is connected and acyclic.
- is acyclic and has edges.
- is connected and has edges.
- is acyclic, and adding any missing edge creates exactly one simple cycle.
- is connected, and deleting any edge disconnects it.
- Every pair of distinct vertices is joined by exactly one simple path.
In particular each condition characterises a tree. For the clauses about adding or deleting an edge are vacuous.
Discussion.
Six conditions are proved equivalent by a cycle of implications, , so that each follows from each. The proof uses two facts. One is the relation between a cycle and two different paths between the same vertices: a cycle gives two routes around it, and two different paths give a cycle where they separate and rejoin. The other is the edge count for acyclic graphs, which converts between “has edges” and “has one component”. The last implication, , uses the growing argument from the edge bound for connected graphs: it produces edges forming a connected acyclic spanning subgraph, and a graph with only edges has no others.
Proof.
. Connectivity supplies a simple path between every pair. If two different simple paths joined the same pair, follow them from the common start until they first diverge, and then along the first until it next meets the second; the two segments between those meeting points form a cycle. Thus the path is unique.
. is connected by 6. The unique simple path between the endpoints of an edge is that edge itself, so deleting it leaves the endpoints with no path between them.
. If had a cycle, deleting one of its edges would leave the endpoints of that edge connected along the rest of the cycle, so it would not disconnect . So is acyclic. By connectivity a simple path joins the endpoints of any missing edge, and adding the edge closes the path into a simple cycle. Now is connected and acyclic, so by that path is unique, and every simple cycle through the new edge consists of the new edge and a simple path of between its endpoints; so the new simple cycle is unique.
. If had two components, adding an edge between them would create no cycle, contrary to 4. Thus , and since is acyclic the edge count gives .
. Since is acyclic, the edge count gives . One component means that is connected.
. Because is connected, start at one vertex and repeatedly add an edge to a new vertex until every vertex is reached, as in the first proof of the edge bound. The chosen edges form a spanning subgraph with exactly edges, one for each vertex other than the first. It is connected, since every vertex was joined to one reached earlier, and acyclic, since each edge was added to a new vertex and so cannot close a cycle among those already reached. Since itself has exactly edges, it has no others, so is this subgraph and is acyclic.
In the tree of Figure 5.4 the unique path from to is , of length . Removing separates it into two components of sizes and . Adding the missing edge creates the unique cycle .
Rooted Trees
Definition 5.10 (Out-arborescence).
An out-arborescence rooted at is a directed graph in which there is exactly one directed simple path from to each vertex, and whose underlying undirected graph is a tree. Equivalently, has in-degree zero, every other vertex has in-degree one, and every vertex is reachable from . It has arcs.
Orienting the tree of Figure 5.4 away from , as , , , , and , gives the out-arborescence with root on the right of the figure.
Definition 5.11 (Rooted and binary trees).
A rooted tree is a tree with one vertex chosen as its root. For a vertex , the vertex before on the unique path from to is the parent of , and is a child of its parent. A vertex with no children is a leaf, and the others are internal. The depth of is , the length of the path from to , and the height of the tree is the largest depth of a vertex. The subtree at consists of and every vertex whose path from passes through , rooted at .
A binary tree is a rooted tree in which every vertex has at most two children, each labelled as a left child or a right child, with at most one of each. It is full if every internal vertex has exactly two children.
Orienting each edge of a rooted tree from parent to child gives an out-arborescence rooted at , since the unique path from to in the tree becomes the unique directed path. The subtrees at the children of the root of a binary tree are again binary trees, of height one less at most.
Proposition 5.12 (How large a binary tree of given height can be).
A binary tree of height at most has at most leaves and at most vertices.
Discussion.
A binary tree consists of its root and at most two smaller binary trees below it, so both counts are proved by induction on the height. Each subtree at a child of the root has height at most , so by induction each has at most leaves and vertices, and there are at most two of them. The leaves of the whole tree are the leaves of the subtrees, unless the root has no children, and the vertices are those of the subtrees plus the root. The recurrence with is the Tower of Hanoi recurrence shifted by one.
Proof.
We use induction on . A binary tree of height is a single vertex, which is a leaf: leaves and vertices.
Let and suppose the claim holds for height at most . If the root has no children the tree has one leaf and one vertex. Otherwise the root is not a leaf, and every other vertex lies in the subtree at exactly one of the at most two children of the root; each such subtree is a binary tree of height at most . So there are at most leaves, and at most vertices.
Corollary 5.13 (Height of a binary tree).
A binary tree with vertices has height at least , and a binary tree with leaves has height at least .
Proof.
If the height is , the proposition gives , so , and since is an integer, . Likewise gives , and so .
Show that every tree with at least two vertices has at least two leaves, that is, vertices of degree one, and that a full binary tree with leaves has exactly internal vertices.
Count the binary trees with , , and vertices, where two binary trees are the same only if they have the same shape including the left and right labels. Find a recurrence for the number with vertices by considering the sizes of the two subtrees at the root.
Searching
To search an array for a key is to return an index with , or to report that there is none. This is the find(k) operation of the set interface on an array.
Linear Search
Linear search inspects the entries in order and returns the first index whose key equals . The entries need not be in any order.
Linear Search
Input: an array T[0], …, T[n − 1] and a key x.
Output: an index i with key(T[i]) = x, or −1 if there is none.
for i ≝ 0 to n − 1 do
if key(T[i]) = x then return i
return −1
def linear_search(T, x):
for i in range(len(T)):
if T[i] == x:
return i
return -1
T = [7, 3, 11, 2, 5]
print(linear_search(T, 2), linear_search(T, 4)) # 3 -1
The invariant before the pass for is that does not occur among . It holds trivially at , a pass that does not return extends it by one position, and when the loop ends without returning it says that does not occur at all. The variant is .
Proposition 5.14 (Cost of linear search).
Linear search on an array of entries makes at most comparisons with , and exactly when does not occur. If occurs exactly once, at each of the positions with equal likelihood, the mean number of comparisons is . So linear search takes time in the worst case and on average.
Discussion.
Each pass makes one comparison, and the pass for is reached only if was not among the first entries, so the count is the position at which the search stops, plus one. The worst case is an absent key, which runs through all passes. For the mean, a key at position costs comparisons, and averaging is the sum of an arithmetic progression divided by .
Proof.
The loop makes one comparison per pass and at most passes, and it makes all when no entry has key . If occurs exactly once, at position , the search stops after the st comparison. Averaging over the equally likely positions and using the sum of an arithmetic progression,
Each comparison and each pass cost a bounded number of elementary operations, so the running time is in both the worst case and the mean.
On an unsorted array no comparison algorithm does better. We prove this with an adversary, as in the second proof of the proposition on finding a maximum.
Proposition 5.15 (Searching an unsorted array).
Every deterministic algorithm that decides whether occurs in an array of entries, and learns about the entries only by comparing them with , compares with all entries on some input.
Discussion.
An adversary answers every comparison as though the entry compared were larger than , and fixes the entries only as far as those answers require. If the algorithm stops before comparing with some entry, that entry is still free. The adversary then sets it equal to if the algorithm answered “absent”, and to anything else if it answered “present at that entry”, and in either case the answers given stay true while the output becomes wrong.
Proof.
Answer every comparison of with an entry as . Suppose that on these answers the algorithm stops having compared with only some of the entries, and let be one it never compared. Every compared entry can be given a key larger than , consistently with all the answers. If the algorithm reports that does not occur, give the key : the answers are unchanged, so a deterministic algorithm gives the same wrong report. If it reports an index , then either was compared and has key larger than , or and we give a key different from ; in both cases the report is wrong. So on some input every entry is compared with .
Suppose that is absent with probability , and otherwise lies at each of the positions with probability . Find the mean number of comparisons made by linear search.
A sentinel search appends at the end of the array before searching, so that the loop needs no test of the index against . Write it in Python using append and pop, show that it is correct, and count its comparisons of keys and of indices, compared with the linear search above.
Binary Search
When the array is sorted we can search it by bisection, as in Lesson 3, halving a range of positions instead of an interval of reals.
Let be sorted in nondecreasing key order, meaning . Binary search for a target key keeps a candidate interval of positions , that is . It compares with the key at the middle position . If they are equal it returns ; if is smaller it sets ; otherwise it sets . It returns when the interval is empty, .
Binary Search
Input: an array T[0], …, T[n − 1] sorted by key, and a key x.
Output: an index m with key(T[m]) = x, or −1 if there is none.
L ≝ 0; R ≝ n
while L < R do
m ≝ L + ⌊(R − L) / 2⌋
if x = key(T[m]) then return m
if x < key(T[m]) then R ≝ m
else L ≝ m + 1
return −1
def binary_search(T, x):
L, R = 0, len(T)
while L < R:
m = L + (R - L) // 2
if x == T[m]:
return m
if x < T[m]:
R = m
else:
L = m + 1
return -1
T = [2, 3, 5, 7, 11, 13, 17]
print(binary_search(T, 11), binary_search(T, 4)) # 4 -1
The invariant is: if occurs in , then at least one matching index lies in . It holds initially, when the interval is the whole array. Sortedness maintains it: if then every position from on has key at least and can be discarded, and symmetrically when . At termination either a match has been returned or is empty, and then by the invariant does not occur. The length is a variant: it strictly decreases at every pass. We count one comparison per pass, a three-way comparison of with whose outcomes are smaller, equal and larger.
Theorem 5.16 (Binary search takes comparisons).
For , binary search on a sorted array of length makes at most passes of its loop.
Discussion.
The proof is the one for the convergence of bisection, with the length of the interval in place of its width. Because the middle position is removed from the interval, a pass leaves at most of the positions rather than exactly half. After passes the length is therefore at most , which is zero once , and the least such is .
Proof.
Let at the start of a pass, so that . If the pass does not return, the new interval is , of length , or , of length . So after each pass the length is at most , and since for , after passes it is at most . For we have , so the length is and the loop has stopped.
The bound is attained, for example by an unsuccessful search for a key smaller than every key in the array: it moves left at every pass, and the length goes down to .
For the positions tested form the binary tree of Figure 5.5: the root is the first position tested, and the left and right children of a position are the ones tested next when the target is smaller or larger. A search that stops at depth has made comparisons.
Proposition 5.17 (Mean cost of a successful search).
Let and suppose the target is one of the keys, each equally likely, with all keys distinct. The mean number of comparisons made by binary search is
Discussion.
For every interval that arises has odd length and splits into two equal halves, so the tree of tested positions is full, with positions at depth for , and a target at depth is found after comparisons. That gives the sum. To evaluate we proceed as for the geometric sum and compare with , whose terms are those of shifted by one place with the multiplier lowered by one, so that the difference is a geometric sum.
Proof.
An interval of length with has middle position with positions on each side, so by induction on the positions tested form a full binary tree with positions at depth , for . The target at a position of depth is found at the th comparison, so the total over the equally likely targets is .
For ,
by the geometric sum. So with , and induction gives : at both sides are , and . Finally , and dividing by gives the stated mean.
The mean is . For arbitrary interval halving gives an worst case, by the theorem. For a lower bound on the mean, the positions found within comparisons are those of depth less than in the tree of tested positions, and by the proposition on binary trees there are at most of them. Take for ; then , so fewer than half the positions are found within comparisons. At least half need more than , and the mean over equally likely targets is .
A Lower Bound for Searching
A deterministic comparison search algorithm can be pictured as a fixed binary decision tree of all its possible executions, in which each internal vertex is a comparison the algorithm makes. The algorithm walks down the tree from the root: the first comparison it makes is at the root, and according to the outcome it continues at one of the two children. It stops on reaching a leaf, which records its output, so there must be a leaf for every possible output. The number of comparisons on a given input is the depth of the leaf reached, and the worst-case number is the height of the tree.
Theorem 5.18 (Searching needs comparisons).
Every deterministic algorithm that searches a set of items with distinct keys for a given key, using only comparisons of keys, makes at least comparisons on some input.
Discussion.
The proof counts outputs. A search can end in different ways, one for each stored item and one for “not present”, and each must appear at some leaf of the decision tree. A binary tree with that many leaves has height at least by the corollary on heights, and height is the worst-case number of comparisons.
Proof.
The algorithm has possible outputs, the stored items and the report that no item has the key, and each occurs for some input, so its decision tree has at least leaves. By the corollary on heights of binary trees its height is at least , and some input follows a path of that length.
So binary search makes the least possible number of comparisons, up to a constant factor. The same argument with any fixed number of outcomes per comparison in place of two still gives . Hashing does better because it is not a comparison algorithm: reading a direct access array at an index computed from the key can go to any one of its slots in a single step.
Modify binary_search so that it returns the least index with , or if there is none, still in time. State the invariant, and use your function to count the entries of a sorted array lying in an interval .
A programmer writes L = m in place of L = m + 1. Give an array and a key for which the modified loop never terminates, and say which part of the termination argument fails.
Sorting
The Sorting Problem
We sort records, each carrying a key that can be compared with other keys. A comparison sort learns about the keys only by comparing them, as in the comparison model.
Definition 5.19 (Sorting, in place and stable).
A sort returns the same records as its input, rearranged in nondecreasing key order. A sort is in place if it uses auxiliary cells besides the input array, apart from stack space for recursion if that is being counted separately. A sort is stable if records with equal keys keep their input order.
Checking only that the output keys are in order is not enough to certify a sort: the output must also be a rearrangement of the input records, with none lost or duplicated. Stability can be forced in any comparison sort by comparing the pairs lexicographically, first by key and then by index, though storing the original indices may change the space requirements.
Example 5.20 (Stable and unstable).
Sorting stably by the numeric key gives : the two records with key remain in their original relative order. A routine that returns has sorted the records, but not stably.
A rearrangement of records is a permutation, written as the list of the input positions in output order, and there are of them. A brute-force method tests permutations one by one until it finds an arrangement in increasing order of pairwise distinct keys. A candidate may need comparisons of adjacent keys to certify that it is sorted, and there are candidates.
An inversion of an array is a pair of positions with .
A sorted array has no inversions, and an array of distinct keys in decreasing order has all possible ones, one for each unordered pair of positions. An exchange of two adjacent records with removes exactly one inversion, that pair itself, since the relative order of every other pair is unchanged.
Selection Sort
Selection sort repeatedly removes the largest remaining key and puts it at the next final position from right to left. Having already sorted the largest items into the subarray A[i+1:], it scans A[:i+1] for the largest item not yet placed and swaps it with A[i].
Selection Sort
Input: an array A[0], …, A[n − 1].
Output: the same array, sorted.
for i ≝ n − 1 down to 1 do
m ≝ i
for j ≝ 0 to i − 1 do
if key(A[m]) < key(A[j]) then m ≝ j
swap A[m] and A[i]
def selection_sort(A):
for i in range(len(A) - 1, 0, -1): # O(n) passes
m = i # O(1) index of the largest so far
for j in range(i): # O(i) search A[:i] for a larger item
if A[m] < A[j]: # O(1)
m = j # O(1) new largest found
A[m], A[i] = A[i], A[m] # O(1) swap
A = [5, 2, 9, 1, 5, 6]
selection_sort(A)
print(A) # [1, 2, 5, 5, 6, 9]
The invariant before the pass for is that A[i+1:] holds the largest records in sorted order, and that every key in A[:i+1] is at most every key in A[i+1:]. The pass moves the largest key of A[:i+1] to position , which extends the sorted suffix by one. When the loop ends, at , the suffix A[1:] is sorted and A[0] holds the smallest key.
Finding the maximum among remaining keys takes comparisons, which is the least possible by the proposition on finding a maximum, so the method uses
comparisons, even on an input that is already sorted. It performs at most swaps. It is in place: apart from the array it uses the names i, j and m.
On , selection chooses , then , then . Each chosen maximum occupies its final, rightmost open position, and the comparison counts are .
Selection sort, swapping as above, is not stable. On the records , compared by key alone, the first pass finds at position as the maximum and swaps it with at position , giving ; the second pass makes no change, and ends before .
Bubble Sort
A left-to-right pass of bubble sort compares with and exchanges them if they are out of order. The largest key in the scanned part moves to its right end, so a full pass places the maximum of the active prefix at its final position. Repeating on successively shorter prefixes sorts the array. If a pass makes no exchange, the array is already sorted and the algorithm can stop early.
Bubble Sort
Input: an array T[0], …, T[n − 1].
Output: the same array, sorted.
for last ≝ n − 1 down to 1 do
changed ≝ false
for j ≝ 0 to last − 1 do
if key(T[j]) > key(T[j + 1]) then
swap T[j] and T[j + 1]; changed ≝ true
if not changed then stop
def bubble_sort(T):
for last in range(len(T) - 1, 0, -1):
changed = False
for j in range(last):
if T[j] > T[j + 1]:
T[j], T[j + 1] = T[j + 1], T[j]
changed = True
if not changed:
return
The invariant before the pass with a given last is that T[last+1:] holds the largest records in sorted order, each at least every key in T[:last+1].
For , the worst-case number of comparisons is , and the best case, on a sorted input, is with the early stop. Over the orders of distinct keys, counted equally, the mean number of comparisons is still . For the lower bound, the smallest key begins in the last half of the array, at one of the positions from on, in at least half of the orders. A left-to-right pass moves the smallest key left by at most one position, so on those orders at least passes are needed, and each of those passes makes at least comparisons. Thus at least half the orders cost at least comparisons, and the mean is . The upper bound holds on every input.
Proposition 5.23 (Exchanges in bubble sort).
The number of exchanges bubble sort makes on an array equals its number of inversions. Over the orders of distinct keys, counted equally, the mean number of inversions is .
Discussion.
For the first statement, every exchange removes exactly one inversion, and the algorithm stops with a sorted array, which has none; so the number of exchanges is the number of inversions at the start. For the mean, pair each order with its reverse. A pair of positions is inverted in exactly one of the two, so the inversion counts of an order and of its reverse add up to . The reversal pairs the orders off, so the average over all of them is half of .
Proof.
Bubble sort exchanges and only when , and such an exchange of adjacent records removes exactly one inversion. It stops with a sorted array, which has no inversions. So if the input has inversions, it makes exactly exchanges.
For an order of distinct keys let be its number of inversions, and let be the reverse order. For each of the pairs of keys, exactly one of and lists the larger key first, so . Reversal is a bijection from the set of orders to itself, so summing over all orders,
and the mean is .
Exchanges happen only on strict inversions, so records with equal keys are never exchanged with each other and keep their original order: bubble sort is stable. Small keys near the right end move slowly, one position per pass, and are sometimes called turtles; large keys near the left move quickly to the right and are called hares. Bidirectional bubble sort alternates the direction of its passes to move turtles faster, and gnome sort steps back to recheck the previous adjacent pair after each exchange.
Insertion Sort
Insertion sort treats T[0..i-1] as sorted and inserts T[i] into that prefix by shifting the larger records one position right.
Insertion Sort
Input: an array T[0], …, T[n − 1].
Output: the same array, sorted.
for i ≝ 1 to n − 1 do
x ≝ T[i]; j ≝ i − 1
while j ⩾ 0 and key(x) < key(T[j]) do
T[j + 1] ≝ T[j]; j ≝ j − 1
T[j + 1] ≝ x
def insertion_sort(T):
for i in range(1, len(T)): # O(n) passes
x = T[i] # the record to insert
j = i - 1
while j >= 0 and x < T[j]: # O(i) shift larger records right
T[j + 1] = T[j]
j = j - 1
T[j + 1] = x # fill the gap
The invariant before the pass for is that T[0..i-1] holds the first input records in sorted order. The final assignment T[j+1] = x places the saved record in the gap left by the shifts, and it is essential: writing anywhere else would duplicate one record and lose another. The while test relies on and stopping as soon as j >= 0 is False, so that T[-1] is never read.
On , save , shift right and write at index , giving . Next save , shift right and write at index , giving .
Insertion sort is in the worst case, on an input in decreasing order, where the pass for shifts all records of the prefix, and on an already sorted input, where every pass makes one comparison and no shift.
In Place and Stable
Selection sort, bubble sort and insertion sort are all in place: each uses a constant amount of space besides the array, and acts on the array only by comparisons and by exchanging or moving records. Bubble sort and insertion sort are stable, insertion sort because a record is never shifted past one with an equal key. Selection sort, as implemented above, is not.
Show that insertion sort performs exactly as many shifts T[j + 1] = T[j] as the input has inversions, and deduce the mean number of shifts over the orders of distinct keys. Give an input of length on which selection sort makes swaps and insertion sort makes no shift.
Run bubble sort and insertion sort on , keeping the two s apart as and . Record the array after each pass of each algorithm, count the comparisons and exchanges or shifts, and confirm that both outputs keep before .
Merge Sort
Merge sort splits the array in half, sorts each half recursively, and merges the two sorted halves into one.
Proposition 5.25 (Merging two sorted arrays).
Two sorted arrays of lengths can be merged into one sorted array with at most key comparisons. If ties take the record from the left array first, the merge is stable.
Discussion.
The smallest remaining record of the whole is always at the front of one of the two arrays, because each array is sorted, so one comparison of the two front records decides which comes next. Each comparison outputs one record, and once one array is exhausted the rest of the other is copied without comparing. At least one record, the last, is output without a comparison, which gives . For stability, when the two front keys are equal, taking the left one keeps records from the left array ahead of equal records from the right.
Proof.
Keep a cursor at the first unmerged element of each array. While both arrays have elements left, compare their current heads and copy the smaller one to the output. Its key is no larger than any remaining key, because each input array is already sorted. So the output stays sorted, and no record is lost or copied twice. Each comparison advances one cursor, so after at most comparisons one array must be empty; the other array’s sorted tail is then appended without further comparisons. On equal keys, choosing the left head first preserves the original order between the two arrays, while the order within each array is unchanged.
Merge
Input: sorted arrays X and Y.
Output: a sorted array Z holding the records of X and Y.
i ≝ 0; j ≝ 0; Z ≝ empty
while i < length(X) and j < length(Y) do
if key(X[i]) ⩽ key(Y[j]) then append X[i] to Z; i ≝ i + 1
else append Y[j] to Z; j ≝ j + 1
append the remaining part of X, then of Y, to Z
return Z
Merge Sort
Input: an array A.
Output: a sorted array holding the records of A.
if length(A) ⩽ 1 then return A
h ≝ ⌊length(A) / 2⌋
return Merge(Merge Sort(A[0..h − 1]), Merge Sort(A[h..length(A) − 1]))
def merge(X, Y):
i, j, Z = 0, 0, []
while i < len(X) and j < len(Y):
if X[i] <= Y[j]:
Z.append(X[i])
i = i + 1
else:
Z.append(Y[j])
j = j + 1
return Z + X[i:] + Y[j:]
def merge_sort(A):
if len(A) <= 1:
return A
h = len(A) // 2
return merge(merge_sort(A[:h]), merge_sort(A[h:]))
print(merge_sort([15, 5, 64, 8, 12, 6, 4, 35])) # [4, 5, 6, 8, 12, 15, 35, 64]
On an array of records with comparable keys, merge sort returns a sorted rearrangement of the input, and choosing the left head on equal keys makes it stable. It uses time and auxiliary storage. For , its worst-case number of key comparisons is exactly .
Discussion.
Correctness is an induction on whose step is the merge proposition: the recursive calls are on shorter arrays, so by the hypothesis they return sorted rearrangements of the halves, and merging those gives a sorted rearrangement of the whole. For the time, we count level by level: the arrays at one level are disjoint pieces of the input, so merging all of them costs , and halving gives about levels. For the exact count, the merge proposition allows a merge of two halves of size up to comparisons, which gives the recurrence . It remains to find an input that attains the bound at every merge, and to solve the recurrence.
Proof.
We prove correctness by induction on . For the array is already sorted. If , both halves have fewer than elements, so the recursive calls terminate and, by induction, return sorted rearrangements of their halves. The merge proposition combines those into a sorted rearrangement of the whole input. Its tie rule preserves the order of equal-key records across the halves, so induction also proves stability. The recursion terminates because every call on more than one record is made on strictly shorter arrays.
At any fixed level of the recursion the subarrays are disjoint parts of the original array, so their lengths add up to at most . Merging each costs time proportional to its length, so the total work per level is . Halving gives at most levels of merging, and therefore time. At any instant the arrays alive along the current chain of calls have lengths at most together with their merge outputs, so at most auxiliary storage is present at once; the recursion stack has frames.
When , a merge of two sorted halves of equal size can need all comparisons the merge proposition allows: give the left half the records of odd rank and the right half those of even rank, so that their sorted values alternate until only one remains. Apply the same odd–even assignment recursively within each half. Arranging the input by these assignments makes every merge attain its worst case. Writing for the worst case at size , this gives and
We claim . At both sides are . Assuming the formula at ,
With this is . It is a count of key comparisons, and an array of one record makes none.
Write an iterative merge sort that merges adjacent runs of length , then , then , and so on, with no recursion. Prove it correct with a loop invariant on the run length, and show that it makes at most comparisons.
Quicksort
Quicksort chooses a pivot, partitions the array so that smaller keys precede the pivot and larger or equal keys follow it, and then recursively sorts the two sides. For the partition in place, with the pivot initially at position , the invariant before inspecting position is: positions hold keys smaller than the pivot, positions hold keys larger than or equal to it, and positions are unexamined. A newly found smaller record is swapped into position and is increased. At the end the pivot is swapped with the record at position . Partitioning records compares each of the other records with the pivot once.
The pseudocode below sorts the half-open slice of the original array in place; no slice is copied. The partition moves only records strictly smaller than the pivot to its left.
Quicksort
Input: an array A and positions L ⩽ R.
Output: A with A[L..R − 1] sorted.
Sort(A, L, R):
if R − L ⩽ 1 then return
choose q uniformly from L, …, R − 1; swap A[L] and A[q]
pivot ≝ A[L]; p ≝ L
for k ≝ L + 1 to R − 1 do
if key(A[k]) < key(pivot) then
p ≝ p + 1; swap A[p] and A[k]
swap A[L] and A[p]
Sort(A, L, p); Sort(A, p + 1, R)
The statement from random import randrange makes randrange available: randrange(L, R) returns an integer chosen uniformly from , the same range as range(L, R).
from random import randrange
def sort_slice(A, L, R):
if R - L <= 1:
return
q = randrange(L, R)
A[L], A[q] = A[q], A[L]
pivot = A[L]
p = L
for k in range(L + 1, R):
if A[k] < pivot:
p = p + 1
A[p], A[k] = A[k], A[p]
A[L], A[p] = A[p], A[L]
sort_slice(A, L, p)
sort_slice(A, p + 1, R)
def quicksort(A):
sort_slice(A, 0, len(A))
If the pivot has rank among distinct keys, meaning keys are smaller, the number of comparisons satisfies
For the input , take the first value as pivot, with no random swap. The partition puts the five smaller values before it and after it, and the in-place result is . Recursively sorting the two sides gives . The order within each side before the recursion depends on the partition routine.
Proposition 5.28 (Worst and best case of quicksort).
For distinct keys, any run of quicksort makes at most key comparisons. This bound is attained when every pivot is the smallest or largest key in its current subarray, so the worst-case number of comparisons is . If every pivot splits its subarray as evenly as possible, the number of comparisons is .
Discussion.
For the upper bound we count pairs of keys. Two keys are compared only when one of them is the pivot, and a pivot is left out of all later calls, so no pair is compared twice; there are pairs. When every pivot is extreme, one side of each partition is empty, and the recurrence becomes , which sums to the same number. For even splits, the depth of the recursion is logarithmic and each level costs at most comparisons, which gives ; the matching lower bound needs a count of how many levels still have large subarrays, and how many comparisons each such level makes.
Proof.
Partitioning a subarray of size compares its pivot with each of the other keys exactly once. A given pair of keys can be compared only when one of them is the pivot, and that pivot is then excluded from all recursive subarrays. Thus no pair is compared twice, and there are at most comparisons. If every pivot is extreme, one recursive side is empty and the other has size . The count obeys with , and hence
For balanced splits, the recursion has levels. At any level the active subarrays are disjoint, so their sizes add up to at most and their partitions make at most comparisons in total. This proves .
For the reverse bound, number the levels from . A balanced split of records leaves at least records on either side. Applying this at each level shows that every subarray at level has at least records. For this is at least , so no branch has yet ended at a call on one record. Before such a level at most pivots have been removed, and the remaining records all belong to at most active subarrays. A subarray of size uses comparisons, so level uses at least comparisons. There are such levels for large , which proves .
The in-place partition is not stable in general. The recursive calls use stack space for balanced splits and when the splits are as uneven as possible.
With a random pivot the cost is an average over the random choices, in the sense of the expectation defined for hashing in the last lesson.
Remark (Expected cost of a randomized algorithm).
The expectation used for hashing is an average over one uniform choice. Randomized quicksort makes a uniform choice at every call, and its expected cost is defined in the same way one call at a time: if a call on keys chooses the pivot rank with probability , and the rest of the run then has expected cost , the expected cost of the call is its own comparisons plus . Equivalently, it is the average over all complete runs, each run weighted by the product of the probabilities of the choices it makes.
Theorem 5.29 (Expected cost of randomized quicksort).
Fix any input order of distinct keys. At every recursive call choose the pivot uniformly from the current subarray, and compare it once with each other key there. Let be the expected total number of key comparisons. Then
In particular . The expectation is over the pivot choices; the input order is fixed and need not be random.
Discussion.
The proof has four steps. Conditioning on the rank of the first pivot turns the expectation into a recurrence in which depends on all of through their sum. Writing the recurrence at and at and subtracting removes the sum, leaving a first-order recurrence with variable coefficients, of the kind the summation factor of the last lesson solves; the example there is the same recurrence with in place of . Here the answer is checked by induction instead. Finally the bounds on give the growth rate.
Proof.
Step 1: condition on the first pivot. A subarray of size at most needs no comparisons, so . For the first partition costs exactly comparisons. The pivot is equally likely to have any rank among the keys. Rank leaves smaller keys on the left and larger keys on the right. Because every later pivot is chosen uniformly within its own subarray, the expected costs of those two calls are and , whatever their internal order. Averaging over the possible ranks gives
since both and run once through as runs through .
Step 2: remove the sum. Multiply this equation by , and write the same equation for multiplied by :
Subtract the second from the first. The two sums differ only by , while . Therefore , that is,
Step 3: solve by induction. At the claimed expression is . Assume for some . Substituting into the last recurrence and using ,
Dividing by gives .
Step 4: the growth rate. By the bounds on the harmonic numbers, . The upper bound gives . The lower bound gives , and for all sufficiently large the term exceeds twice the rest, so . Thus .
For three distinct keys, a smallest or largest first pivot costs two comparisons and leaves a call on two keys costing one more, for a total of . A middle first pivot costs two comparisons and leaves only calls on one key, for a total of . The three ranks are equally likely, so . The formula agrees: .
Show that on the input quicksort with the first record always taken as pivot makes comparisons, and that with a uniformly random pivot the probability of making all is for .
Two keys of ranks are compared by randomized quicksort exactly when the first pivot chosen from the keys of ranks is one of the two. Deduce that they are compared with probability , and use linearity of expectation to give a second proof that .
Lower Bounds for Comparison Sorting
A deterministic comparison sort is described, like a comparison search, by a binary decision tree: each internal vertex is a comparison, the two branches below it are its two outcomes, and each leaf records the rearrangement the algorithm outputs. An algorithm may ask a comparison whose result is already forced by earlier answers, and then one branch below it is reached by no input.
The comparison tree of Figure 5.6 names the compared records rather than their changing positions in the array. Its six reachable leaves are the six possible strict orders; the crossed leaves are impossible by transitivity.
Theorem 5.31 (Worst-case lower bound for sorting).
Every deterministic sorting algorithm that learns about distinct, otherwise arbitrary keys only by comparing pairs makes at least comparisons on some input. Hence its worst-case number of comparisons is .
Discussion.
As for searching, the proof counts outputs, and there are now of them. Two inputs whose keys are in different relative orders need different rearrangements, so they must reach different leaves, and the decision tree has at least reachable leaves. The corollary on heights of binary trees then bounds the height below by . For the growth rate, half of the factors of are at least , so is at least about .
Proof.
There are possible relative orders of the distinct input keys: choices for the rank of the first record, for the second, and so on. Represent the algorithm by its binary decision tree. Each input follows one path from the root to a leaf, and the length of that path is its number of comparisons. Two different relative orders cannot end at the same leaf: that leaf prescribes one output rearrangement for both, and for at least one of them it is wrong. Therefore the tree has at least reachable leaves, and after removing the unreachable branches it is a binary tree with at least leaves. By the corollary on heights its height satisfies .
The last factors of are each at least , so and
Removing the branches that no input can follow, and contracting each vertex left with one child, turns the decision tree into a full binary tree with one leaf per possible outcome.
Merge sort makes at most comparisons, so it is asymptotically optimal among comparison sorts in the worst case.
The mean number of comparisons over the equally weighted orders of distinct keys is the mean depth of the leaves of the decision tree, one leaf per order. A full binary tree with leaves is balanced if its leaf depths differ by at most one.
Proposition 5.32 (Balanced trees minimise the total depth).
Among full binary trees with leaves, a balanced one has the least sum of leaf depths, and in a balanced full binary tree with leaves every leaf has depth at least .
Discussion.
The first claim is proved by an exchange that lowers the depth sum of an unbalanced tree. If some leaf is at least two levels above the deepest leaves, move a deepest pair of sibling leaves together with their parent into the place of , and put where their parent was. Three depths change, and the sum falls. The depth sum is a nonnegative integer, so the exchange can be repeated only finitely often, and it stops at a balanced tree. For the second claim, look at the shallowest leaf: every level above it is full, and all leaves lie on its level or the next, which bounds by twice the size of its level.
Proof.
Take a full binary tree that is not balanced. Choose deepest sibling leaves , at depth , with parent , and a leaf at depth . Exchange the subtree consisting of , and with the leaf . The tree remains a full binary tree with the same leaves, and move to depth , and moves to depth . The sum of these three depths changes from to , a decrease of , and no other depth changes. Repeating the operation reaches a balanced tree, because the nonnegative integer depth sum strictly decreases each time. Every tree with leaves can be transformed in this way into a balanced one with no larger depth sum, and all balanced full binary trees with leaves have the same multiset of leaf depths, as the count below shows; so a balanced tree has the least sum.
Let the shallowest leaf of a balanced full binary tree have depth . There is no leaf above depth , so every vertex at depth less than is internal with two children, and depth holds vertices. The leaves are at depth or , and those at depth are the children of the internal vertices at depth , so . At least one vertex at depth is a leaf, so . This determines , and with it the number of leaves at each depth, and every leaf has depth at least .
For example, six leaves can occur at depths and . The proposition gives the asymptotic bound: the mean depth of any full binary tree with leaves is at least , which is . The exact bound is proved differently.
Theorem 5.33 (Average lower bound for sorting).
Suppose the relative orders of distinct keys are equally likely. Every deterministic comparison sort has mean number of comparisons at least , and therefore .
Discussion.
The proof turns the leaf depths into lengths of intervals. Each leaf is reached by a word of left and right choices, and a word of length picks out a subinterval of of length by halving repeatedly. Since no leaf’s word begins another leaf’s word, the intervals do not overlap, so their lengths add up to at most . The mean depth is then bounded below by comparing the arithmetic mean of the numbers with their geometric mean. The inequality of the means was proved for two numbers in Lesson 1; the proof extends it to numbers by doubling up to a power of two and padding.
Proof.
Keep one reachable leaf of the decision tree for each of the relative orders, and let their depths be ; the mean number of comparisons is . Each path from the root to a leaf is a word of left and right choices. No leaf’s word can be a prefix of another leaf’s word, because a computation stops when it reaches a leaf. To a word of length associate the subinterval of of length obtained by choosing the left or right half at each successive letter. The intervals for distinct leaf words do not overlap, so their total length is at most :
Write and , so that . We need the inequality of arithmetic and geometric means for positive numbers, . For two numbers it is . For a list of numbers, split it into two equal halves with geometric means and arithmetic means ; by induction on , and , so the whole list has geometric mean , its arithmetic mean. For general , choose a power of two and append copies of to the list . The enlarged list still has arithmetic mean , so the power-of-two case gives , and cancelling the positive factor gives .
Substituting ,
Taking base-two logarithms and multiplying by , which reverses the inequality, gives . Finally , as in the worst-case bound.
With six equally likely outcomes, a full binary tree may have two leaves at depth and four at depth . Its mean depth is , which is at least . The same leaf-depth argument applies to the possible input orders.
Sorting algorithms that are faster than exist, such as counting sort and radix sort, but only because they use information about the keys beyond pairwise comparison, for instance that they are small integers usable as array indices.
Draw a decision tree for insertion sort on three distinct keys , and give its height and the mean depth of its leaves. Compare both with and .
Show that five distinct keys can be sorted with comparisons in the worst case, and that no comparison sort does it with .
Sorted Arrays as Sets
A sorted array implements the set interface. Building it is sorting, by merge sort. find(k) is binary search, . The smallest and largest keys are at the two ends, and find_next(k) and find_prev(k) are binary searches for the position where would go. Inserting or deleting while keeping the order shifts up to items.
| Data structure | build(X) | find(k) | insert(x), delete(k) | find_min(), find_max() | find_prev(k), find_next(k) |
|---|---|---|---|---|---|
| Array | |||||
| Sorted array | |||||
| Direct access array | |||||
| Hash table | (e) | (e) | (a)(e) |
Each entry is an bound; (e) marks an expected bound and (a) an amortized one. For the sorted array, find is optimal among comparison algorithms by the lower bound for searching, and the bound does not apply to the hash table, which is not a comparison algorithm.
Implement the set interface on a sorted array as a class Sorted_Array_Set, using merge_sort in build and binary search in find, find_next and find_prev, with the running times of the table.
Exercises on Connectivity and Trees
How many spanning subgraphs of are trees?
Prove that every connected graph with at least two vertices has a vertex that is not an articulation vertex.
Let , and let be integers. Show that there is a tree with vertex degrees if and only if and .
Let be subtrees of a tree , meaning subgraphs that are trees, any two of which have at least one vertex in common. Prove that some vertex lies in every .
The radius of a connected graph is , the least over all vertices of the greatest distance from to another vertex. Let be the cycle on vertices, with vertices and edges and . Find the radius and the diameter of , of and of .
A directed graph has vertices. Its underlying undirected graph has one connected component of size , and has one strongly connected component of size ; its other strongly connected components are smaller.
- Suppose vertices are reachable from a vertex . Is in the component of size ? Explain.
- Is in the strongly connected component of size ? Explain.
- Suppose vertices are reachable from a vertex , and vertices are reachable from once every arc is reversed. Is in the strongly connected component of size ? Explain.
- Prove that for every vertex of the component of size , there is a directed walk from to or one from to .
The divisibility graph has vertices and an edge between whenever one of them divides the other.
- Draw . How many connected components does it have, and what is the size of its largest clique?
- Prove that for every , a graph with no loops in which every vertex has degree at least contains a simple cycle through at least vertices.
Exercises on Searching
A narrow island runs north–south for kilometres, and a searcher must locate a friend to the nearest kilometre. A tracking device tells the searcher whether the friend is north or south of the current position, but not how far, and a teleporter jumps to any given kilometre in constant time. If the friend is kilometres from the nearer end of the island, at kilometre or , describe an algorithm that finds the friend after visiting locations.
A ridge is given as an array of distinct altitudes. A point is a good collection point if it is lower than all its neighbours in the array. Design an algorithm running in time that finds a good collection point, prove it correct, and prove its running time.
Exercises on Sorting
Implementations of insertion sort and merge sort run on the same machine. On inputs of size , insertion sort takes steps and merge sort takes steps. For which values of does insertion sort beat merge sort?
Person , for , enters a room at time and leaves at time , all the being distinct. The lights are off at the start of the day; the first person to enter switches them on, and a person who leaves an empty room switches them off. Given , we want the number of times the lights are switched on. Design, and prove correct and costed,
- a algorithm, and
- an algorithm.
For each scenario choose selection sort, insertion sort or merge sort, and justify the choice by asymptotic running time.
- A data structure maintains an extrinsic order on items, with
D.get_at(i)in worst-case time andD.set_at(i, x)in worst-case time. Sort the items of in place. - A static array holds references to comparable objects, any two of which take time to compare. Sort the references so that the objects appear in nondecreasing order.
- A sorted array of integers, each fitting in a machine word, has had exchanges made between pairs of adjacent items. Re-sort it.
- Describe the principle of merge sort, and show the steps it takes to sort the array .
- Insertion sort can be seen as a merge sort in which each step splits an array of size into one of size , the element to be inserted, and one of size . By solving the appropriate recurrence, show that this recursive insertion sort takes time, assuming that merging two arrays takes time.
- Show that merge sort on a linked list takes time, and that it can be done with auxiliary space apart from the recursion, showing how. A programmer who can merge arrays only with extra space proposes to convert arrays to linked lists before sorting them, to save space. Comment on this strategy.
- Suppose that quicksort always partitions into two parts of relative sizes and , for a constant . Ignoring rounding, find the least depth of a leaf in the recursion tree as a function of and .
- How long does the quicksort of these notes take if all the keys are equal? Explain.
- What are the advantages and disadvantages of choosing the pivot at random? How does it affect the worst-case and the average-case running time?
How would you construct an input that makes randomized quicksort take quadratic time, without access to the state of the random number generator?
Merge sort can be implemented as the usual two-way merge sort, or as a three-way merge sort that splits its input into three and sorts each part recursively.
- Find the worst-case number of comparisons needed to merge two sorted arrays of length .
- Find the worst-case number of comparisons needed to merge three sorted arrays of length , both by merging all three at once and by merging in pairs.
- Using these, and solving suitable recurrences, find the total number of comparisons made by two-way and by three-way merge sort.
- If comparisons dominate the cost, which would you expect to be faster on an arbitrary array?
Describe an algorithm, running in time strictly better than , that takes a positive integer and a set of positive integers and decides whether two distinct elements of add up to exactly . Give its running time.
Each of computers has run the same computation, and we want to know whether strictly more than of them arrived at the same result. The only available query takes two computers and reports whether they produced the same result. Design an algorithm that decides this with queries, prove it correct, and prove the bound on the number of queries.
Find an asymptotically tight upper bound for the recurrence , , and explain your answer.
Check Yourself
Fresh questions on the whole lesson — none of them is worked out above. Work each one out on paper before opening Python; the box only tells you whether you got there.
Answers are checked in your browser, as often as you like. Nothing is sent anywhere and
nothing is kept but your own work. A formula may be written with the symbols themselves or
with ~ & | -> <-> ^, and \and, \or, \to expand as you type.
A forest has vertices and components. How many edges does it have?
What is the largest number of leaves a binary tree of height can have?
What is the least height of a binary tree with vertices?
At most how many passes does binary search make on a sorted array of length ?
How many comparisons does linear search make on an array of entries that does not contain ?
How many inversions does the array have?
How many comparisons does selection sort make on an array of records?
What is the worst-case number of key comparisons made by merge sort on records?
What is the expected number of comparisons made by randomized quicksort on four distinct keys?
What is the smallest integer with ?
In how many relative orders can distinct keys arrive?
Which of the three quadratic sorts of these notes is not stable as implemented?
At most how many comparisons does quicksort make on distinct keys?
At most how many comparisons does merging sorted arrays of lengths and take?
How many comparisons does bubble sort with the early stop make on an already sorted array of records?
How many shifts does insertion sort make on ?