0% found this document useful (0 votes)
11 views9 pages

Chapter 2

The document discusses algorithms in computer science, focusing on their definition, notation, and complexity, particularly in relation to numbers. It introduces pseudocode for algorithm representation, basic data structures, and O-notation for analyzing running times. The text also covers the semantics of various control structures and the significance of O-notation in classifying algorithm complexity.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
11 views9 pages

Chapter 2

The document discusses algorithms in computer science, focusing on their definition, notation, and complexity, particularly in relation to numbers. It introduces pseudocode for algorithm representation, basic data structures, and O-notation for analyzing running times. The text also covers the semantics of various control structures and the significance of O-notation in classifying algorithm complexity.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 9

2.

Algorithms for Numbers and Their


Complexity

The notion of an algorithm is basic in computer science. Usually, one says that
an algorithm is a finite piece of text that describes in an unambiguous way
which elementary computational steps are to be performed on any given in-
put, and in which way the result should be read off after the computation has
ended. In the theory of algorithms and in computational complexity theory,
one traditionally formalizes the notion of an algorithm as a program for a par-
ticular theoretical machine model, the Turing machine. In our context, where
we deal with numbers rather than with strings, this is not appropriate, hence
we use a different notation for algorithms, described in Sect. 2.1. As a tech-
nical prerequisite for discussing complexity issues, we introduce O-notation
in Sect. 2.2. In Sect. 2.3 the complexity of some elementary operations on
numbers is discussed.

2.1 Notation for Algorithms on Numbers

We describe algorithms in an informal framework (“pseudocode”), resembling


imperative programs for simplified computers with one CPU and a main
memory. For readers without programming experience we briefly describe
the main features of the notation.
We use only two elementary data structures:
• A variable may contain an integer. (Variables are denoted by typewriter
type names like a, b, k, l, and so on.)
• An array corresponds to a sequence of variables, indexed by a segment
{1, . . . , k} of the natural numbers. (Arrays are denoted by typewriter let-
ters, together with their index range in square brackets; an array element
is given by the name with the index in brackets. Thus, a[1..100] denotes
an array with 100 components; a[37] is the 37th component of this array.)
Each array component may contain an integer.
If not obvious from the context, we list the variables and arrays used in an
algorithm at the beginning. In many algorithms numbers are used that do not
change during the execution; such numbers, so-called constants, are denoted
by the usual mathematical notation.

M. Dietzfelbinger: Primality Testing in Polynomial Time, LNCS 3000, pp. 13-21, 2004.
 Springer-Verlag Berlin Heidelberg 2004
14 2. Algorithms for Numbers and Their Complexity

There are two basic ways in which constants, variables, and array com-
ponents can be used:
• Usage: write the name of the variable to extract and use its content by
itself or in an expression. Similarly, constants are used by their name.
• Assigning a new value: if v is some integral value, e.g., obtained by evalu-
ating some expression, then x ← v is an instruction that causes this value
to be put into x.
By combining variables and constants from Z with operations like addition
and multiplication, parenthesized if necessary, we form expressions, which
are meant to cause the corresponding computation to be carried out. For
example, the instruction

x[i] ← (a + b) div c

causes the current contents of a and b to be added and the result to be divided
by the constant c. The resulting number is stored in component x[i], where
i is the number currently stored in variable i.
By comparing the values of numerical expressions with a comparison op-
erator from {≤, ≥, <, >, =, =}, we obtain boolean values from {true, false},
which may further be combined using the usual boolean operators in {∧, ∨, ¬},
to yield boolean expressions.
Further elementary instructions are the return statement that immedi-
ately finishes the execution of the algorithm, and the break statement that
causes a loop to be finished.
Next, we describe ways in which elementary instructions (assignments, re-
turn, break) may be combined to form more complicated program segments
or statements. Formally, this is done using an inductive definition. Elemen-
tary instructions are statements. If stm1 , . . . , stmr , r ≥ 1, are statements,
then the sequence {stm1 ; · · · stmr ; } is also a statement; the semantics is
that the statements stm1 , . . . , stmr are to be carried out one after the other.
In our notation for algorithms, we use an indentation scheme to avoid curly
braces: a consecutive sequence of statements that are indented to the same
depth is to be thought of as being enclosed in curly braces.
Further, we use if -then statements and if -then-else statements with the
obvious semantics. For bool expr an expression that evaluates to a boolean
value and stm a statement, the statement
if bool expr then stm
is executed as follows: first the boolean expression is evaluated to some value
in {true, false}; if and only if the result is true, the (simple or composite)
statement stm is carried out. Similarly, a statement
if bool expr then stm1 else stm2
2.2 O-notation 15

