Data Structures: Lists

You might also like

Download as pdf or txt
Download as pdf or txt
You are on page 1of 40

Data Structures

Lists
Hikmat Farhat

March 7, 2013
Hikmat Farhat

Data Structures

March 7, 2013

1 / 39

Introduction

A list is a sequentially organized set of elements.


Generalization of arrays.
many kinds of lists:
I
I
I

singly linked lists.


doubly linked lists.
circular lists.

Hikmat Farhat

Data Structures

March 7, 2013

1 / 39

Operations on lists

a list object should implement the following operations


I
I
I
I
I
I
I

create: create the list.


push back: add an item to the end of the list.
insert: insert an item in the list at an arbitrary position.
pop back: delete an item from the end of the list
erase: delete an item from a certain position or a range.
empty: test wether the list is empty or not.
find: search for the existance of an element in the list.

Hikmat Farhat

Data Structures

March 7, 2013

2 / 39

Vector Implementation

One data structure provide by the Standard Template Library (stl) is


the vector class.
First we will use the stl vector class and see how it implements the list
ADT.
A vector implementation is a suitable implementation when
I
I

Elements are added/deleted only from the end of the list


Finding element at position k is used often and must be fast.

It is fast in access elements at random positions


More importantly: a vector implementation is not suitable when we
need to add/remove the front element.

Hikmat Farhat

Data Structures

March 7, 2013

3 / 39

Iterators

There are many times where we need to iterate through the


elements of a list.
We do this regardless how the list is implemented.
A convenient way of doing this is to use iterators
An iterator is simply a pointer to an element of the list
An iterator supports increments methods.

Hikmat Farhat

Data Structures

March 7, 2013

4 / 39

Using Iterators to print all elements of vector


#i n c l u d e <i o s t r e a m >
#i n c l u d e <v e c t o r >
#i n c l u d e <s t r i n g >
u s i n g namespace s t d ;
i n t p r i n t ( v e c t o r <s t r i n g > & v ) {
/ add
v . push
v . push
v . push

s t r i n g e l e m e n t s /
back ( f i r s t s t r i n g ) ;
back ( second s t r i n g ) ;
back ( t h i r d s t r i n g ) ;

v e c t o r <s t r i n g > : : i t e r a t o r

itr ;

f o r ( i t r =v . b e g i n ( ) ; i t r != v . end ( ) ; i t r ++)
cout << i t r <<e n d l ;
return 0;
}
Hikmat Farhat

Data Structures

March 7, 2013

5 / 39

Using Iterators to insert and remove elements


v e c t o r <s t r i n g > : : i t e r a t o r i t r ;
/ i n s e r t an e l e m e n t between
f i r s t and s e c o n d e l e m e n t s /
i t r =v . b e g i n ( ) ;
i t r ++;
v . i n s e r t ( i t r , between 1 & 2 ) ;
/ e r a s e t h e e l e m e n t b e f o r e t h e l a s t /
/ t h e l a s t e l e m e n t i s a t v . end () 1 /
i t r =v . end ( ) ;
i t r =2;
v . erase ( i t r );
f o r ( i t r =v . b e g i n ( ) ; i t r != v . end ( ) ; i t r ++)
cout << i t r <<e n d l ;
return 0;

Listing 2: insertion and deletion


Hikmat Farhat

Data Structures

March 7, 2013

6 / 39

STL list class

A different implementation of the list ADT is the STL list


Unlike the vector class which uses an array for internal storage
the list is implemented as a linked list.
Unlike vector it provides an efficient implementation of push front
and pop front methods.
Unlike vector it does NOT provide an efficient implementation of
element at position k.

Hikmat Farhat

Data Structures

March 7, 2013

7 / 39

Using STL list


#i n c l u d e <i o s t r e a m >
#i n c l u d e < l i s t >
u s i n g namespace s t d ;
i n t main ( i n t a r g c , c o n s t c h a r a r g v [ ] ) {
l i s t <s t r i n g > m y l i s t ;
m y l i s t . p u s h f r o n t ( f i r s t element ) ;
m y l i s t . push back ( second element ) ;
m y l i s t . p u s h f r o n t ( t h i r d element ) ;
l i s t <s t r i n g > : : i t e r a t o r

itr ;

f o r ( i t r =m y l i s t . b e g i n ( ) ; i t r != m y l i s t . end ( ) ; i t r ++)
cout << i t r <<e n d l ;
return 0;
}
Hikmat Farhat

