Notes
Notes
Notes
2
Nii Quaynor, Ike Mensah, Wilberforce
Course description
Concepts and techniques fundamental to the science of programming are
developed. Methods of enquiry of algorithms of design, theory and
implementation in programs and architectures. Topics include analysis of
best, average and worst case of time and space complexity. Proof of
correctness of algorithms and use of invariants in making proofs of
iterative and recursive algorithms. Introduction to numerical, recursion,
sequential and linked allocation, sorting, searching, encryption, grammar
and graphs algorithms. The African calculating instrument is also explored.
The Notes
Algorithms
Informally, an Algorithm is a well defined computational procedure that
takes some values as inputs and produces some values as output. Thus a
sequence of computational steps that transform input into output
1
Rigorous notion of experiments
Problems
Must be general enough to appear over and over in slightly different forms
(“instances”)
Algorithms
Process for solving a problem
Examples
Size of matrix multiplication might be the largest dimension of the two
matrices
2
The time needed by an algorithm is expressed as a f(size of problem)
called time complexity of an algorithm
To get a feel for how time complexity affects amount of work done
consider a machine that one unit of work takes 1 msec ie 1/1000 secs and
ask what is the maximum problem size can solve in 1 second for each of
these time complexities: n, n*log(n), n**2, n**3 and 2**n
A1 n 1000
A2 n*log(n) 140
A3 n**2 31
A4 n**3 10
A5 2**n 9
A1
n*(1/1000) = 1
Therefore n = 1000
3
A2
n*log (n)*(1/1000) = 1
n*log(n) = 1000
A3
(n**2)*(1/1000) = 1
n**2 = 1000
n = sqrt (1000)
n = 31
A4
(n**3)*(1/1000) = 1
A5
(2**n)*(1/1000) = 1
2**n = 1000
Log(2**n) = Log(1000)
n = Log (1000) = 9
Design
Objects from problem statement include list a[1..n] and element a[i]. The
element looking for x, have or not found it.
That is:
found, x, i, n, a[1..n]
4
x=3; n=40; i=1
found=false
If (x==a[i]) {
found=true
i=i+1
printf(“%d”, found)
1 Initialization C1 1
3 If (x==a[i]) { C3 n
4 found=true} C4 0
5 i++} C5 1
5
Theory - complexity
Time complexity
T = c1 + c2(n+1) + c3n + c5n + c6
T = (c2 + c3 + c5)n + (c1 + c2 + c6)
Similar to y = ax + b
Minimum
The minimum number of comparisons is when i=1
The number of comparisons in general is equal to i+1 for i=1 it’s equal to 2
Maximum
The maximum number of comparisons is when i=n and therefore equal to
(n+1)
Average
To determine the average for a size n taking into account that the
algorithm terminated when an item x is found requires more calculations
6
The average A = C/n
Equal to (n+1)/2 + n
Theory - correctness
Proof:
The algorithm starts with i less than n. Initialized with i=1 and n=40
7
a1’ <= a2’ <= a3’ … <= an’
Theory - complexity
1 for i = 1 to (n-1) { C1 n
Assume a function swap(i,j) exist to swap elements a[i] and a[j] such as
1 j=2..n n-1
2 j=3..n n-2
n-1 j=n..n 1
8
We know,
Sum:1..(n-1) of i is equal to ((n-1)*n)/2
And
Sum:1..(n-1) of (i-1) is equal to Sum:1..(n-1) of i minus Sum:1..(n-1) of 1
Sum:1..(n-1) of 1 is equal to (n-1)
Equal to (c2/2 + c3/2 + c4/2)* n**2 + (c1 - c2/2 -3*c3 - 3*c4)*n + (c3 + c4)
The numbers to sort are known as keys. The input comes in form of an
array
9
Theory-complexity
4 i = (j-1) c4 n-1
7 i = i - 1} c7 sum:j=2..n of (tj-1)
Best case
List is already sorted
For each j=2, 3, .., n find that a[i]<= key and line 5 fail when i has initial
value of (j-1)
10
Worst case
The array is in reverse order
Have to compare each element a[j] to all elements in subarray a[1..(j-1)]
Correctness
An algorithm is said to be correct if, for every input instance, it halts with
the correct output ie a correct algorithm solves the given computational
problem and an incorrect algorithm may not halt at all
An algorithm is thus correct, Iff the algorithm ends with all post conditions
true, whenever started with all preconditions true
{P} ->Algorithm->{Q}
Proof show algorithm works for all possible inputs and works directly with
the abstract algorithm (idea)
11
They can’t tell anything about implementation
A Theorem
“The sum of two odd numbers is always even”
Testing
3+5=8
5+5=10,,,etc
This helps but not convincing
Intuition
(Think about why true)
Rigorous Argument
Odd number is (2*i + 1)
Therefore,
2*i + 1 + 2*j + 1
Equals
Structure of Proofs
Precondition
A statement of assumptions
12
Theorem
Statement to be proven
Show that the post conditions hold at end of algorithm
Proof proper ( Argument as to why theorem is true)
Proof methods
Modus Ponens
Start with known facts or precondition and make series of deductions until
obtain desired goal
P => Q
P
———
Q
P => Q is equivalent to
If P then Q
(Implications)
Proving Termination
Show that every iteration makes finite progress to satisfying exit conditions
(minimum, non zero progress)
Example,
a[i] = 33;
14
At the start of each iteration of the for loop of line 1-8, the subarray a[1..
(j-1)] consists of the elements originally in a[1..(j-1)] but in sorted order
Initialization: the loop invariant holds before the first loop iteration, when
j=2. The subarray a[1..(j-1)] therefore consists of single element a[1], which
is the original a[1]
The body of loop works by moving a[j-1], a[j-2], a[j-3],, etc by one position
to the right until the proper position for a[j]. Lines 4-7. At which point it
inserts the value of a[j]. Line 8
The subarray a[1..j] consist of the elements originally in a[1..j] and in sorted
Thus incrementing j for the next iteration of the for loop then preserves the
loop invariant
15
We have the subarray a[1..n] consisting of elements originally in a[1..n] and
in sorted order
As the subarray a[1..n] is the entire array, we conclude the array is sorted.
Hence algorithm is correct
Binary Search
——————————————
|<—-x lies in—->|
——————————————
^ ^
| |
low high
Repeat the following until either (low > high) or (you find x
If x < item at 1/2 way between positions low and high
then set high equal to position before the half waypoint
Otherwise,
If x > item at 1/2 between positions low and high
then set low equal to position after half way point
16
Boolean binarySearch (int x) {
low = 0;
high = n-1;
If ( x< a[(low+high)/2]) {
} else {
}
If (low > high) {
return false
} else {
return true
n/2/2/2…2
17
x times
2**x = n
if (x < a[(low+high)/2]) {
high = (low+high)/2 - 1
} else {
low = (low+high)/2 + 1
}
Show that every iteration makes progress to low becoming > high
Change high
(L + H)/2 - 1
Change low
(L + H)/2 + 1
18
Every iteration either
reduces high by 1
Or
Increases low by 1
p => q
p
———
q
Theorem: p= !( !(p) )
Proof:
Consider 2 cases: p is true and p is not true
Case1: assume p is true. Then !p is false and !(!p) must be !(false) which is
true
So when p is true !(!p) is also true
Case2: assume p is false. Then !p is true and !(!p) must be !(true) which is
false
So when p is false, !(!p) is also false
Therefore p = !( !(p) )
P1 P2 P1 and P2
F F F
F T F
T F F
T T T
P1 P2 P1 or P2
F F F
F T T
T F T
T T T
p q p => q (implication)
F F T
20
F T T
T F F
T T T
Halting Problem
loop forever
} else {
return immediately
21
}
magic (magic)
|
| invokes
|
terminate (magic, magic)
Proof by cases
We use induction:
(Generalize individual observations)
22
1 BASE CASE: find small examples for which it is true
2 INDUCTION STEP: if statement true for small numbers then assume true
for larger numbers
Use assumption to show the theorem for a larger number
Base n=1
Sum:i=1..1 of i = 1 and 1(1+1)/2 = 1
Step
If holds for n=k-1
Then holds for n=k
If n=k-1,
sum:i=1..k-1 of i must equal by assumption (k-1)((k-1)+1)/2
Equals (k-1)k/2
Application to recursion
23
if ( x > 0 ) then { return ( x * factorial(x-1)) }
else { return 1}
Notice that the function definition includes a call to itself inside the then
clause
If we were to call the function print factorial(3), it would trace like this
Proof of termination
The exit condition is when x is not greater than 0, ie
Since start with factorial (x) with x>0, and every new recursive call to
factorial reduces parameter x by 1
Eventually the recursion will execute the else clause when x=0 and e it
terminating the execution
1, 1, 2, 3, 5, 8, 13,….
else {
if (m=1) then {return 1}
else {
Selection sort
25
Selection_sort() {
Sieve of Eratosthenes
You may generate small primes with a simple algorithm like this. Assume
have a Boolean array a, which contain true or false if it’s not a multiple of a
smaller number or false
Primes {
int n;
boolean [ ] a;//
// initialization
//marking
for i=2 to n-1 {
if (a[i] != false) {
if (i<(n-100) ) then {
if a[i] { print i}
}
Integration of functions
27
Area {
x(0) = A;
y(0) = f(A);
n = 20;//number of steps
h = ( B - A)/n;
for i = 1 to n {
y(i) = f( A + i*h);
T = T + ( y(i-1) + y(i) )*h/2;
y(i-1) = y(i);
}
Print T;
}
28
Single key encryption procedure
Suppose have a table of alphabets and a number code associated. In this
character table is made up of alphabets A to Z and a space mark
separator (27 characters)
Alphabet Code
Space 0
A 1
B 2
C 3
29
D 4
E 5
F 6
G 7
H 8
I 9
J 10
K 11
L 12
M 13
N 14
O 15
P 16
Q 17
R 18
S 19
T 20
U 21
V 22
W 23
X 24
Y 25
Z 26
encrypt {
// inputs are I[i], K[i]
for i = 0 to 100 {
t = I[i] + K[i];
if t >= 27 then t = t - 27;
E[i] = t;
}
}
31
decrypt {
// inputs are E[i], K[i]
for i = 0 to 100 {
t = 27 - K[i] + E[i]
if t >= 27 then t = t - 27;
I[i] = t;
}
The system of equations may also be written using sigma for summing
elements
Or simply as
For example,
(1) 3x + 6y + 9z = 39
(2) 2x + 5y - 2z = 3
32
(3) x + 3y - z = 2
3 6 9
2 5 -2
1 3 -1
39
If divide (1) by 3
1 2 3
2 5 -2
1 3 -1
13
1 2 3
0 1 -8
0 1 -4
33
And the b,
13
-23
-11
Dividing (2’’) by 1 and subtract (2’’) from (3’’) we get (1’’’), (2’’’), (3’’’) in A and
b respectively
1 2 3
0 1 -8
0 0 4
And b,
13
-23
12
1 2 3
0 1 -8
0 0 1
And. As follows
13
-23
z=3
y = -23+ 24 = 1
34
x = 13 -2 -9 = 2
//
// main.c
// Linear
//
// Created by nii n. quaynor on 9/25/14.
// Copyright (c) 2014 GDC. All rights reserved.
//
#include <stdio.h>
/*create coefficients*/
float a[3][3] = {
{3,6,9},
{2,5,-2},
35
{1,3,-1}
};
int i,j,k;
float d,q;
for (i=0;i<=n-1;i++)
{
/*pivot*/
d=a[i][i];
a[i][i]=a[i][i]/d;
for (k=i+1;k<=n-1;k++)
{
a[i][k]=a[i][k]/d;
}
b[i]=b[i]/d;
/*elimination*/
for (j=i+1;j<=n-1;j++)
{
/*left hand side*/
q=a[j][i];
for (k=i;k<=n-1;k++)
{
a[j][k]=a[j][k]-q*a[i][k];
}
printf("%s %2.1f\n","a[0,0]=",a[0][0]);
printf("%s %2.1f\n","a[0,1]=",a[0][1]);
printf("%s %2.1f\n\n","a[0,2]=",a[0][2]);
printf("%s %2.1f\n","a[1,0]=",a[1][0]);
printf("%s %2.1f\n","a[1,1]=",a[1][1]);
printf("%s %2.1f\n\n","a[1,2]=",a[1][2]);
36
printf("%s %2.1f\n","a[2,0]=",a[2][0]);
printf("%s %2.1f\n","a[2,1]=",a[2][1]);
printf("%s %2.1f\n\n","a[2,2]=",a[2][2]);
printf("%s %2.1f\n","b[0]=",b[0]);
printf("%s %2.1f\n","b[1]=",b[1]);
printf("%s %2.1f\n\n","b[2]=",b[2]);
/*back substitution*/
x[n-1]=b[n-1];
for (i=n-2;i>=0;i--)
{
printf("%s %d\n", "i=",i);
x[i]=b[i];
printf("%s %f\n\n","x[i]=",x[i]);
for (k=i+1;k<=n-1;k++)
{
printf("%s %d\n","k=",k);
x[i]=x[i]-a[i][k]*x[k];
printf("%s %f\n\n","x[i]=",x[i]);
}
}
printf("%s %2.1f\n","x[0]=",x[0]);
printf("%s %2.1f\n","x[1]=",x[1]);
printf("%s %2.1f\n","x[2]=",x[2]);
Conditionals make it impossible to say exactly how long the algorithm will
run
Worst case
Upper bound: the algorithm requires no more than 3 units of time
Best case
Lower bound: the algorithm requires at least 2 units of time
Error in average
Due to variations among measurements
More measurements provide more improvement and average more likely
to be close to “true” value
Standard deviation
Produce a table
Example: sorting times
Avg
Standard Error
Grammars
Machines and languages have a relationship. The simplest language is the
regular languages whose equivalent for automatons is finite state
machines
Although there are other languages such as context free, context sensitive
and Type 0 languages we will focus our attention on regular languages
implemented as finite state machines and algorithms of interest
39
A grammar G = (N, T, P, S) where N are a set of non terminals, T is a set of
terminal symbols, P is a set of production rules and a starting symbol S
S = {<sentence >}
40
The derivation from <sentence > is as follows
<sentence >
=1=> <noun phrase ><verb phrase >
=2=> <adjective ><noun phrase ><verb phrase >
=5=> the <noun phrase ><verb phrase >
=3=> the <adjective ><noun><verb phrase >
=5=> the little <noun><verb phrase >
=6=> the little fish <verb phrase >
=4=> the little fish <verb><adverb>
=7=> the little fish swam <adverb>
=8=> the little fish swam quickly
The above grammar is familiar yet is much more complicated than the
grammars we study here
A -> aB
A -> a
Thus =G=> relates 2 strings when the second is obtained from the first by
application of a single production
That is string consists solely of terminals and derived from starting symbol
S
41
Finite state machines
These are used in design of electronics computers and communications
equipment but also used to control complex coordination among software
agents
K = set of states
Sigma is a set of inputs
F = set of final states
Delta = a set of state transitions of form delta (state, input) = next state
T(M) = { x | delta(q0,x) is in F }
Define G = ( N, T, P, S) as
G = ( K, sigma, P, q0)
An example might be
Sigma = {0, 1}
K = { q0, q1, q2, q3}
42
F = { q0 }
Delta(q0,0) = q2
Delta(q0,1) = q1
Delta(q1,0) = q3
Delta(q1,1) = q0
Delta(q2,0) = q0
Delta(q2,1) = q3
Delta(q3,0) = q1
Delta(q3,1) = q2
43
The productions in the resulting grammar P:
P = {q0->0q2
q0->1q1
q1->0q3
q1->1q0
q1->1
q2->0q0
q2->0
q2->1q3
q3->0q1
q3->1q2}
Notice that all the rules are of form A->bC or A->b where A and C are non
terminals and b is a terminal symbol. It’s a grammar for a regular language
as expected
Sequential Stack
A stack has a top and two operations. A push() to place some item at the
top of the stack and a pop() to remove at item. Use array stack[0..n] to
represent stack
push(item) {
if top+1 <= n then {
44
top=top+1
stack[top] = item
} else {
pop (item) {
if top>=0 then {
item=stack[top]
top=top-1
} else {
}
45
Sequential queue (circular buffer)
A queue has two ends, a front F and a rear R. New items are added at the
rear R and items are removed from the front F
Insert(item) {
If (@1 != F) then {
R = R@1
Buffer[R] = item
} else {
Print “buffer full”
46
}
Remove (item) {
If F != R then {
F = F@1
item=Buffer[F]
} else {
Print “buffer empty”
}
Modulo arithmetic
int @(x,y) {
T= x+y
While T>= n {
T=T-n
}
return T
}
47
Linked allocation
Quite often in systems programming you do not have contiguous storage
to allocate as resources. Instead may have available several smaller pieces
which together far exceeds the space requested. In linked allocation,
storage is organized as having two parts: an information part and a
reference pointer to where to find the next part. Hence a pointer refers to
an object with info and link
We will implement with two arrays info[] and Link[] hence a pointer PTR
refers to both Info[PTR] and Link[PTR]
Linked stack
The stack is only accessible at the top. To work a pointer TOP, points to
the top of the stack. From this first element the links all point to item below
48
them. Thus from the TOP one may traverse the stack to the end usually
marked with a sentinel
We may initialize the two arrays Info[] and Link[] to form a stack as follows
Initialization
Info[0] = 0
Link[0] = -1
for i = 1 to N {
Info[i] = 0
Link[i] = i-1
}
TOP = N
0 0 -1
1 0 0
2 0 1
3 0 2
N-2
N-1 0 N-2
N 0 N-1
49
Dump () {
for i = 0 to N {
Print Info[i]
Print Link[i]
}
push(PTR) {
Link[PTR] = TOP
TOP = PTR
PTR = -1
50
Note the grounding of PTR to make sure PTR no longer refers to the
structure
pop(PTR) {
if TOP ! -ve {
51
Linked queue
In the event that need to build a queue with linked node you may reference
below
The states of a linked queue are analyzed. Empty queue is represented by
both F and R equal to ground -1. From empty state with addition of a node
result in F and R equal to PTR and PTR equal -1
And similarly, when the last item in queue is removed it returns F and R
equal to ground -1
52
The routines Remove() and Insert() are as depicted here
Remove(ptr) {
If F>= 0 then {
ptr = F
F = Link[F]
53
} else {
Print “empty queue”
}
Insert (ptr) {
Link[ptr] = -1
If R >= 0 then {
Link[R] = ptr
R = ptr
} else {
F=R=ptr
ptr = -1
54
}
One would observe that the first item in the structure and pointed to by
Front has its left pointer shown L to -1 (ground) invalid address. Recall
addresses are array indices
You can use this to build an even more complex data structures such as
an array of Front like pointers. That is identified as a Hash table of entries
that you may traverse forward and backwards. Colliding hashes are
grouped into an Array H[0..(n-1)]. Hash may be any function but normally
want a hash that spreads uniformly to increase performance of n finding
items
55
Linked circular lists
In thee event that have interest to conserve link space may implement a
linked circular list using exclusive OR to put more information in the link
but also ask for additional information to determine object
P Q (+)
0 0 0
0 1 1
1 0 1
1 1 0
In this case we put the exclusive OR of Left and Right items into a pointer
And likewise,
If know R and a pointer PTR then can derive L
This enables traversing the link in any direction. If it is circular can traverse
circularly
Routines
Link[PTR] (+) R
Is equal to (L (+) R) (+) R
is equal to L (+) (R (+) R)
is equal to L (+) (0000…000)
57
is equal to L
Graph Algorithms
We have two basic ways to represent graphs as adjacency lists or as
adjacency matrix
Nodes 1 2 3 4 5
1 0 1 0 0 1
2 1 0 1 1 1
3 0 1 0 1 0
4 0 1 1 0 1
5 1 1 0 1 0
58
The adjacency list for the graph would be implemented as a hash array of
linked stacks
59
60