is executed as follows: if the value of the boolean expression is true, then stm1
is carried out, otherwise stm2 is carried out.
In order to be able to write repetitive instructions in a concise way, we
use for loops, while loops, and repeat loops. A for statement
for i from expr1 to expr2 do stm
has the following semantics. The expressions expr1 and expr2 are evalu-
ated, with integral results n1 and n2 . Then the “loop body” ins is executed
max{0, n2 − n1 + 1} times, once with i containing n1 , once with i containing
n1 + 1, . . ., once with i containing n2 . Finally i is assigned the value n2 + 1
(or n1 , if n1 > n2 ). It is understood that the loop body does not contain an
assignment for i. The loop body may contain the special instruction break,
which, when executed, immediately terminates the execution of the loop,
without changing the contents of i. Again, we use indentation to indicate
how far the body of the loop extends. If instead of the keyword to we use
downto, then the content of i is decreased in each execution of the loop
body.
The number of repetitions of a while loop is not calculated beforehand.
Such a statement, written
while bool expr do stm
has the following semantics. The boolean expression bool expr is evaluated.
If the outcome is true, the body stm is carried out once, and we start again
carrying out the whole while statement. Otherwise the execution of the state-
ment is finished.
A repeat statement is similar. It has the syntax
repeat stm until bool expr
and is executed as follows. The statement stm is carried out once. Then the
boolean expression bool expr is evaluated. If the result is true, the execution
of the statement is finished. Otherwise we start again carrying out the whole
repeat statement. Just as with a for loop, execution of a while loop or a
repeat loop may also be finished by executing a break statement.
A special elementary instruction is the operation random. The statement
random(r), where r ≥ 2 is a number, returns as value a randomly chosen
element of {0, . . . , r − 1}. If this instruction is used in an algorithm, the result
becomes a random quantity and the running time might become a random
variable. If the random instruction is used in an algorithm, we call it a
“randomized algorithm”.

2.2 O-notation
In algorithm analysis, it is convenient to have a notation for running times
which is not clogged up by too much details, because often it is not possible
16 2. Algorithms for Numbers and Their Complexity

and often it would even be undesirable to have an exact formula for the
number of operations. We want to be able to express that the running time
grows at most “at the rate of log n” with the input n, or that the number of
bit operations needed for a certain task is roughly proportional to (log n)3 .
For this, “O-notation” (read “big-Oh-notation”) is commonly used.
We give the definitions and basic rules for manipulating bounds given in
O-notation. The proofs of the formulas and claims are simple exercises in
calculus. For more details on O-notation the reader may consult any text on
the analysis of algorithms (e.g., [15]).

Definition 2.2.1. For a function f : N → R+ we let


O(f ) = {g | g : N → R+ , ∃C > 0∃n0 ∀n ≥ n0 : g(n) ≤ C · f (n)} ,
Ω(f ) = {g | g : N → R+ , ∃c > 0∃n0 ∀n ≥ n0 : g(n) ≥ c · f (n)} ,
Θ(f ) = O(f ) ∩ Ω(f ).

Alternatively, we may describe O(f ) [or Ω(f ) or Θ(f ), resp.] as the set of
functions g with lim supn→∞ fg(n) g(n)
(n) < ∞ [or lim inf n→∞ f (n) > 0, or both,
resp.].

Example 2.2.2. Let fi (n) = (log n)i , for i = 1, 2, . . ..


(a) If g is a function with g(n) ≤ 50(log n)2 − 100(log log n)2 for all n ≥ 100,
then g ∈ O(f2 ), and g ∈ O(fi ) for all i > 2.
(b) If g is a function with g(n) ≥ (log n)3 · (log log n)2 for all n ≥ 50, then
g ∈ Ω(f3 ) and g ∈ Ω(f2 ).
(c) If g is a function with 101
log n ≤ g(n) ≤ 15 log n + 5(log log n)2 for all
n ≥ 50, then g ∈ Θ(f1 ).

Thus, O-notation helps us in classifying functions g by simple representative