Data Structures

March 7, 2013

8 / 39

Vector interface
#i f n d e f l i s t v e c t o r V e c t o r h
#d e f i n e l i s t v e c t o r V e c t o r h
t e m p l a t e <typename O b j e c t>
c l a s s Vector
{
private :
int theSize ;
int theCapacity ;
Object o b j e c t s ;
public :
e x p l i c i t V e c t o r ( i n t i n i t S i z e =0);
Vector ( const Vector & rhs ) ;
Vector ( ) ;
c o n s t V e c t o r & o p e r a t o r= ( c o n s t V e c t o r & r h s ) ;
void r e s i z e ( i n t newCapacity ) ;
Object & operator [ ] ( i n t index ) ;
b o o l empty ( ) c o n s t ;
i n t s i z e ( ) c o n s t ; / / how many e l e m e n t s ?
i n t capacity ( ) const ;// t o t a l capacity
void push back ( const Object & x ) ;
void pop back ( ) ;
typedef Object i t e r a t o r ;
i t e r a t o r begin ( ) ;
i t e r a t o r end ( ) ;
};

Hikmat Farhat

Data Structures

March 7, 2013

9 / 39

Adding an element to Vector


t e m p l a t e <typename O b j e c t>
v o i d V e c t o r<O b j e c t >:: p u s h b a c k ( c o n s t O b j e c t & x )
{
i f ( t h e S i z e == t h e C a p a c i t y )
r e s i z e ( 2 theCapacity );
o b j e c t s [ t h e S i z e++ ] = x ;
}

In the code above sometimes we need to call the expensive resize .


t e m p l a t e <typename O b j e c t>
v o i d V e c t o r<O b j e c t >:: r e s i z e ( i n t n e w C a p a c i t y )
{
i f ( newCapacity < t h e S i z e )
return ;
Object oldArray = o b j e c t s ;
o b j e c t s = new O b j e c t [ n e w C a p a c i t y ] ;
f o r ( i n t k = 0 ; k < t h e S i z e ; k++ )
objects [ k ] = oldArray [ k ] ;
theCapacity = newCapacity ;
delete [ ] oldArray ;
}

Hikmat Farhat

Data Structures

March 7, 2013

10 / 39

Vector Implementation
t e m p l a t e <typename O b j e c t>
V e c t o r<O b j e c t >:: V e c t o r ( i n t i n i t S i z e )
: theSize ( i n i t S i z e ) , theCapacity ( i n i t S i z e )
{ o b j e c t s=new O b j e c t [ t h e C a p a c i t y ] ; }
t e m p l a t e <typename O b j e c t>
V e c t o r<O b j e c t >:: V e c t o r ( c o n s t V e c t o r<O b j e c t>& r h s ) : t h e S i z e ( r h s . s i z e ( ) ) ,
theCapacity ( rhs . theCapacity )
{
o b j e c t s = new O b j e c t [ c a p a c i t y ( ) ] ;
f o r ( i n t k = 0 ; k < s i z e ( ) ; k++ )
objects [ k ] = rhs . objects [ k ] ;
}
t e m p l a t e <typename O b j e c t>
V e c t o r<O b j e c t >:: V e c t o r ( )
{ d e l e t e [ ] o b j e c t s ;}
t e m p l a t e <typename O b j e c t>
c o n s t V e c t o r<O b j e c t> & V e c t o r<O b j e c t >:: o p e r a t o r= ( c o n s t V e c t o r<O b j e c t>& r h s )
i f ( t h i s != &r h s )
{
delete [ ] objects ;
t h e S i z e=r h s . s i z e ( ) ;
t h e C a p a c i t y=r h s . c a p a c i t y ( ) ;
o b j e c t s=new O b j e c t [ c a p a c i t y ( ) ] ;
f o r ( i n t k =0; k<s i z e ( ) ; k++)
o b j e c t s [ k ]= r h s . o b j e c t s [ k ] ;
}
return this ;
}
Hikmat Farhat

Data Structures

March 7, 2013

11 / 39

