An algorithm is a procedure to accomplish a specific task.
An algorithm is the idea behind any reasonable computer program.
Algorithms should solve a general, well-specified problem.
For example, the algorithmic program of sorting is defined as follows:
Problem: Sorting
Input: A sequence of n keys
Output: The reordering of the input sequence such that a1 <= a2 <= a3< an
There are three desirable properties for a good algorithm:
Correct algorithms usually come with a proof of correctness, which explains why we know an algorithm must take every instance of the problem to the desired result.
The steps of an algorithm must should meet certain characteristics:
We need tools to distinguish correct algorithms from incorrect ones, the primary tool being a proof.
A proper mathematical proof comprises several parts:
The three most common forms of algorithmic notation are:
All three notations are useful because there is a natural tradeoff between greater ease of expression and precision.
The choice of which notation is best really just depends upon which method you are most comfortable with.
Problem specifications have two parts:
It is impossible to prove the correctness of an algorithm for a badly stated problem.
Ask the wrong problem, and you will receive a wrong answer.
There are two common traps in specifying the output requirements of a problem:
The best way to prove that an algorithm is incorrect is to produce an instance in which it yields an incorrect answer.
We know these instances as counter-examples.
Good counter-examples have two important properties:
Here are some techniques to hunt for counter-examples:
Failure to find a counter-example to a given algorithm does not mean that it is obviously true.
The algorithm must include a proof or demonstration of correctness. Often, mathematical induction is the method of choice.
Consider the correctness of insertion sort, for example:
Let's consider a problem:
Incremental Correctness
Problem: Prove the correctness of the following recursive algorithm for incrementing natural numbers: y -> y + 1
:
Increment(y)
if y = 0 then return (1) else
if (y mod 2) = 1 then
return (2 * Increment([y / 2]))
else
return (y + 1)
Solution:
y = 0
is obviously correct because the value 1 is returned and 0 + 1 = 1.For odd numbers, the answer depends upon what is returned by Increment([y / 2]). We can assume that increment worked correctly for y = n - 1, but not for a value which is about half of it.
The case of odd y (y = 2m + 1 for some integer m) can be dealt with as:
2 * Increment([2m + 1) / 2]) = 2 * Increment([m + 1 / 2])
= 2 * Increment(m)
= 2 (m + 1)
= 2m + 2 = y + 1
and the general case is resolved. QED
Modeling is the art of formulating your application in terms of precisely described, well-understood problems.
Proper modeling is the key to applying algorithmic design techniques to real-world problems.
To exploit the algorithms literature, you must learn to describe your problem abstractly, in terms of procedures on fundamental structures.
Odds are very good that others have stumbled upon your algorithmic problem before you, perhaps in substantially different contexts.
You can formulate optimizations in terms of computing properties of common structures such as:
{1, 4, 3, 2}
and {4, 3, 2, 1}
are two distinct permutations of the same set of four integers.{1, 3, 4}
and {2}
are two distinct subsets of the first four integers. Order does not matter in subsets the way it does in permutations. Thus, the subsets {1, 3, 4}
and {4, 3, 1}
would be identical.Familiarity with these structures is important, because they provide the structure for modeling applications.
Modeling your application in terms of well-defined structures and algorithms is the most important single step towards a solution.
Learning to think recursively is learning to look for big things that are made from smaller things of exactly the same type as the big thing.
The primary abstract structures can be thought about recursively:
{1,...,*n*}
things and you get a permutation of the remaining n - 1
things.{1,...,*n*}
contains a subset of {1,...,*n* - 1}
made visible by deleting element n if it is present.Induction isn't the only way to prove things.
Sometimes, the best method is contradiction.
The basic scheme of contradiction is as follows:
When you don't know the right answer, the best thing to do is to make a guess.
Principled guessing is called estimation.
Estimation problems are best solved through some kind of logical reasoning process, typical a mix of principled calculations and anologies.
A best practice in estimation is to try to solve the problem in different ways and see if the answers generally agree in magnitude.
Data structure - https://en.wikipedia.org/wiki/Data_structure
Discussion on induction and conduction - https://portal.research.lu.se/ws/files/5654540/4249227.pdf
The Algorithm Design Manual - https://www.algorist.com/