K08 Δομές Δεδομένων και Τεχνικές Προγραμματισμού
Κώστας Χατζηκοκολάκης
// Ταξινομεί τον πίνακα array μεγέθους size
void selection_sort(int array[], int size) {
// Βρίσκουμε το μικρότερο στοιχείο του πίνακα, το τοποθετούμε στη θέση 0,
// και συνεχίζουμε με τον ίδιο τρόπο στον υπόλοιπο πίνακα.
for (int i = 0; i < size; i++) {
// βρίσκουμε το μικρότερο στοιχείο από αυτά σε θέσεις >= i
int min_position = i;
for (int j = i; j < size; j++)
if (array[j] < array[min_position])
min_position = j;
// swap των στοιχείων i και min_position
int temp = array[i];
array[i] = array[min_position];
a[min_position] = temp;
}
}
Computer | Time (secs) |
---|---|
Computer A | 51.915 |
Computer B | 11.508 |
Computer C | 2.382 |
Computer D | 0.431 |
Computer E | 0.087 |
selection_sort
running timeIn msecs, on two types of computers
Array Size | Home Computer | Desktop Computer |
---|---|---|
125 | 12.5 | 2.8 |
250 | 49.3 | 11.0 |
500 | 195.8 | 43.4 |
1000 | 780.3 | 172.9 |
2000 | 3114.9 | 690.5 |
If we plot these numbers, they lie on the following two curves:
The curves have the quadratic form $f(n) = an^2 + bn + c$
Different computer / programming language / compiler:
The exact numbers change, but the shape of the curve stays the same.
We say that an algorithm belongs to a complexity class
A class is denoted by $O(g(n))$
For selection_sort
the time complexity is $O(n^2)$
$f(n) = an^2 + bn + c$
with $a = 0.0001724, b = 0.0004$ and $c = 0.1$.
$n$ | $f(n)$ | $an^2$ | $n^2$ term as % of total |
---|---|---|---|
125 | 2.8 | 2.7 | 94.7 |
250 | 11.0 | 10.8 | 98.2 |
500 | 43.4 | 43.1 | 99.3 |
1000 | 172.9 | 172.4 | 99.7 |
2000 | 690.5 | 689.6 | 99.9 |
$O$-notation | Adjective Name |
---|---|
$O(1)$ | Constant |
$O(\log n)$ | Logarithmic |
$O(n)$ | Linear |
$O(n \log n)$ | Quasi-linear |
$O(n^2)$ | Quadratic |
$O(n^3)$ | Cubic |
$O(2^n)$ | Exponential |
$O(10^n)$ | Exponential |
$O(2^{2^n})$ | Doubly exponential |
Assume 1 step = 1 μsec.
$g(n)$ | $n=2$ | $n=16$ | $n=256$ | $n=1024$ |
---|---|---|---|---|
1 | 1 μsec | 1 μsec | 1 μsec | 1 μsec |
$\log n$ | 1 μsec | 4 μsec | 8 μsec | 10 μsec |
$n$ | 2 μsec | 16 μsec | 256 μsec | 1.02 ms |
$n \log n$ | 2 μsec | 64 μsec | 2.05 ms | 10.2 ms |
$n^2$ | 4 μsec | 25.6 μsec | 65.5 ms | 1.05 |
$n^3$ | 8 μsec | 4.1 ms | 16.8 ms | 17.9 min |
$2^n$ | 4 μsec | 65.5 ms | $10^{63}$ years | $10^{297}$ years |
Assume 1 step = 1 μsec.
$g(n)$ | T = 1 min | T = 1hr |
---|---|---|
$n$ | $6 \times 10^7$ | $3.6 \times 10^9$ |
$n\log n$ | $2.8 \times 10^6$ | $1.3 \times 10^8$ |
$n^2$ | $7.75 \times 10^3$ | $6.0 \times 10^4$ |
$n^3$ | $3.91 \times 10^2$ | $1.53 \times 10^3$ |
$2^n$ | $25$ | $31$ |
$10^n$ | $7$ | $9$ |
Sequential searching of an array | $O(n)$ |
Binary searching of a sorted array | $O(\log n)$ |
Hashing (under certain conditions) | $O(1)$ |
Searching using binary search trees | $O(\log n)$ |
Selection sort, Insertion sort | $O(n^2)$ |
Quick sort, Heap sort, Merge sort | $O(n \log n)$ |
Multiplying two square x matrices | $O(n^3)$ |
Traveling salesman, graph coloring | $O(2^n)$ |
$f(n)$ is the function giving the actual time of the algorithm.
We say that $f(n)$ is $O(g(n))$ iff
We will not focus on the formal definition in this course.
An algorithm runs in time $O(g(n))$ iff it finishes in at most $g(n)$ steps.
A “step” is anything that takes constant time
a = b + 3
if(a == 4)
Typical way to compute this
To determine the dominant term and the lesser terms:
$$O(1) < O(\log n) < O(n) < O(n^2) < O(n^3) < O(2^n) < O(10^n)$$
Example:
Depending on the data
Depending on the number of executions
Say we want to sort an array, which values are stored in the array?
Worst-case: take the worst possible values
Average-case: average wrt to all possible values
Eg. quicksort
How many times do we run the algorithm?
Real-time: just once
Armortized-time: multiple times
Example: Dynamic array! (we will see it soon)
We will analyze the following algorithms
// Αναζητά τον ακέραιο target στον πίνακα target. Επιστρέφει τη θέση
// του στοιχείου αν βρεθεί, διαφορετικά -1
int sequential_search(int target, int array[], int size) {
for (int i = 0; i < size; i++)
if (array[i] == target)
return i;
return -1;
}
target
depends on its position in array
target
is in array[0]
, then we need only one steptarget
is in array[i-1]
, then we need $i$ stepsWorst case
target
is in array[size-1]
Average case
Assume that we always search for a target
that exists in array
If target == array[i-1]
then we need $i$ steps
Average wrt all possible positions $i$ (all are equally likely) $$\textstyle \textrm{Average} = \frac{1+\ldots+n}{n} = \frac{ \frac{n(n+1)}{2} }{n} = \frac{n}{2} + \frac{1}{2}$$
Therefore the average is $O(n)$
target
s that don't exist in array
// Ταξινομεί τον πίνακα array μεγέθους size
void selection_sort(int array[], int size) {
// Βρίσκουμε το μικρότερο στοιχείο του πίνακα, το τοποθετούμε στη θέση 0,
// και συνεχίζουμε με τον ίδιο τρόπο στον υπόλοιπο πίνακα.
for (int i = 0; i < size; i++) {
// βρίσκουμε το μικρότερο στοιχείο από αυτά σε θέσεις >= i
int min_position = i;
for (int j = i; j < size; j++)
if (array[j] < array[min_position])
min_position = j;
// swap των στοιχείων i και min_position
int temp = array[i];
array[i] = array[min_position];
a[min_position] = temp;
}
}
for
size
, $i = $current value of i
)for
:
Auxiliary functions
// Βρίσκει τη θέση του ελάχιστου στοιχείου στον πίνακα array
int find_min_position(int array[], int size) {
int min_position = 0;
for (int i = 1; i < size; i++)
if (array[i] < array[min_position])
min_position = i;
return min_position
}
// Ανταλλάσει τα στοιχεία a,b του πίνακα array
void swap (int array[], int a, int b) {
int temp = array[a];
array[a] = array[b];
array[b] = temp;
}
Elegant recursive version of the algorithm
// Ταξινομεί τον πίνακα array μεγέθους size
void selection_sort(int array[], int size) {
// Με λιγότερα από 2 στοιχεία δεν έχουμε τίποτα να κάνουμε
if (size < 2)
return;
// Τοποθετούμε το ελάχιστο στοιχείο στην αρχή
swap(array, 0, find_min_position(array, size));
// Ταξινομούμε τον υπόλοιπο πίνακα
selection_sort(&array[1], size - 1);
}
selection_sort
take?
selection_sort
calls:
find_min_position
: $n$ stepsswap
: 1 step (ignored compared to $n$)selection_sort
: $g(n-1)$ stepsSo $g(n) = \begin{cases} n + g(n -1) & n > 1 \\ 1 &n \le 1 \end{cases}$
This is a recurrence relation, we can solve it by unrolling:
$$ \begin{aligned} g(n) &= n + g(n -1) \\ &= n + (n-1) + g(n-2) \\ &= n + (n-1) + (n-2) + g(n-3) \\ &\ldots \\ &= n + \ldots + 1 \\ &= \frac{n(n+1)}{2} \end{aligned} $$ So again we get complexity $O(n^2)$
What is the worst case complexity of each operation?
list_insert_next
list_remove_next
list_next
list_last
list_find