Vector Implementation
t e m p l a t e <typename O b j e c t>
v o i d V e c t o r<O b j e c t >:: r e s i z e ( i n t n e w C a p a c i t y )
{
i f ( newCapacity < t h e S i z e )
return ;
Object oldArray = o b j e c t s ;
o b j e c t s = new O b j e c t [ n e w C a p a c i t y ] ;
f o r ( i n t k = 0 ; k < t h e S i z e ; k++ )
objects [ k ] = oldArray [ k ] ;
theCapacity = newCapacity ;
delete [ ] oldArray ;
}
t e m p l a t e <typename O b j e c t>
O b j e c t & V e c t o r<O b j e c t >:: o p e r a t o r [ ] (
{ return objects [ index ] ; }

int index )

t e m p l a t e <typename O b j e c t>
b o o l V e c t o r<O b j e c t >:: empty ( ) c o n s t
{ r e t u r n s i z e ( ) == 0 ; }

Hikmat Farhat

Data Structures

March 7, 2013

12 / 39

inserting elements in the vector is a costly operation because a whole


portion of the array needs to be copied.
the worst case happens when the insertion is done on the front.
This is why the vector class supports inserts at the end only.
Even in that case it becomes costly when we run out of space and we
need to increase the storage
check the method resize(int ) in our implementation of vector.

Hikmat Farhat

Data Structures

March 7, 2013

13 / 39

Why a copy constructor?

A copy constructor is used by the compiler in the following cases


I

When an argument is passed by value, a copy of the argument should


be made
when a function returns a local object (not a pointer or reference to it),
an anonymous and temporary copy should be made to be returned to
the caller.
when a object is initialized as: Type obj(initial obj) then obj is made as
a copy of initial obj using the copy constructor.

the compiler usually supplies a default copy constructor.


when there is dynamic memory allocation this default copy
constructor does not work.

Hikmat Farhat

Data Structures

March 7, 2013

14 / 39

why a destructor?

When an object goes out of scope it is destroyed.


This is done by calling the destrcutor method.
if no destrcutor method is specified the compiler uses a default one.
If memory was allocating dynamically through a pointer, the default
destructor destroys the pointer and NOT the memory that the pointer
points to.
therefore when memory is allocated dynamically it should be
destroyed manually through the destructor.

Hikmat Farhat

Data Structures

March 7, 2013

15 / 39

Linked lists

We need to be able to change the size of the list dynamically. this is


not possible with an array implementation.
the implementation of insert and delete is very inefficient.
An implementation that satisfies the above two condition is a linked
list structure:
I
I

Elements do not need to be store consecutively.


But then we need to link the elements together.

Hikmat Farhat

Data Structures

March 7, 2013

16 / 39

A linked list is a sequence of nodes


Each node has two parts:
I
I
I
I
I

Data part: information stored in that particular node.


Next part: a link (pointer) to the next node.
We only need to have a pointer to the first node.
the next part of the last node is null.
In this case we use a doubly linked list where each node has a next and
prev pointers.

Hikmat Farhat

Data Structures

March 7, 2013

17 / 39

Sentinel Node

For convenience, we use empty nodes, called sentinel, for head and
tail.
The first element in the list is the node just after the head.
The last element in the list is the node just before the tail.

Hikmat Farhat

Data Structures

March 7, 2013

18 / 39

Operations on Linked Lists

We would liked to have the following operations implemented on linked


lists.
create a list.
test if the list is empty.
display the list.
search for an item in the list.
delete an item from the list.
insert an item into the list.

Hikmat Farhat

Data Structures

March 7, 2013

19 / 39

Most operations will make use of an iterator


In this case an iterator will be a node with extra operations
Like itr++, itr, itr=, itr!=, *itr
All elements of the list are also accessed through iterators
{
Object data ;
Node p r e v ;
Node n e x t ;
Node ( c o n s t O b j e c t & d=O b j e c t ( ) , Node p=NULL ,
Node n=NULL)
: data ( d ) , prev ( p ) , next ( n ){}
};

Hikmat Farhat

Data Structures

March 7, 2013

20 / 39

