Computer programming is a creative task.
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)!\)
Acknowledgement:
This script is based on a script by Räcke (2019). Additional inspiration is drawn from Tantau (2016).