Algorithms and Data Structures
Algorithms and Data Structures
standard library
Complexity analysis
♦ Answers the question “How does the time needed for an algorithm
scale with the problem size N?”
♦ Worst case analysis: maximum time needed over all possible inputs
♦ Best case analysis: minimum time needed
♦ Average case analysis: average time needed
♦ Amortized analysis: average over a sequence of operations
The O notation
An algorithm is O(f (N)) if there are constants c and N0, such that for
N≥ N0 the time to perform the algorithm for an input size N is
bounded by t(N) < c f(N)
♦ Consequences
♦ O(f(N)) is identically the same as O(a f(N))
♦ O(a Nx + b Ny) is identically the same as O(Nmax(x,y))
♦ O(Nx) implies O(Ny) for all y ≥ x
An algorithm is Ω(f (N)) if there are constants c and N0, such that
for N≥ N0 the time to perform the algorithm for an input size N is
bounded by t(N) > c f(N)
1 1 ns 1 ns 1 ns 1 ns 1 ns 1ns
ln N 3 ns 7 ns 10 ns 13 ns 17 ns 20 ns
N 10 ns 100 ns 1 µs 10 µs 100 µs 1 ms
N3 1 µs 1 ms 1 s 17 min 11.5 d 31 a
Complexity: example 1
double x;
std::cin >> x;
std::cout << std::sqrt(x);
Complexity: example 2
Complexity: example 3
Complexity: example 4
♦ Part 2:
double y;
std::cin >> y;
for (int i=0; i<n; ++i)
if (x[i]==y) {
std::cout << i << “\n”;
break;
}
♦ The complexity is O(N), but letʼs look at the next elements added:
♦ mark one more element as used
♦ write additional element
♦ Since this is not very important for numerical simulations I will not
go into details. Please read your C++ book
♦ Arrays: ♦ Trees
♦ C array
♦ map
♦ vector
♦ set
♦ valarray
♦ multimap
♦ deque
♦ multiset
♦ list
♦ queue
♦ priority_queue
♦ stack
♦ Advantages
♦ Fast O(1) access to arbitrary elements: a[i] is *(a+i)
♦ Profits from cache effects
♦ Insertion or removal at the end is O(1)
♦ Searching in a sorted array is O(ln N)
♦ Disadvantage
♦ Insertion and removal at arbitrary positions is O(N)
♦ Inserting an element
♦ Need to copy O(N) elements
a b c d e f g h
a b c d x e f g h
♦ Removing an element
♦ Also need to copy O(N) elements
a b c d e f g h
a b c e f g h
a
b
c
d
e
♦ then just change the size:
a
b
c
d
e
f
a b c d e f g
♦ Allows in O(1)
♦ Pushing an element to the top of the stack
♦ Accessing the top-most element
in
out
♦ Removing the top-most element
♦ Allows in O(1)
♦ Pushing an element to the end of the queue
♦ Accessing the first and last element
in
♦ Removing the first element
out
♦ The element with highest priority (as given by the < relation) is the first
one out
♦ If there are elements with equal priority, the first one in the queue is
the first one out
head tail
♦ Advantages
♦ Fast O(1) insertion and removal anywhere
♦ Just reconnect the pointers
♦ Disadvantage
♦ Does not profit from cache effects
♦ Access to an arbitrary element is O(N)
♦ Searching in a list is O(N)
♦ An array needs
♦ O(N) operations for arbitrary insertions and removals
♦ O(1) operations for random access
♦ O(N) operations for searches
♦ O(ln N) operations for searches in a sorted array
♦ A list needs
♦ O(1) operations for arbitrary insertions and removals
♦ O(N) operations for random access and searches
d s
A binary tree
b g n x
a i w y
z leaf
Unbalanced trees
c
♦ Solutions
♦ Rebalance the tree
d
♦ Use self-balancing trees
e
♦ Look into a data structures book to learn more
f
Generic traversal
♦ Instead of
♦ Instead of
for (T* p = a;
for (Node<T>* p=l.first;
p !=a+size;
p!=0;
++p)
p=p->next)
cout << *p;
cout << p->elem;
Iterators
Container requirements
Sequence constructors
♦ Constructors
♦ container() … empty container
♦ container(n) … n elements with default value
♦ container(n,x) … n elements with value x
♦ container(c) … copy of container c
♦ container(first,last) … first and last are iterators
♦ container with elements from the range [first,last[
♦ Example:
♦ std::list<double> l;
// fill the list
…
// copy list to a vector
std::vector<double> v(l.begin(),l.end());
♦ Assignments
♦ container = c … copy of container c
♦ container.assign(n) …assign n elements the default value
♦ container.assign(n,x) … assign n elements the value x
♦ container.assign(first,last) … assign values from the range
[first,last[
♦ Watch out: assignment does not allocate, do a resize before!
♦ are based on deques, but can also use vectors and lists
♦ stack is first in-last out
♦ queue is first in-first out
♦ priority_queue prioritizes with < operator
♦ stack functions
♦ void push(const T& x) … insert at top
♦ void pop() … removes top
♦ T& top()
♦ const T& top() const
♦ queue functions
♦ void push(const T& x) … inserts at end
♦ void pop() … removes front
♦ T& front(), T& back(),
const T& front(), const T& back()
♦ multimap
♦ can contain more than one entry (e.g. phone number) per key
♦ set
♦ unordered container, each entry occurs only once
♦ multiset
♦ unordered container, multiple entries possible
// the phonebook
phonebook_t phonebook;
Almost Containers
♦ C-style array
♦ string
♦ valarray
♦ bitset
♦ Very useful
Example: find
Example: find_if
♦ Attention:
♦ vector<int> v,w;
for (int k=0;k<100;++k){
v[k]=k; //error: v is size 0!
w.push_back(k); // OK:grows the array and assigns
}
♦ Same problem with copy:
♦ vector<int> v(100), w(0);
copy(v.begin(),v.end(),w.begin()); // problem: w of size 0!
♦ Solution1: vectors only
♦ w.resize(v.size()); copy(v.begin(),v.end(),w.begin());
♦ Solution 2: elegant
♦ copy(v.begin(),v.end(),back_inserter(w)); // uses push_back
♦ also push_front and front_inserter for some containers
Penna Population
♦ easiest modeled as
♦ class Population : public list<Animal> {…}
♦ Removing dead:
♦ remove_if(mem_fun_ref(&Animal::is_dead));
♦ Removing dead, and others with probability N/N0:
♦ remove_if(animal_dies(N/N0));
♦ where animal_dies is a function object taking N/N0 as parameter
♦ Inserting children:
♦ cannot go into same container, as that might invalidate iterators:
vector<Animal> children;
for(const_iterator a=begin();a!=end();++a)
if(a->pregnant())
children.push_back(a->child());
copy(children.begin(),children.end(),
back_inserter(*this);
♦ The search range is halved in every step and we thus need at most
O(ln N) steps
Example: lower_bound
Algorithms overview
♦ Nonmodifying
♦ Modifying
♦ for_each
♦ transform
♦ find, find_if, ♦ copy, copy_backward
find_first_of
♦ swap, iter_swap,
♦ adjacent_find
swap_ranges
♦ count, count_if
♦ replace, replace_if,
♦ mismatch
replace_copy,
♦ equal
replace_copy_if
♦ fill, fill_n
♦ search
♦ generate, generate_n
♦ find_end
♦ remove, remove_if,
♦ search_n
remove_copy,
remove_copy_if
♦ unique, unique_copy
♦ reverse, reverse_copy
♦ rotate, rotate_copy
♦ random_shuffle
Exercise
int main()
{
vector<string> data;
copy(istream_iterator<string>(cin),istream_iterator<string>(),
back_inserter(data));
sort(data.begin(), data.end());
unique_copy(data.begin(),data.end(),ostream_iterator<string>(cout,"\n"));
}
Summary