Professional Documents
Culture Documents
Lectures Slideset
Lectures Slideset
Algoritmen
An introduction
2023-2024
Silvia Zaccardi Katka Kostkova Dr. Lubos Omelina Prof. Bart Jansen
PL9.1 PL9.1 PL9.1 PL9.1
szaccard@etrovub.be kkostkova@etrovub.be lomelina@etrovub.be bjansen@etrovub.be
The goal of this course is to truly understand a set of basic data structures
and algorithms, by implementing them yourself in concrete computer
programs. These algorithms and data structures are the building blocks for
more complex algorithms and data structures in other courses and are
required in almost all day to day programming.
The project will force you to think about the proper selection and usage of
the different algorithms and data structures. You have two options:
1 We provide you a description of the assignment. We have it split in
four parts that match with the progress of the course.
2 You design your own project. More freedom, but less structure.
Individual discussions to set goals. A game
There will be 3 project deadlines:
1 1st intermediate deadline: 26.11.2023
2 2nd intermediate deadline: 17.12.2023
3 final deadline - one week before the written exam
16
14
12
10
0
0.0 2.5 5.0 7.5 10.0 12.5 15.0 17.5
20
15
10
0
0.0 2.5 5.0 7.5 10.0 12.5 15.0 17.5 20.0
Week after week, we will build more complex code, based on previous
code.
The advantage is that you will be able to build some complex data
structures and algorithms very soon.
The danger is that you get lost soon!
The required programming skills and the concepts get more difficult
at a very fast pace.
It is your duty and not mine to do everything you can not to get lost!
Be proactive, work hard, ask for help, ...
If you get lost in this semester (this actually happens for many
students), do not wait for a miracle but do something about it.
Theorem 1
A number p ∈ N is prime if it can only be divided by 1 and itself.
p u b l i c c l a s s MyFirstJavaProgram {
/∗ T h i s i s my f i r s t j a v a program .
∗ T h i s w i l l p r i n t ’ H e l l o World ’ a s t h e o u t p u t
∗ T h i s i s an e x a m p l e o f m u l t i−l i n e comments .
∗/
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
// T h i s i s an e x a m p l e o f s i n g l e l i n e comment
/∗ T h i s i s a l s o an e x a m p l e o f s i n g l e l i n e comment . ∗/
System . o u t . p r i n t l n ( ” H e l l o World ” ) ;
}
}
p u b l i c c l a s s MySecondJavaProgram {
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
// f o r now we p u t a l l c o d e h e r e and don ’ t a s k why
int x = 5;
int y = 6;
int z = x + y ;
double j = 1.21 + z ;
System . o u t . p r i n t l n ( z ) ;
System . o u t . p r i n t l n ( j ) ;
}
}
p u b l i c c l a s s MyThirdJavaProgram
{
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s )
{
// f o r now we p u t a l l c o d e h e r e and don ’ t a s k why
int x = 5;
i f ( x > 6)
{
System . o u t . p r i n t l n ( ”Too s m a l l ” ) ;
}
else
{
System . o u t . p r i n t l n ( ” L a r g e enough ” ) ;
}
}
}
p u b l i c c l a s s MyFourthJavaProgram
{
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s )
{
// f o r now we p u t a l l c o d e h e r e and don ’ t a s k why
f o r ( i n t x = 0 ; x < 1 0 ; x++)
{
System . o u t . p r i n t l n ( x ) ;
}
}
}
p u b l i c c l a s s MyFifthJavaProgram
{
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s )
{
// f o r now we p u t a l l c o d e h e r e and don ’ t a s k why
i n t x = 10;
w h i l e ( x>6)
{
System . o u t . p r i n t l n ( x − 1 ) ;
}
}
}
p u b l i c c l a s s MySixthJavaProgram
{
p u b l i c s t a t i c i n t sum ( i n t x , i n t y )
{
return x + y ;
}
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s )
{
i n t r e s u l t = sum ( 1 , 2 ) ;
System . o u t . p r i n t l n ( r e s u l t ) ;
}
}
p u b l i c c l a s s MySeventhJavaProgram
{
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s )
{
i n t d a t a [ ] = new i n t [ 3 ] ;
data [ 0 ] = 3;
data [ 1 ] = 6;
data [ 2 ] = 9
System . o u t . p r i n t l n ( d a t a [ 0 ] ) ;
}
}
p u b l i c c l a s s Vector
{
p r i v a t e i n t data [ ] ;
p r i v a t e i n t count ;
p u b l i c V e c t o r ( i n t c a p a c i t y ){ }
p u b l i c i n t g e t ( i n t i n d e x ){ }
p u b l i c v o i d a d d F i r s t ( i n t i t e m ){ }
}
p u b l i c c l a s s Vector
{
p r i v a t e i n t data [ ] ;
p r i v a t e i n t count ;
p u b l i c Vector ( i n t capacity )
{
d a t a = new i n t [ c a p a c i t y ] ;
count = 0;
}
...
}
p u b l i c c l a s s Vector
{
...
p u b l i c i n t get ( i n t index )
{
r e t u r n data [ index ] ;
}
p u b l i c void a d d F i r s t ( i n t item )
{
f o r ( i n t i = c o u n t ; i > 0 ; i −−){
d a t a [ i ] = d a t a [ i −1];
}
data [ 0 ] = item ;
c o u n t ++;
}
}
System . o u t . p r i n t l n ( numbers . g e t ( 0 ) ) ;
System . o u t . p r i n t l n ( numbers . g e t ( 1 ) ) ;
}
}
When the vector is sorted, searching for a given element can be done
more efficiently.
First check whether the element is supposed to be in the first or the
second half of the vector.
This way only half of it needs to be searched for.
In the right half of the vector, check whether the element should be
in the first or the second half.
...
How to make sure a Vector is sorted?
I Implement addSorted(...) method.
I Remove other add∗ methods.
I Rename Vector class to SortedVector .
p u b l i c boolean b i n a r y S e a r c h ( i n t key )
{
int start = 0;
i n t end = c ou nt −1;
w h i l e ( s t a r t <= end )
{
i n t m i d d l e = ( s t a r t + end + 1 ) / 2 ;
i f ( k e y < d a t a [ m i d d l e ] ) end = m i d d l e −1;
e l s e i f ( key > data [ middle ] ) s t a r t = middle + 1 ;
else return true ;
}
return false ;
}
1 Simple, yet useful. Vector will be the base for building more complex
structures.
2 Implementation is limited to a vector containing integers.
3 We need generic data structures in order to build a reusable tool set.
4 using Object, Comparable, Generics, ...
Theorem 2
Pn n(n+1)
i=1 i = 2
Proof.
1 For n = 1. Trivial
2 (induction step)
Pn+1 n(n+1) 2n+2+n2 +n (n+1)(n+2)
i=1 i = n + 1 + 2 = 2 = 2
Theorem 3
Any positive integer greater than one can be written as a product of
primes.
OR more formally
For any integer n ≥ 1 there exists primes p1 , ..., pm such that
n = p1 ...pm
Proof.
1 n is prime. A trivial basecase
2 n is not prime. Hence, it is a product of two numbers, say n1 and n2 ,
and nor n1 nor n2 are 1.
n1 and n2 are smaller than n, hence the induction hypothesis says
that n1 and n2 can be written as a product of primes.
Hence, n1 n2 is also a product of primes.
Definition 2 (Factorial)
Qn−1
n! = i=0 n−i
Example: 5! = 5 × 4 × 3 × 2 × 1 = 120
int f a c t o r i a l ( int n)
{
double r e s u l t = 1;
f o r ( i n t i =0; i<n ; i ++)
{
r e s u l t ∗= ( n − i ) ;
}
return result ;
}
Definition 3 (Factorial)
1 if n = 0
n! =
n × (n − 1)! otherwise
5! = 5 × 4!
= 5 × 4 × 3!
= 5 × 4 × 3 × 2!
= 5 × 4 × 3 × 2 × 1!
= 5 × 4 × 3 × 2 × 1 × 0!
=5×4×3×2×1×1
= 120
Definition 4
A function is recursive if it is defined in terms of itself.
int f a c t o r i a l ( int n)
{
i f ( n == 0 ) r e t u r n 1 ;
e l s e r e t u r n n ∗ f a c t o r i a l ( n −1);
}
Theorem 4 (gcd)
The greatest common divisor (gcd) of two positive integers is the
largest integer that divides evenly into both of them. (divides evenly =
the remainder of the division is zero)
For example, the greatest common divisor of 102 and 68 is 34 since both
102 and 68 are multiples of 34, but no integer larger than 34 divides
evenly into 102 and 68.
p u b l i c i n t gcd ( i n t p , i n t q )
{
w h i l e ( q != 0 )
{
i n t temp = q ;
q = p % q;
p = temp ;
}
return p ;
}
p u b l i c i n t gcd ( i n t p , i n t q )
{
i f ( q == 0 ) r e t u r n p ;
e l s e r e t u r n gcd ( q , p % q ) ;
}
p u b l i c i n t mcCarthy ( i n t n )
{
i f ( n > 100)
return n − 10;
r e t u r n mcCarthy ( mcCarthy ( n + 1 1 ) ) ;
}
Definition 5 (big-Oh)
T (n) is O(F (n)) if there are positive constants c and n0 such that
T (n) ≤ cF (n) when n ≥ no .
Definition 6 (big-Omega)
T (n) is Ω(F (n)) if there are positive constants c and n0 such that
T (n) ≥ cF (n) when n ≥ no .
Definition 7 (big-Theta)
T (n) is Θ(F (n)) if and only if T (n) is O(F (n)) and T (n) is Ω(F (n)).
Time complexity
1000
O(2n )
O(n2 )
800
O(nlogn)
O(n)
O(logn)
Running time
600
400
200
0
1 2 3 4 5 6 7 8 9 10
Time complexity
25
O(nlogn)
O(n)
20
O(logn)
Running time
15
10
0
1 2 3 4 5 6 7 8 9 10
Proof.
Theorem 7
The time complexity of divide and conquer algorithms is defined by
the following recurrence relation: T(n) = a T(n/b) + f(n)
Theorem 8
Under the above stated conditions, the solution of the recurrence
relation
T (n) = aT (n/b) + f (n)
is given by
log n/ log b
X
T (n) = ai f (n/b i )
i=0
Proof.
At the start of the algorithm (level 0 ), there is a single problem of
size n (or put differently a0 pieces of size n/b 0 each).
At level 1, the problem is divided into a pieces of size n/b each (or
put differently a1 pieces of size n/b 1 each).
Each of these a smaller pieces will be broken down into smaller pieces.
Consequently, at level i, there will be ai pieces, each of size n/b i .
This process of dividing the problem into smaller problems continues
until the problems reach size 1. But after how many steps is this
exactly?
At the lowest level i, we have that n/b i = 1, so n = b i , so
i = logb n = log2 n/ log2 b.
Theorem 9
The time complexity of the Binary Search algorithm on a sorted vector
is O(log n).
Proof.
At each level, there is one comparison to be made for deciding whether to
go to the left or to the right and in each step the size of the subproblems
is reduced by a factor of 2.
getFirst: O(1)
getLast: O(1)
addFirst: O(n)
addLast: O(1)
get: O(1)
size: O(1)
contains: O(n)
removeFirst: O(n)
removeLast: O(1)
...
p r i v a t e L i s t E l e m e n t head ;
Lubos Omelina, Bart Jansen Algo Data 2023-2024 84 / 259
AddFirst: Adding a new element at the beginning of the
list
p u b l i c void a d d F i r s t ( Object o )
{
L i s t E l e m e n t newElement = new L i s t E l e m e n t ( o ) ; // s t e p 1
newElement . s e t R e s t ( head ) ; // s t e p 2
head = newElement ; // s t e p 3
}
p u b l i c void a d d F i r s t ( Object o )
{
head = new L i s t E l e m e n t ( o , head ) ;
}
p u b l i c Object get ( i n t n)
{
L i s t E l e m e n t d = head ;
w h i l e ( n>0)
{
d = d. rest ();
n−−;
}
return d . el1 ;
}
getFirst: O(1)
getLast: O(n)
addFirst: O(1)
addLast: O(n)
get: O(n)
size: O(1)
contains: O(n)
removeFirst: O(1)
removeLast: O(n)
...
p u b l i c c l a s s Stack
{
p r i v a t e Vector data ;
p u b l i c Stack ()
{
d a t a = new V e c t o r ( ) ;
}
p u b l i c c l a s s Queue
{
p r i v a t e Vector data ;
p u b l i c Queue ( )
{
d a t a = new V e c t o r ( ) ;
}
The queue only implements two basic methods (push and pop), but
their time complexities are very different.
This is the case for both the vector and the linked list based
implementation.
See later for O(1) push and pop methods.
Similar to the Stack and Queue data structure, the priority queue is a
simple structure supporting the push and pop methods.
The pop method does not return the topmost element in the
structure, but the element with the highest priority.
As a consequence, the push method does not only store the data, but
also the priority.
p u b l i c v o i d addSorted ( Comparable o )
{
// an empty l i s t , add e l e m e n t i n f r o n t
i f ( head == n u l l ) head = new L i s t E l e m e n t ( o , n u l l ) ;
e l s e i f ( head . f i r s t ( ) . compareTo ( o ) > 0 )
{
// we h a v e t o add t h e e l e m e n t i n f r o n t
head = new L i s t E l e m e n t ( o , head ) ;
}
else
{
// we h a v e t o f i n d t h e f i r s t e l e m e n t w h i c h i s b i g g e r
L i s t E l e m e n t d = head ;
w h i l e ( ( d . r e s t ( ) != n u l l )&&
( d . r e s t ( ) . f i r s t ( ) . compareTo ( o ) < 0 ) )
{
d = d. rest ();
}
ListElement next = d . r e s t ( ) ;
d . s e t R e s t ( new L i s t E l e m e n t ( o , n e x t ) ) ;
}
c o u n t ++;
}
p u b l i c i n t compareTo ( O b j e c t o )
{
P r i o r i t y P a i r p2 = ( P r i o r i t y P a i r ) o ;
r e t u r n ( ( C o m p a r a b l e ) p r i o r i t y ) . compareTo ( p2 . p r i o r i t y ) ;
}
}
p r i v a t e L i n k e d L i s t data ;
public PriorityQueue ()
{
d a t a = new L i n k e d L i s t ( ) ;
}
p u b l i c v o i d push ( O b j e c t o , i n t p r i o r i t y )
{
// make a p a i r o f o and p r i o r i t y
// add t h i s p a i r t o t h e s o r t e d l i n k e d l i s t .
}
p u b l i c O b j e c t pop ( )
{
Lubos//Omelina,
add y oBartu r cJansen
ode h e r e Algo Data 2023-2024 106 / 259
Circular Vectors
addFirst
I Add the new element at position ”start - 1”
I Unless start is 1, then we need to add at the end of the vector, i.e. at
”capacity - 1”.
I start = (start + capacity - 1) % capacity
addLast
I Add the new element at position ”start + count”
I When start + count ≥ capacity, then we need to add at the beginning
of the vector.
I Store the element at (start + count) % capacity
public v o i d a d d F i r s t ( O b j e c t e l e m e n t ){}
public v o i d a d d L a s t ( O b j e c t e l e m e n t ){}
public O b j e c t g e t F i r s t (){}
public O b j e c t g e t L a s t (){}
public v o i d r e m o v e F i r s t (){}
public v o i d r e m o v e L a s t (){}
public v o i d p r i n t (){}
}
addFirst: O(1)
addLast: O(1)
removeFirst: O(1)
removeLast: O(1)
p u b l i c c l a s s Queue
{
p r i v a t e Vector data ;
p u b l i c Queue ( )
{
d a t a = new V e c t o r ( ) ;
}
p u b l i c c l a s s Queue
{
p r i v a t e C i r c u l a r V e c t o r data ;
p u b l i c Queue ( )
{
d a t a = new C i r c u l a r V e c t o r ( ) ;
}
p r i v a t e i n t count ;
p r i v a t e D o u b l e L i n k e d L i s t E l e m e n t head ;
private DoubleLinkedListElement t a i l ;
public DoubleLinkedList ()
{
head = n u l l ;
tail = null ;
count = 0;
}
public Object g e t F i r s t ( ) { . . . }
public Object getLast ( ) { . . . }
public int size (){...}
public void a d d F i r s t ( Object value ) { . . . }
public void addLast ( Object value ) { . . . }
}
A Binary Search Tree is a binary tree in which for each node K all the
nodes in the left subtree of K are smaller than the node K and all the
nodes in the right subtree of K are larger than K .
7 200
4 11
1 9 12
8 10
8,5
p u b l i c c l a s s Tree
{
p r i v a t e c l a s s TreeNode i m p l e m e n t s C o m p a r a b l e
{
p r o t e c t e d Comparable v a l u e ;
p r o t e c t e d TreeNode l e f t N o d e ;
p r o t e c t e d TreeNode r i g h t N o d e ;
p r o t e c t e d TreeNode p a r e n t N o d e ;
public TreeNode g e t L e f t T r e e ( ) { r e t u r n l e f t N o d e ; }
public TreeNode g e t R i g h t T r e e ( ) { r e t u r n r i g h t N o d e ; }
public TreeNode g e t P a r e n t ( ) { r e t u r n p a r e n t N o d e ; }
public Comparable g e t V a l u e (){ r e t u r n v a l u e ;}
p u b l i c i n t compareTo ( O b j e c t a r g 0 )
{
TreeNode node2 = ( TreeNode ) a r g 0 ;
r e t u r n v a l u e . compareTo ( node2 . v a l u e ) ;
}
}
Lubos Omelina, Bart Jansen Algo Data 2023-2024 126 / 259
Binary Search Tree
p u b l i c c l a s s TreeNode i m p l e m e n t s C o m p a r a b l e
{
p r o t e c t e d Comparable v a l u e ;
p r o t e c t e d TreeNode l e f t N o d e ;
p r o t e c t e d TreeNode r i g h t N o d e ;
p r o t e c t e d TreeNode p a r e n t N o d e ;
public TreeNode g e t L e f t T r e e ( ) { r e t u r n l e f t N o d e ; }
public TreeNode g e t R i g h t T r e e ( ) { r e t u r n r i g h t N o d e ; }
public TreeNode g e t P a r e n t ( ) { r e t u r n p a r e n t N o d e ; }
public Comparable g e t V a l u e (){ r e t u r n v a l u e ;}
p u b l i c i n t compareTo ( O b j e c t a r g 0 )
{
TreeNode node2 = ( TreeNode ) a r g 0 ;
r e t u r n v a l u e . compareTo ( node2 . v a l u e ) ;
}
}
p u b l i c b o o l e a n f i n d ( Comparable element )
{
r e t u r n findNode ( element , r o o t ) ;
}
p r i v a t e v o i d i n s e r t A t N o d e ( Comparable element ,
TreeNode c u r r e n t ,
TreeNode p a r e n t )
{
// i f t h e node we c h e c k i s empty
i f ( c u r r e n t == n u l l )
{
TreeNode newNode = new TreeNode ( e l e m e n t ) ;
// t h e c u r r e n t node i s empty , b u t we h a v e a p a r e n t
i f ( p a r e n t != n u l l )
{
i f ( e l e m e n t . compareTo ( p a r e n t . v a l u e ) < 0 ) p a r e n t . l e f t N o d e = newNode ;
e l s e p a r e n t . r i g h t N o d e = newNode ;
}
e l s e r o o t = newNode ;
newNode . p a r e n t N o d e = p a r e n t ;
}
e l s e i f ( e l e m e n t . compareTo ( c u r r e n t . v a l u e ) == 0 )
{
// we do n o t c a r e
}
// i f t h e e l e m e n t i s s m a l l e r t h a n c u r r e n t , go l e f t
e l s e i f ( e l e m e n t . compareTo ( c u r r e n t . v a l u e ) < 0 )
{
insertAtNode ( element , c u r r e n t . g e t L e f t T r e e ( ) , c u r r e n t ) ;
}
// i f t h e e l e m e n t i s b i g g e r t h a n c u r r e n t , go r i g h t
e l s e insertAtNode ( element , c u r r e n t . getRightTree ( ) ,
current );
Lubos Omelina, Bart Jansen Algo Data 2023-2024 130 / 259
Binary Search Tree: Deleting 7
100
7 200
4 11
1 9 12
8 10
8,5
7 200
4 11
1 9 12
8 10
8,5
7 200
4 11
1 9 12
8 10
8,5
100
8 200
4 11
1 9 12
8,5 10
100
8 200
4 11
1 9 12
8,5 10
100
8 200
4 11
1 9 12
8,5 10
q q
(a) z r
NIL r
q q
(b) z l
l NIL
q q
q q
(c) z y
l y l x
NIL x
q q
q q q
(d) z z y y
l r l NIL r l r
y x x
NIL x
p r i v a t e v o i d removeNode ( C o m p a r a b l e e l e m e n t , TreeNode c u r r e n t )
{
i f ( c u r r e n t == n u l l ) r e t u r n ;
e l s e i f ( e l e m e n t . compareTo ( c u r r e n t . v a l u e ) == 0 )
{
i f ( c u r r e n t . l e f t N o d e == n u l l ) t r a n s p l a n t ( c u r r e n t , c u r r e n t . r i g h t N o d e ) ;
e l s e i f ( c u r r e n t . r i g h t N o d e == n u l l ) t r a n s p l a n t ( c u r r e n t , c u r r e n t . l e f t N o d e ) ;
else
{
TreeNode y = minNode ( c u r r e n t . r i g h t N o d e ) ;
i f ( y . p a r e n t N o d e != c u r r e n t )
{
transplant (y , y . rightNode ) ;
y . rightNode = current . rightNode ;
y . rightNode . parentNode = y ;
}
transplant ( current , y ) ;
y . leftNode = current . leftNode ;
y . l e f t N o d e . parentNode = y ;
}
}
e l s e i f ( e l e m e n t . compareTo ( c u r r e n t . v a l u e ) < 0 )
{
removeNode ( e l e m e n t , c u r r e n t . g e t L e f t T r e e ( ) ) ;
}
e l s e removeNode ( e l e m e n t , c u r r e n t . g e t R i g h t T r e e ( ) ) ;
}
A tree traverse is a method that visits each element in the tree and
performs some operation on the element.
Print all elements
Call a method on each element
Double each node value if it is a number
...
p r i v a t e v o i d p r i n t N o d e ( TreeNode n )
{
i f ( n != n u l l )
{
System . o u t . p r i n t l n ( n . v a l u e ) ;
printNode (n . leftNode ) ;
printNode (n . rightNode ) ;
}
}
System . o u t . p r i n t l n ( n . v a l u e ) ;
i f ( n . g e t R i g h t T r e e ( ) != n u l l ) t . push ( n . g e t R i g h t T r e e ( ) ) ;
i f ( n . g e t L e f t T r e e ( ) != n u l l ) t . push ( n . g e t L e f t T r e e ( ) ) ;
}
}
System . o u t . p r i n t l n ( n . v a l u e ) ;
i f ( n . g e t R i g h t T r e e ( ) != n u l l ) t . push ( n . g e t R i g h t T r e e ( ) ) ;
i f ( n . g e t L e f t T r e e ( ) != n u l l ) t . push ( n . g e t L e f t T r e e ( ) ) ;
}
}
p u b l i c abstract c l a s s TreeAction
{
p u b l i c a b s t r a c t v o i d r u n ( TreeNode n ) ;
}
Subclass from this one for each operation you want to perform.
! !
! !
! !
! !
! !
! !
! !
! !
p u b l i c NQueens ( i n t n )
{
L i n k e d L i s t v i s i t e d L i s t = new L i n k e d L i s t ( ) ;
C h e s s B o a r d s t a t e = new C h e s s B o a r d ( n ) ;
S t a c k t o d o L i s t = new S t a c k ( ) ;
t o d o L i s t . push ( s t a t e ) ;
w h i l e ( ! t o d o L i s t . empty ( ) )
{
C h e s s B o a r d c u r r e n t S t a t e = ( C h e s s B o a r d ) t o d o L i s t . pop ( ) ;
v i s i t e d L i s t . addFirst ( currentState );
i f ( c u r r e n t S t a t e . goalReached ( ) )
{
System . o u t . p r i n t l n ( ”GOAL REACHED” ) ;
currentState . print ();
return ;
}
else
{
LinkedList successorStates = currentState . successors ();
f o r ( i n t i =0; i<s u c c e s s o r S t a t e s . s i z e ( ) ; i ++)
{
ChessBoard next = ( ChessBoard ) s u c c e s s o r S t a t e s . get ( i ) ;
i f ( ! v i s i t e d L i s t . f i n d ( n e x t ) ) t o d o L i s t . push ( n e x t ) ;
}
}
}
}
}
p u b l i c C h e s s B o a r d ( i n t n){}
p u b l i c C h e s s B o a r d ( C h e s s B o a r d a n o t h e r B o a r d ){}
p u b l i c b o o l e a n g e t ( i n t i , i n t j ){}
p u b l i c i n t g e t C o u n t (){}
p u b l i c v o i d s e t ( i n t i , i n t j , b o o l e a n v a l u e ){}
p u b l i c L i n k e d L i s t s u c c e s s o r s (){}
p u b l i c b o o l e a n g o a l R e a c h e d (){}
p u b l i c v o i d p r i n t (){}
p r i v a t e b o o l e a n canAdd ( i n t x , i n t y ){}
p u b l i c i n t compareTo ( O b j e c t a r g 0 ) {}
}
p u b l i c ChessBoard ( i n t n )
{
size = n;
b o a r d = new b o o l e a n [ n ] [ n ] ;
f o r ( i n t i= 0 ; i<n ; i ++)
f o r ( i n t j =0; j<n ; j ++)
board [ i ] [ j ] = f a l s e ;
}
p u b l i c boolean get ( i n t i , i n t j )
{
r e t u r n board [ i ] [ j ] ;
}
p u b l i c i n t getCount ( )
{
r e t u r n count ;
}
p u b l i c boolean goalReached ()
{
r e t u r n c o u n t == s i z e ;
}
p u b l i c i n t compareTo ( O b j e c t a r g 0 ) {
ChessBoard another = ( ChessBoard ) arg0 ;
f o r ( i n t i =0; i<s i z e ; i ++)
{
f o r ( i n t j =0; j<s i z e ; j ++)
{
i f ( b o a r d [ i ] [ j ] != a n o t h e r . b o a r d [ i ] [ j ] ) r e t u r n 1 ;
}
}
return 0;
}
It works!
But we are a little bit naive in our implementation:
I The visited list is O(n) for finding.
I The board is 2D, comparing two n-by-n boards requires up to n2
comparisons, O(n2 ).
I To generate a successor, the board is copied to a new board ( O(n2 )).
I Generating the successors of a board also requires quite some checks at
the horizontal, vertical and diagonal lines.
Guarantee that those elements that are accessed often are close to
the root.
I Keep the BST as is, when accessing move elements up.
I Example: Splay Trees
All Kinds of approaches to guarantee that the tree is more or less
balanced.
I If all paths have equal length, the length of the path H is log2 (n).
I Example: Red-Black Trees
y x
Right rotation
x y
C A
B B
A Left rotation C
p r i v a t e v o i d r o t a t e L e f t ( TreeNode x )
{
TreeNode y = x . r i g h t N o d e ;
x . rightNode = y . leftNode ;
i f ( y . l e f t N o d e != n u l l ) y . l e f t N o d e . p a r e n t N o d e = x ;
y . parentNode = x . parentNode ;
i f ( x . p a r e n t N o d e == n u l l ) r o o t = y ;
e l s e i f ( x == x . p a r e n t N o d e . l e f t N o d e ) x . p a r e n t N o d e . l e f t N o d e = y ;
e l s e x . parentNode . rightNode = y ;
y . leftNode = x ;
x . parentNode = y ;
}
p r i v a t e v o i d r o t a t e R i g h t ( TreeNode y )
{
TreeNode x = y . l e f t N o d e ;
y . leftNode = x . rightNode ;
i f ( x . r i g h t N o d e != n u l l ) x . r i g h t N o d e . p a r e n t N o d e = y ;
x . parentNode = y . parentNode ;
i f ( y . p a r e n t N o d e == n u l l ) r o o t = x ;
e l s e i f ( y == y . p a r e n t N o d e . r i g h t N o d e ) y . p a r e n t N o d e . r i g h t N o d e = x ;
e l s e y . parentNode . l e f t N o d e = x ;
x . rightNode = y ;
y . parentNode = x ;
}
Upon access of the node x, Left and Right rotations are used to move
x up to the root.
If x is accessed in the near future, its access will be more efficient.
Each rotation maintains the BST properties and can be done in
constant time.
Each rotation moves the node x one level up into the tree. If the tree
has a depth H, then this extra cost for inserting elements is
O(H) = O(log2 (n))
g p x
(a) p x g p
x g
g g x
g g x
p x p g
(b) x p
p
p
x x
p x
x p
g x
p p
x g
x g
g g
p x
p g
x p
g
x
p p
g x
x g
g g
p x
g p
x p
Definition 9 (black-height)
The black-height of a node x bh(x) in a red-black tree is the number of
black nodes on any simple path from, but not including, a node x down to
a leaf.
3 26
3 17 41 2
2 14 2 21 2 30 1 47
2 10 1 16 1 19 1 23 1 28 1 38 NIL NIL
NIL NIL
(a)
Proof.
The proof is based on induction on the height h of the tree x.
If h(x) = 0, then x is a leaf. Then bh(x) = 0, so 2bh(x) − 1 = 0 and the
statement holds.
If however h(x) ≥ 0, then x has two children. Each child has a
black-height of either bh(x) or bh(x) − 1, depending on whether its colour
is red or black. So, black height is at least bh(x) − 1.
As the height of the subtrees is smaller than h(x), we apply the induction
hypothesis and find that each child has at least 2bh(x)−1 − 1 internal
nodes. So, the subtree rooted at x has at least
(2bh(x)−1 − 1) + (2bh(x)−1 − 1) + 1 = 2bh(x) − 1 nodes
Proof.
Let h be the height of the tree. According to red-black property 4, at least
half of the nodes on the any simple path from root to leaf, not including
the root, is black. Hence, the back-height of the root must be at least
h/2, hence n ≥ 2h/2 − 1, hence n + 1 ≥ 2h/2 , and consequently
log2 (n + 1) ≥ h/2 and h ≤ 2 log2 (n + 1).
p r o t e c t e d c l a s s C o l o u r e d T r e e N o d e i m p l e m e n t s C o m p a r a b l e {}
p r o t e c t e d ColouredTreeNode r o o t ;
p r i v a t e void r o t a t e L e f t ( ColouredTreeNode x )
{}
p r i v a t e void r o t a t e R i g h t ( ColouredTreeNode y )
{}
p u b l i c v o i d i n s e r t ( Comparable element )
{
C o l o u r e d T r e e N o d e newNode = new C o l o u r e d T r e e N o d e ( e l e m e n t , T r e e N o d e C o l o r . Red ) ;
i n s e r t A t N o d e ( newNode , r o o t , n u l l ) ;
f i x U p ( newNode ) ;
}
p r o t e c t e d v o i d i n s e r t A t N o d e ( C o l o u r e d T r e e N o d e newNode ,
ColouredTreeNode current , ColouredTreeNode parent )
{}
p u b l i c i n t compareTo ( O b j e c t a r g 0 )
{
C o l o u r e d T r e e N o d e node2 = ( C o l o u r e d T r e e N o d e ) a r g 0 ;
r e t u r n v a l u e . compareTo ( node2 . v a l u e ) ;
}
}
We keep the same insert method as in the original binary search tree.
The node we add is coloured red.
After insertion, we are guaranteed we still have a binary search tree.
Which red-black tree properties can be violated by inserting this way?
If the binary search tree meets all the red-black tree properties, except
property 2 (the root is black), then a simple recolouring of the root to
black is possible. This does never violate any of the other red-black
tree properties.
If the binary search tree violates the fourth property (if a node is red
then both the children are black), then we first solve this violation.
I This solution should not violate red-black properties 1, 3 and 5.
I If the solution violates red-black property 2, then see above.
11
2 14
1 7 15
5 8 y
z 4 Case 1
11
2 14 y
1 7 z 15
5 8
4 Case 2
11
7 14 y
(c) z 2 8 15
1 5
Case 3
4
z 2 11
1 5 8 14
4 15
C new z C
(a) A D y A D
α B z δ ε α B δ ε
β γ β γ
C new z C
(b) B D y B D
z A γ δ ε A γ δ ε
α β α β
C C B
A δ y B δ y z A C
α B z z A γ α β γ δ
β γ α β
Case 2 Case 3
1 2 3 4 5 6
1 0 1 0 1 0 0
1 2 3 2 0 0 0 0 1 0
3 0 0 0 0 1 1
4 0 1 0 0 0 0
4 5 6 5 0 0 0 1 0 0
(a) 6 0 (b)
0 0 0 0 1
(b) (c)
p u b l i c c l a s s Matrix
{
// some a p p r o p r i a t e p r i v a t e members .
p u b l i c Matrix ( i n t nrNodes )
{
// a l l o c a t e an N−by−N m a t r i x w h e r e N = n r N o d e s
// a l l e l e m e n t s a r e i n i t i a l l y 0
}
p u b l i c v o i d s e t ( i n t row , i n t c o l , C o m p a r a b l e w e i g h t )
{
// s t o r e t h e w e i g h t a t t h e g i v e n row and column .
}
p u b l i c C o m p a r a b l e g e t ( i n t row , i n t c o l )
{
// r e t u r n t h e w e i g h t a t t h e g i v e n row and column .
}
}
p u b l i c c l a s s MatrixGraph
{
p r i v a t e Matrix data ;
p u b l i c MatrixGraph ( i n t nrNodes )
{
d a t a = new M a t r i x ( n r N o d e s ) ;
}
p u b l i c v o i d addEdge ( i n t from , i n t to , d o u b l e w)
{
d a t a . s e t ( from , to , w ) ;
}
p u b l i c d o u b l e g e t E d g e ( i n t from , i n t t o )
{
r e t u r n ( Do ub le ) d a t a . g e t ( from , t o ) ;
}
}
1 2 4
1 2 3 2 5
3 6 5
4 2
4 5 6 5 4
6 6
(a) (b)
(a) (b)
p u b l i c Node ( C o m p a r a b l e l a b e l )
{
info = label ;
e d g e s = new V e c t o r ( ) ;
}
p u b l i c v o i d addEdge ( Edge e )
{
edges . addLast ( e ) ;
}
p u b l i c Comparable g e t L a b e l ( )
{
return info ;
}
}
p r i v a t e c l a s s Edge i m p l e m e n t s C o m p a r a b l e
{
p r i v a t e Node toNode ;
p u b l i c Edge ( Node t o )
{
toNode = t o ;
}
p u b l i c i n t compareTo ( O b j e c t o )
{
// two e d g e s a r e e q u a l i f t h e y p o i n t
// t o t h e same node .
// t h i s a s s u m e s t h a t t h e e d g e s a r e
// s t a r t i n g from t h e same node ! ! !
Edge n = ( Edge ) o ;
r e t u r n n . toNode . compareTo ( toNode ) ;
}
}
p u b l i c Node ( C o m p a r a b l e l a b e l )
{
info = label ;
e d g e s = new V e c t o r ( ) ;
}
p u b l i c v o i d addEdge ( Edge e )
{
edges . addLast ( e ) ;
}
p u b l i c i n t compareTo ( O b j e c t o ){ . . . }
p u b l i c Comparable g e t L a b e l ( )
{
return info ;
}
}
p r i v a t e c l a s s Edge i m p l e m e n t s C o m p a r a b l e
{
p r i v a t e Node toNode ;
p u b l i c Edge ( Node t o )
{
toNode = t o ;
}
Lubos Omelina, Bart Jansen Algo Data 2023-2024 211 / 259
Edge list representation
p r i v a t e Node f i n d N o d e ( C o m p a r a b l e n o d e L a b e l )
{
Node r e s = n u l l ;
f o r ( i n t i =0; i<n o d e s . s i z e ( ) ; i ++)
{
Node n = ( Node ) n o d e s . g e t ( i ) ;
i f ( n . g e t L a b e l ( ) == n o d e L a b e l )
{
res = n;
break ;
}
}
return res ;
}
This is almost the find method on the vector ... put the right responsibility
in the right datastructure.
p u b l i c Comparable c o n t a i n s ( Comparable o b j )
{
f o r ( i n t i =0; i<c o u n t ; i ++)
{
i f ( d a t a [ i ] . compareTo ( o b j ) == 0 ) r e t u r n o b j ;
}
return null ;
}
p r o t e c t e d Node f i n d N o d e ( C o m p a r a b l e n o d e L a b e l )
{
r e t u r n ( Node ) n o d e s . c o n t a i n s ( new Node ( n o d e L a b e l ) ) ;
}
p u b l i c b o o l e a n f i n d P a t h ( Comparable nodeLabel1 ,
Comparable nodeLabel2 )
{
Node s t a r t S t a t e = f i n d N o d e ( n o d e L a b e l 1 ) ;
Node e n d S t a t e = f i n d N o d e ( n o d e L a b e l 2 ) ;
S t a c k t o D o L i s t = new S t a c k ( ) ;
t o D o L i s t . push ( s t a r t S t a t e ) ;
w h i l e ( ! t o D o L i s t . empty ( ) )
{
Node c u r r e n t = ( Node ) t o D o L i s t . pop ( ) ;
i f ( c u r r e n t == e n d S t a t e )
return true ;
else
{
f o r ( i n t i =0; i<c u r r e n t . e d g e s . s i z e ( ) ; i ++)
{
Edge e = ( Edge ) c u r r e n t . e d g e s . g e t ( i ) ;
t o D o L i s t . push ( e . toNode ) ;
}
}
}
return false ;
}
The DFS on the previous slide does not have a visited list.
Just as in the tree search, it can easily be added, but it is time
consuming to check the visited list at each step.
Keep a boolean in each node to mark whether it was visited before or
not.
Recursive version
p r i v a t e v o i d DFS( Node c u r r e n t )
{
current . v i s i t e d = true ;
f o r ( i n t i =0; i<c u r r e n t . e d g e s . s i z e ( ) ; i ++)
{
Edge e = ( Edge ) c u r r e n t . e d g e s . g e t ( i ) ;
Node n e x t = ( Node ) e . toNode ;
i f ( ! n e x t . v i s i t e d ) DFS( n e x t ) ;
}
p u b l i c v o i d DFS ( )
{
f o r ( i n t i =0; i<n o d e s . s i z e ( ) ; i ++)
{
Node c u r r e n t = ( Node ) n o d e s . g e t ( i ) ;
current . visited = false ;
}
f o r ( i n t i =0; i<n o d e s . s i z e ( ) ; i ++)
{
Node c u r r e n t = ( Node ) n o d e s . g e t ( i ) ;
i f ( ! c u r r e n t . v i s i t e d ) DFS( c u r r e n t ) ;
}
}
DFS exactly gives this ordering (see how they are printed)!
p u b l i c v o i d DFS ( )
{
f o r ( i n t i =0; i<n o d e s . s i z e ( ) ; i ++)
{
Node c u r r e n t = ( Node ) n o d e s . g e t ( i ) ;
current . visited = false ;
}
f o r ( i n t i =0; i<n o d e s . s i z e ( ) ; i ++)
{
Node c u r r e n t = ( Node ) n o d e s . g e t ( i ) ;
i f ( ! c u r r e n t . v i s i t e d ) DFS( c u r r e n t ) ;
}
}
Proof.
Assume G has a cycle c. Define v to be the first vertex in c discovered in
the DFS. Let (u,v) be an edge in c. The DFS from v will explore all
vertices reachable from v, also u. So, there is a path from v to u in G. So,
(u,v) is a back edge.
Assume the DFS results in a back edge (u,v). Then v is an ancestor of u
in G. So, there is a path from u to v in G. This path, together with (u,v)
forms a cycle.
Pn
Consider C = A x B, then cij = k=1 aik bkj
So in A2 , aij = nk=1 aik akj
P
aij > 0 in A2 if there is a node k such that there is an edge (i,k) and
an edge (k,j)
(which is a path of length 2).
So, in A2 , an element on the diagonal is larger than zero if there is an
element with a path of length two to itself.
We are looking for the path with the smallest total weight.
We are assuming there is no heuristic available (otherwise A∗ ).
We focus on algorithms for finding Single-Source shortest paths, i.e.
algorithms that find the shortest path to all reachable nodes from a
given node.
Proof.
0i p pij pjk
p can be written as v0 −→ vi −→ vj −→ vk and hence
w (p) = w (p0i ) + w (pij ) + w (pjk ). Assume there is a path pij0 from vi to vj
p pij0 pjk
such that w (pij0 ) < w (pij ), then p 0 = v0 −→
0i
vi −→ vj −→ vk is a path
from v0 to vk with a weight w (p ) = w (p0i ) + w (pij0 ) + w (pjk ) < w (p).
0
u v u v
2 2
5 9 5 6 R ELAX .u; ; w/
RELAX(u,v,w) RELAX(u,v,w) 1 if :d > u:d C w.u; /
u v u v 2 :d D u:d C w.u; /
2 2
5 7 5 6 3 : D u
(a) (b)
Theorem 13
Let G=(V,E) be a weighted, directed graph with source s and weight
function w : E → R, and assume that G contains no
(negative-weight) cycles that are reachable from s. Then, after the
|V | − 1 iterations of the for loop of lines 24 of BELLMAN-FORD, we
have v .d = δ(s, v ) for all vertices v that are reachable from s.
We consider the vector to have two parts: a first part that is already
sorted and a second part that still needs to be sorted.
Start with the first element in the first part and the second part to be
the rest.
Take all elements from the still to be sorted part and move them one
by one in the sorted part.
1 2 3 4 5 6 1 2 3 4 5 6 1 2 3 4 5 6
(a) 5 2 4 6 1 3 (b) 2 5 4 6 1 3 (c) 2 4 5 6 1 3
1 2 3 4 5 6 1 2 3 4 5 6 1 2 3 4 5 6
(d) 2 4 5 6 1 3 (e) 1 2 4 5 6 3 (f) 1 2 3 4 5 6
p r i v a t e v o i d i n s e r t S o r t e d ( Comparable o , i n t l a s t )
{
i f ( isEmpty ( ) ) data [ 0 ] = o ;
else
{
int i = last ;
w h i l e ( ( i >=0)&&(d a t a [ i ] . compareTo ( o ) > 0 ) )
{
d a t a [ i +1] = d a t a [ i ] ;
i −−;
}
d a t a [ i +1]=o ;
}
}
public void i n s e r t i o n S o r t ()
{
f o r ( i n t i = 1 ; i<c o u n t ; i ++)
{
i n s e r t S o r t e d ( d a t a [ i ] , i −1);
}
}
p u b l i c Tree l i s t 2 T r e e ( )
{
T r e e s o r t T r e e = new T r e e ( ) ;
L i s t E l e m e n t d = head ;
w h i l e ( d!= n u l l )
{
sortTree . i n s e r t (d . f i r s t ( ) ) ;
d = d. rest ();
}
return sortTree ;
}
Split the vector in two parts: the first half and the second half.
Sort the first half and Sort the second half.
I This is done by doing the merge sort on the first half and the second
half.
I If a vector has no elements or one element, the sorting is trivial.
Merge both sorted halves together.
sorted sequence
1 2 2 3 4 5 6 7
merge
2 4 5 7 1 2 3 6
merge merge
2 5 4 7 1 3 2 6
5 2 4 7 1 3 2 6
initial sequence
return result ;
}
s t a t i c V e c t o r m e r g e S o r t A u x ( V e c t o r v , i n t from , i n t t o )
{
i f ( t o > from )
{
i n t h a l f = ( from + t o ) / 2 ;
V e c t o r v1 = m e r g e S o r t A u x ( v , from , h a l f ) ;
V e c t o r v2 = m e r g e S o r t A u x ( v , h a l f +1 , t o ) ;
V e c t o r r e s u l t = merge ( v1 , v2 ) ;
return result ;
}
else
{
V e c t o r r e s u l t = new V e c t o r ( ) ;
r e s u l t . a d d L a s t ( v . g e t ( from ) ) ;
return result ;
}
}
Theorem 14
The time complexity of merge sort is O(n log n).
Proof.
The number of comparisons in merge sort is given by
T (n) = 2T (n/2) + n − 1
Hence, T (n) = aT (n/b) + f (n) with a = 2, b = 2 and f (n) = n − 1.
According to theorem 8, the solution to this recurrence relation is
Plog n/ log b i
a f (n/b i ) = log
P n i i
given by T (n) = i=0 i=0 2 (n/2 − 1).
log
Xn
T (n) = 2i (n/2i − 1)
i=0
log
Xn
= (n − 2i )
i=0
= n(log n + 1) − (2n − 1)1
= n log n − n + 1
= O(n log n)
Plog n
because i=0 2i = 20 + 21 + ... + 2log n = 1 + ... + n/2 + n = 2n − 1
It depends . . .
In Comparison Sort (i.e. determining the sorted order by comparing
elements), we cannot do better.
Why is this the lower bound on comparison sorting?
This slide seems to imply that one can sort without comparing
elements ?!?!?
1:2
≤ >
2:3 1:3
≤ > ≤ >
〈1,2,3〉 1:3 〈2,1,3〉 2:3
≤ > ≤ >
〈1,3,2〉 〈3,1,2〉 〈2,3,1〉 〈3,2,1〉
Theorem 15
Any decision tree that sorts n elements has a height of Ω(n log n).
Proof.
Consider a tree of height h that sorts n elements. Since there are n!
permutations of n elements, each representing a distinct sorted order,
the tree must have at least n! leaves.
Since a binary tree of height h has no more than 2h leaves, we have
that n! ≤ 2h and hence:
h ≥ log(n!)2 ≥ log(n/e)n = n log n − n log e = Ω(n log n)
2
√
Stirling’s formula: n! ≈ (n/e)n 2πn
Lubos Omelina, Bart Jansen Algo Data 2023-2024 257 / 259
Beyond Comparison Sort
We keep a vector of length K that will hold the number of times each
number occurs in the sequence.
We need to traverse the entire vector and update the counts.
From the counts, we compute the positions in the sorted vector of the
new element.
We traverse the vector once more and copy all elements to these
computed positions.
O(n + k) !!!