Iterator Class
class iterator{
protected :
Node c u r r e n t ;
public :
i t e r a t o r (){}
i t e r a t o r ( Node p ) : c u r r e n t ( p){}
Object & o p e r a t o r ()
{ r e t u r n c u r r e n t >d a t a ; }
i t e r a t o r & o p e r a t o r ++(){
c u r r e n t=c u r r e n t >n e x t ;
return this ;
}
i t e r a t o r & o p e r a t o r ++( i n t i n ){
c u r r e n t=c u r r e n t >n e x t ;
return this ;
}
i t e r a t o r & o p e r a t o r (){
c u r r e n t=c u r r e n t >p r e v ;
return this ;
}
i t e r a t o r & o p e r a t o r (i n t i n ){
c u r r e n t=c u r r e n t >p r e v ;
return this ;
}
b o o l o p e r a t o r ==( c o n s t
i t e r a t o r & rhs ) const
{ r e t u r n c u r r e n t==r h s . c u r r e n t ; }
b o o l o p e r a t o r !=( c o n s t i t e r a t o r &r h s ) c o n s t
{ r e t u r n ! ( c u r r e n t==r h s . c u r r e n t ) ; }
f r i e n d c l a s s L i s t <O b j e c t >;
};
Hikmat Farhat

Data Structures

March 7, 2013

21 / 39

Empty list

Since we always have sentinel nodes an empty list has two nodes:
head and tail

Hikmat Farhat

Data Structures

March 7, 2013

22 / 39

beginning and end


The beginning and end are usually used differently
for example
f o r ( i t r = l i s t . b e g i n ( ) ; i t r != l i s t . end ( ) ; i t r ++)

In the code above, begin() should return the first element (i.e the one
after head)
Whereas end should return tail thus
iterator
return
}
iterator
return
}

Hikmat Farhat