growth functions f even if the exact values of the functions g are not known.
Note that since we demand a certain behavior only for n ≥ n0 , it does not
matter if the functions that we consider are not defined or not specified for
some initial values n < n0 .
Usually, one writes:
g(n) = O(f (n)) if g ∈ O(f ), 1
g(n) = Ω(f (n)) if g ∈ Ω(f ), and
g(n) = Θ(f (n)) if g ∈ Θ(f ) .
Sometimes, we use O(f (n)) also as an abbreviation for an arbitrary function
in O(f ); e.g., we might say that algorithm A has a running time of O(f (n))
if the running time tA (n) on input n satisfies tA (n) = O(f (n)). Extending
this, we write O(g(n)) = O(f (n)) if every function in O(g) is also in O(f ).
1
Read: “g(n) is big-Oh of f (n)”.
2.2 O-notation 17

The reader should be aware that the equality sign is not used in a proper
way here; in particular, the relation O(g(n)) = O(f (n)) is not symmetric.
In a slight extension of this convention, we write g(n) = O(1) if there is
a constant C such that g(n) ≤ C for all n, and g(n) = Ω(1) if g(n) ≥ c for
all n, for some c > 0.
For example, if the running time tA (n) of some algorithm A on input n is
bounded above by 50(log n)2 − 10(log log n)2 , we write tA (n) = O((log n)2 ).
Similarly, if c ≤ tB (n)/(log n)3 < C for all n ≥ n0 , we write tB (n) =
Θ((log n)3 ).
There are some simple rules for combining estimates in O-notation, which
we will use without further comment:
Lemma 2.2.3. (a) if g1 (n) = O(f1 (n)) and g2 (n) = O(f2 (n)), then
g1 (n) + g2 (n) = O(max{f1 (n), f2 (n)});
(b) if g1 (n) = O(f1 (n)) and g2 (n) = O(f2 (n)), then
g1 (n) · g2 (n) = O(f1 (n) · f2 (n)). 
2
For example, if g1 (n) = O((log n) ) and g2 (n) = O((log n)(log log n)), then
g1 (n) + g2 (n) = O((log n)2 ) and g1 (n) · g2 (n) = O((log n)3 (log log n)).
We extend the O-notation to functions of two variables and write

g(n, m) = O(f (n, m))

if there is a constant C > 0 and there are n0 and m0 such that for all
n ≥ n0 and m ≥ m0 we have g(n, m) ≤ C · f (n, m). Of course, this may be
generalized to more than two variables. Using this notation, we note another
rule that is essential in analyzing the running time of loops. It allows us to
use summation of O(. . .)-terms.

Lemma 2.2.4. If g(n, i) = O(f (n, i)), and G(n, m) = 1≤i≤m g(n, i) and
F (n, m) = 1≤i≤m f (n, i), then G(n, m) = O(F (n, m)). 
A particular class of time bounds are the polynomials (in log n): Note that
if g(n) ≤ ad (log n)d + · · · + a1 log n + a0 for arbitrary integers ad , . . . , a1 , a0 ,
then g(n) = O((log n)d ).
We note that if limn→∞ ff12 (n) (n) = 0 then O(f1 )  O(f2 ). For example,
O(log n)  O((log n)(log log n)). The following sequence of functions charac-
terize more and more extensive O-classes:
log n, (log n)(log log n), (log n)3/2 , (log n)2 , (log n)2 (log log n)3 , (log n)5/2 ,
(log n)3 , (log n)4 , . . . , (log n)log log log n = 2(log log n)(log log log n) ,
2 √ √
2(log log n) , 2 log n , 2(log n)/2 = n, n3/4 , n/ log n, n, n log log n, n log n.
In connection with the bit complexity of operations on numbers, like pri-
mality tests, one often is satisfied with a coarser classification of complexity
bounds that ignores logarithmic factors.
18 2. Algorithms for Numbers and Their Complexity

Definition 2.2.5. For a function f : N → R+ with limn→∞ f (n) = ∞ we let

O∼ (f ) = {g | g : N → R+ , ∃C > 0∃n0 ∃k∀n ≥ n0 : g(n) ≤ C·f (n) log(f (n))k }.

Again, we write g(n) = O∼ (f (n)) if g ∈ O∼ (f ). A typical example is the


