Computer programming is a creative task.
The Art of Computer Programming
There is no recipe to developing algorithms.
Deriving an algorithm from specifications is not automatable.
That’s why we use algorithm design paradigms as inspiration for a possible way to approaching a given problem.
These paradigms are often called design patterns. Do not confuse them with Software design patterns, though!
There are a few main patterns:
A divide-and-conquer algorithm recursively breaks down a problem into two or more sub-problems of the same or related type, until these become simple enough to be solved directly.
The solutions to the sub-problems are then combined to give a solution to the original problem.
Principle of divide and conquer:
Example of an algorithm following design-and-conquer-pattern: MergeSort
function merge_sort(list m) is
// Base case. A list of zero or
// one elements is sorted, by definition.
if length of m ≤ 1 then
return m
// Recursive case. First, divide
// the list into equal-sized sublists
// consisting of the first half
// and second half of the list.
// This assumes lists start at index 0.
var left := empty list
var right := empty list
for each x with index i in m do
if i < (length of m)/2 then
add x to left
else
add x to right
// Recursively sort both sublists.
left := merge_sort(left)
right := merge_sort(right)
// Then merge the now-sorted sublists.
return merge(left, right)
function merge(left, right) is
var result := empty list
while left is not empty and
right is not empty do
if first(left) ≤ first(right) then
append first(left) to result
left := rest(left)
else
append first(right) to result
right := rest(right)
// Either left or right may have
// elements left; consume them.
// (Only one of the following loops
// will actually be entered.)
while left is not empty do
append first(left) to result
left := rest(left)
while right is not empty do
append first(right) to result
right := rest(right)
return result
Developing an algorithm following the
design-and-conquer-pattern
can be difficult.
Let’s visit the simplest solution in between:
a.k.a. brute force
c ← first(P)
while c ≠ Λ do
if c is valid solution for P then
return c
c ← next(P, c)
end while
Principles of brute force:
Using brute force is very easy to implement.
It is also very slow.
Runtime of a brute force approach depends proportionally on number of candidate solutions.
Let’s do a brute force attack.
Consider an encryption key of 256 bits length.
This gives us \(2^{256}\) possible bit patterns to check.
A supercomputer from 2019 had a speed of 100 petaFLOPS
enabling
it to check 100 trillion ( \(10^{14}\)
) bit patterns per seconds.
The time needed for that computer to exhaust the \(2^{256}\) possible bit patterns:
\(3.67\times 10^{55}\) years.
You should know if you could potentially use
brute
force exhaustive search
or if
runtime is just unacceptable…
Take what you can get!
A greedy algorithm is any algorithm that follows the problem-solving heuristic of making the locally optimal choice at each stage.
Principles of greedy algorithms:
In many problems, a greedy strategy does not produce an optimal solution.
A well-known problem solved with a greedy method is the change-making problem.
A change-making example: paid a 1.11€ amount with a 2€ coin.
What is the minimal number of coins to return 89ct?
\[ 89ct = 50ct + 20ct + 10ct + 5ct + 2ct + 2ct \]
Input: amount b
make_change(b)
var count := 0;
while count is smaller than b
// make greedy choice
choose highest valued coin s with count + s <= b
count += s;
What if…
The Euro currency is designed such that you can find an optimal solution greedily.
Systematic search technique to entirely
process a given solution
space
Räcke (2019)
Räcke (2019)
procedure backtrack(P, c) is
if reject(P, c) then return
if accept(P, c) then return c
s ← first(P, c)
while s ≠ NULL do
backtrack(P, s)
s ← next(P, s)
A candidate \(c\) could be a path for the mouse.
Observations:
Example application from textbooks: Eight queens puzzle
An application with a more practical relevance:
The Travelling
salesman
although backtracking
isn’t the best choice here
Räcke (2019)
The problem:
We have \(n\) cities.
And a salesperson who wants to make the shortest round-trip visiting each city exactly once and returns to start.
Räcke (2019)
Input: n cities, roundtrip trip
TSP(trip)
if (trip visits each city)
extend trip with route to start;
return trip and cost;
else
foreach (unvisited city c)
trip’ = trip extended with c
TSP(trip’);
With \(n\) cities and fixed start
and end,
we can possibly do \((n-1)!\) roundtrips.
Here \(n=5\), so
\(4! = 4 \cdot 3 \cdot 2 \cdot 1 = 24\)
Runtime complexity of TSP is \(O(n-1)!\)
mirco.schoenfeld@uni-bayreuth.de
Acknowledgement:
This script is based on a script by Räcke (2019). Additional inspiration is drawn from Tantau (2016).