begin (){
i t e r a t o r ( head>n e x t ) ;
end ( ) {
iterator ( tail );

Data Structures

March 7, 2013

23 / 39

Inserting a Node

i t e r a t o r i n s e r t ( i t e r a t o r i t r , const Object & x )


{
Node p= i t r . c u r r e n t ;
t h e S i z e ++;
Node newNode=new Node ( x , p>p r e v , p ) ;
p>p r e v >n e x t=newNode ;
p>p r e v=newNode ;
r e t u r n i t e r a t o r ( newNode ) ;
}
Hikmat Farhat

Data Structures

March 7, 2013

24 / 39

Deleting a node

i t e r a t o r e r a s e ( i t e r a t o r i t r ){
Node p= i t r . c u r r e n t ;
i t e r a t o r r e t ( p>n e x t ) ;
p>p r e v >n e x t=p>n e x t ;
p>n e x t >p r e v=p>p r e v ;
delete p;
t h e S i z e ;
return ret ;
}

Hikmat Farhat

Data Structures

March 7, 2013

25 / 39

List Destructor

Every time we add a node to the list we allocate additional memory.


When the list is out of scope and need to be destroyed, dynamically
allocated memory is not destroyed automatically.
Therefore we need to provide an explicit destructor for the
dynamically allocated memory.
Similarly we need to provide a copy constructor and define an
assignment operator.

Hikmat Farhat

Data Structures

March 7, 2013

26 / 39

clear ();
d e l e t e head ;
delete t a i l ;
}
L i s t & o p e r a t o r =( L i s t &r h s )
{
pop front ();
}
Object & f r o n t ( )
void pop back ( )
{ e r a s e ( end ( ) ) ; }

Hikmat Farhat

Data Structures

March 7, 2013

27 / 39

Difference between vector and list

Vector
I
I

Insertion and deletion are (n)


Direct access is (1)

Linked list
I
I

Insertion and deletion are (1)


Direct access is (n)

Hikmat Farhat

Data Structures

March 7, 2013

28 / 39

Stack ADT

A stack is a list of elements where only the top element is accessible


Operations are
I
I

push to put a new element at the top


pop to remove the top element

Hikmat Farhat

Data Structures

March 7, 2013

29 / 39

Stack implementation

We can use linked list but since insertion and deletion is done only at
the top it is better to use an array
Since only the top of the stack is accessible, insertion and deletion is
done efficiently
We need the following operations top(),push(),pop()
The stack has :
I
I
I

capacity
top of stack
array of objects

Hikmat Farhat

Data Structures

March 7, 2013

30 / 39

Stack Implementation
t e m p l a t e <typename O b j e c t >
c l a s s Stack
{
private :
i n t topOfStack ;
int theCapacity ;
Object o b j e c t s ;
void r e s e r v e ( i n t newCapacity ) ;
public :
S t a c k ( i n t c a p a c i t y =0)
: theCapacity ( capacity ? capacity :16)
{ t o p O f S t a c k =1;
o b j e c t s=new O b j e c t [ t h e C a p a c i t y ] ;
}
Stack ( const Stack & rhs )
{ o p e r a t o r =( r h s ) ; }
Stack ( )
{ delete [ ] objects ;}
Hikmat Farhat

Data Structures

March 7, 2013

31 / 39

Stack Implementation
c o n s t S t a c k & o p e r a t o r= ( c o n s t S t a c k & r h s ) ;
int s i z e ( ) const {
r e t u r n t o p O f S t a c k +1;
}
v o i d push ( c o n s t O b j e c t & x ) {
i f ( t o p O f S t a c k== t h e C a p a c i t y 1 )
reserve ( 2 theCapacity + 1 );
o b j e c t s [++ t o p O f S t a c k ]= x ;
}
v o i d pop ( ) {
// c h e c k f o r e r r o r
t o p O f S t a c k ;
}
Object top (){
r e t u r n o b j e c t s [ topOfStack ] ;
}
};
Hikmat Farhat

Data Structures

March 7, 2013

32 / 39

Assignment Operator

t e m p l a t e <typename O b j e c t >
c o n s t Stack <O b j e c t > & Stack <O b j e c t > : : o p e r a t o r= ( c o n s t Stack
{
i f ( t h i s != &r h s )
{
delete [ ] objects ;
t o p O f S t a c k = r h s . s i z e ( ) 1;
theCapacity = rhs . theCapacity ;
o b j e c t s = new O b j e c t [ t h e C a p a c i t y ] ;
f o r ( i n t k = 0 ; k < s i z e ( ) ; k++ )
objects [ k ] = rhs . objects [ k ] ;
}
return this ;
}

Hikmat Farhat

Data Structures

March 7, 2013

33 / 39

Overflow
t e m p l a t e <typename O b j e c t >
v o i d Stack <O b j e c t > : : r e s e r v e ( i n t n e w C a p a c i t y )
{
i f ( n e w C a p a c i t y < t o p O f S t a c k+1 )
return ;
Object oldArray = o b j e c t s ;
o b j e c t s = new O b j e c t [ n e w C a p a c i t y ] ;
f o r ( i n t k = 0 ; k < t o p O f S t a c k +1; k++ )
objects [ k ] = oldArray [ k ] ;
theCapacity = newCapacity ;
delete [ ] oldArray ;
}

Hikmat Farhat

Data Structures

March 7, 2013

34 / 39

Stack Application: Postfix Calculator


regular expressions are called infix expressions:
17 + 3 5
is interpreted as:
17 + (3 5)
because has higher precedence than +.
Postfix expressions are easier to evaluate because we dont need to
remember precedence rules. The above in postfix notation is
3 5 17+

Hikmat Farhat

Data Structures

March 7, 2013

35 / 39

A postfix calculator can be implemented using a stack as follows


1
2

If a number is read then it is pushed on the stack.


when an operator is read then
1

2
3

the appropriate number of arguments (usually two) are poped from the
stack.
the operator is applied to the arguments
The results is pushed back onto the stack.

Example, evaluate 6 5 2 3 + 8 * + 3 + *

Hikmat Farhat

Data Structures

March 7, 2013

36 / 39

Queue ADT

The basic operations on a Queue are


1
2

Enqueue to add an element to the end of a list


Dequeue to return ( and remove) the front element of a list

This why sometimes it is called First in First Out (FIFO).

Hikmat Farhat

Data Structures

March 7, 2013

37 / 39

Array Implementation of Queue ADT

A queue can be implemented using an array by maintaining two


values
I
I

front that points to the first element


back that points to end of the queue (last+1).

Hikmat Farhat

Data Structures

March 7, 2013

38 / 39

front

Initially

After enqueue(7)

12

back
12
After dequeue()

Hikmat Farhat

back

front

back

After enqueue(12)

front
5

back

Data Structures

front

March 7, 2013

39 / 39

You might also like