bit complexity t(n) = O((log n)(log log n)(log log log n)) of the Schönhage-
Strassen method for multiplying integers n and m ≤ n [41]. This complexity
is classified as t(n) = O∼ (log n). Likewise, when discussing a time bound
(log n)7.5 (log log n)4 (log log log n) one prefers to concentrate on the most sig-
nificant factor and write O∼ ((log n)7.5 ) instead. The rules from Lemma 2.2.3
also apply to the “O∼ -notation”.

2.3 Complexity of Basic Operations on Numbers

In this section, we review the complexities of some elementary operations


with integers.
For a natural number n ≥ 1, let n = log2 (n+ 1) be the number of bits
in the binary representation of n; let 0 = 1. We recall some simple bounds
on the number of bit operations it takes to perform arithmetic operations
on natural numbers given in binary representation. The formulas would be
the same if decimal notation were used, and multiplication and addition or
subtraction of numbers in {0, 1, . . . , 9} were taken as basic operations. In all
cases, the number of operations is polynomial in the bit length of the input
numbers.

Fact 2.3.1. Let n, m be natural numbers.


(a) Adding or subtracting n and m takes O(n + m) = O(log n + log m)
bit operations.
(b) Multiplying m and n takes O(n · m) = O(log(n) · log(m)) bit opera-
tions.
(c) Computing the quotient n div m and the remainder n mod m takes
O((n − m + 1) · m) bit operations.2

Proof. In all cases, the methods taught in school, adapted to binary notation,
yield the claimed bounds. 
Addition and subtraction have cost linear in the binary length of the
input numbers, which is optimal. The simple methods for multiplication and
division have cost no more than quadratic in the input length. For our main
theme of polynomial time algorithms this is good enough. However, we note
that much faster methods are known.
2
The operations div and mod are defined formally in Definition 3.1.9.
2.3 Complexity of Basic Operations on Numbers 19

Fact 2.3.2. Assume n and m are natural numbers of at most k bits each.
(a) ([38]) We may multiply m and n with O(k(log k)(log log k)) = O∼ (k) bit
operations.
(b) (For example, see [41].) We may compute n div m and n mod m using
O(k(log k)(log log k)) = O∼ (k) bit operations. 

An interesting and important example for an elementary operation on


numbers is modular exponentiation. Assume we wish to calculate the number
210987654321 mod 101. Clearly, the naive way — carrying out 10987654320
multiplications and divisions modulo 101 — is inefficient if feasible at all.
Indeed, if we calculated an mod m by doing n−1 multiplications and divisions
by m, the number of steps needed would grow exponentially in the bit length
of the input, which is a + n + m.
There is a simple, but very effective trick to speed up this operation, called
“repeated squaring”. The basic idea is that we may calculate the powers
i
si = a2 mod m, i ≥ 0, by the recursive formula

s0 = a mod n; si = s2i−1 mod m, for i ≥ 1.


i
Thus, k multiplications and divisions by m are sufficient to calculate a2 mod
m, for 0 ≤
i ≤ k. Further, if bk bk−1 · · · b1 b0 is the binary representation of n,
i.e., n = 0≤i≤k,bi =1 2i , then
 

n
a mod m = si mod m.
0≤i≤k
bi =1

This means that at most k further multiplications and divisions are sufficient
to calculate an mod m. Let us see how this idea works by carrying out the
calculation for 24321 mod 101 (Table 2.1). We precalculate that the binary
representation of  4321 is bk · ·j· b0 = 1000011100001. By ci we denote the
partial product 0≤j≤i,bj =1 a2 mod m. It only remains to efficiently obtain
the bits of the binary representation of n. Here the trivial observation helps
that the last bit b0 is 0 or 1 according as n is even or odd, and that in the
case n ≥ 2 the preceding bits bk · · · b1 are just the binary representation of
n/2 = a div 2. Thus, a simple iterative procedure yields these bits in the
order b0 , b1 , . . . , bk needed by the method sketched above. If we interleave
both calculations, we arrive at the following procedure.

Algorithm 2.3.3 (Fast Modular Exponentiation)


Input: Integers a, n, and m ≥ 1.
Method:
0 u, s, c: integer;
1 u ← n;
20 2. Algorithms for Numbers and Their Complexity

2 s ← a mod m;
3 c ← 1;
4 while u ≥ 1 repeat
5 if u is odd then c ← (c · s) mod m;
6 s ← s · s mod m;
7 u ← u div 2;
8 return c;

i si bi ci
0 21 = 2 1 2
1 22 = 4 0 2
2 42 = 16 0 2
3 162 mod 101 = 54 0 2
4 542 mod 101 = 88 0 2
5 882 mod 101 = 68 1 2 · 68 mod 101 = 35
6 682 mod 101 = 79 1 35 · 79 mod 101 = 38
7 792 mod 101 = 80 1 38 · 80 mod 101 = 10
8 802 mod 101 = 37 0 10
9 372 mod 101 = 56 0 10
10 562 mod 101 = 5 0 10
11 52 mod 101 = 25 0 10
12 252 mod 101 = 19 1 10 · 19 mod 101 = 89

Table 2.1. Exponentiation by repeated squaring

0
In line 2 s is initialized with a mod m = a2 mod m, and u with n. In each
iteration of the loop, u is halved in line 7; thus when line 5 is executed for the
(i + 1)st time, u contains the number with binary representation bk bk−1 . . . bi .
Further, in line 6 of each iteration, the contents of s are squared (modulo m);
i
thus when line 5 is executed for the (i + 1)st time, s contains si = a2 mod m.
The last bit bi of u is used in line 5 to decide whether p should be multiplied
by si or not. Thisentails, by induction, that after carrying out line 5 the ith
j
time p contains 0≤j≤i a2 mod m. The loop stops when u has become 0,
which obviously happens
 after k + 1 iterations have been carried out. At this
j
point p contains 0≤j≤k a2 mod m, the desired result.

Lemma 2.3.4. Calculating an mod m takes O(log n) multiplications and di-


visions of numbers from {0, . . . , m2 −1}, and O((log n)(log m)2 ) (naive) resp.
O∼ ((log n)(log m)) (advanced methods) bit operations. 

It is convenient to here provide an algorithm for testing natural numbers


for the property of being a perfect power. We say that n ≥ 1 is a perfect
power if n = ab for some a, b ≥ 2. Obviously, only exponents b with b ≤ log n
2.3 Complexity of Basic Operations on Numbers 21

can satisfy this. The idea is to carry out the following calculation for each
such b: We may check for any given a < n whether ab < n or ab = n or
ab > n. (Using the Fast Exponentiation Algorithm 2.3.3 without modulus,
but cutting off as soon as an intermediate result larger than n appears will
keep the numbers to be handled smaller than n2 .) This makes it possible to
conduct a binary search in {1, . . . , n} for a number a that satisfies ab = n.
In detail, the algorithm looks as follows:

Algorithm 2.3.5 (Perfect Power Test)


Input: Integer n ≥ 2.
Method:
0 a, b, c, m: integer;
1 b ← 2;
2 while 2b ≤ n repeat
3 a ← 1; c ← n;
4 while c − a ≥ 2 repeat
5 m ← (a + c) div 2;
6 p ← min{mb , n + 1}; (∗ fast exponentiation, truncated ∗)
7 if p = n then return (“perfect power ”, m, b);
8 if p < n then a ← m else c ← m ;
9 b ← b + 1;
10 return “no perfect power ”;

In lines 3–8 a binary search is carried out, maintaining the invariant that
for the contents a, c of a, c we always have ab < n < cb . In a round, the
median m = (a + c) div 2 is calculated and (using the fast exponentiation
algorithm) the power mb is calculated; however, as soon as numbers larger
than n appear in the calculation, we break off and report the answer n + 1. In
this way, numbers larger than n never appear as factors in a multiplication.
If and when m = (a + c) div 2 satisfies mb = n, the algorithm stops and
reports success (line 7). Otherwise either a or c is updated to the new value
m, so that the invariant is maintained. In each round through the loop 3–8
the number of elements in the interval [a + 1, c − 1] halves; thus, the loop runs
for at most log n times before c − b becomes 0 or 1. The outer loop (lines 2–9)
checks all possible exponents, of which there are log n − 1 many. Summing
up, we obtain:

Lemma 2.3.6. Testing whether n is a perfect power is not more expen-


sive than O((log n)2 log log n) multiplications of numbers from {1, . . . , n}.
This can be achieved with O((log n)4 log log n) bit operations (naive) or even
O∼ ((log n)3 ) bit operations (fast multiplication). 

We remark that using a different approach (“Newton iteration”) algorithms


for perfect power testing may be devised that need only O∼ ((log n)2 ) bit
operations. (See [41, Sect. 9.6].)

You might also like