0% found this document useful (0 votes)
6 views113 pages

04 - Lists

introduzione alle Liste

Uploaded by

Sora
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
6 views113 pages

04 - Lists

introduzione alle Liste

Uploaded by

Sora
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 113

Algoritmi e

Programmazione
LISTS
Goal
This lecture aims at presenting Lists as ADT,
focusing first on some typical operations and then
on possible implementation.
Prerequisites
Lectures:
◦ Introduction to ADT
Outline
Lists
Operations on lists
Possible implementations of lists
Sentinel value
Double and multiple lists
Outline
Lists
Operations on lists
Possible implementations of lists
Sentinel value
Double and multiple lists
Lists
Lists are a particularly flexible ADT:
◦ allows the insertion, search and deletion of any element
◦ its dimensions can be dynamically changed according to specific needs.
Array vs List
Arrays store elements in contiguous memory
locations, resulting in easily calculable
addresses for the elements stored and this
allows faster access to an element at a
specific index
Linked lists are less rigid in their storage
structure and elements are usually not stored
in contiguous locations, hence they need to
be stored with additional tags giving a
reference to the next element
Array vs List
Array List
Data can exist at scattered (non-contiguous)
Data stored in a contiguous block of memory.
addresses;
Size cannot be altered at runtime. Size can be changed at runtime
Memory allocation at compile time Memory allocation at runtime
In list, each element requires extra space for the
In vector, each element only requires the space
node which holds the element, including pointers
for itself only.
to the next (and previous elements) in the list.
Supports sequential access, so an element can be
Supports random access, so an element can be
accessed only sequentially by going through each
directly accessed using the index.
element until reaching the required element
Array vs List
Array List
Elements are not independent as they contain a
Elements are independent of each other
pointer to the next (previous) node of the list
Insertion is cheap no matter where in the list it
Insertion could be costly.
occurs.
Deletion at the head or end of the vector needs Deletion is cheap no matter where in the list it
constant time but for the rest it is O(n). occurs.
Iterators become invalid if elements are added to Iterators are valid if elements are added to or
or removed from the vector. removed from the list.
Lists
A list is a data structure based on the use of pointers and dynamic memory allocation /
deallocation. It allows greater flexibility in the use of memory than other data structures (e.g.,
arrays) at the cost of lower efficiency.

1 2 5 3 4 1 1 1 1 1
array list
Introduction
Lists: A sequence of zero or more elements of
the same type:
a1, a2,…, an n≥0
Definitions
if n = 0 the list is called empty list
a1 is the first element or head of the list
an is the last element or tail of the list
ai precedes ai + 1 Ɐi: 1 ≤ i <n
ai follows ai-1 Ɐi: 1 <i ≤ n
The element ai is in the i-th position
In the following, the meaning of the term position will depend both on the context in which it is
inserted and on the specific implementation of the list considered.
It may be synonymous with index, address, pointer, ...
Definitions
It is convenient to assume the existence of a position after the last item on the list.
We will assume that the function
eol(L)
returns the value of that position.
Outline
Lists
Operations on lists
Possible implementations of lists
Sentinel value
Double and multiple lists
Outline
The operations on a list are:
◦ insert(x, p, L)
◦ delete(p, L)
◦ locate(x, p , L)
◦ retrieve(p, L)
◦ next(p, L), previous(p, L)
◦ makenull(L)
◦ first(L)
insert(x, p, L)
Inserts the element x at position p, moving all subsequent ones one position ahead.
Returns:
◦ OK if the operation completed successfully
◦ ERROR otherwise
delete(p, L)
Deletes the element at position p, moving back of one position all the elements after p
Returns:
◦ OK if the operation completed successfully
◦ ERROR otherwise
locate(x, p, L)
Returns the position of the first occurrence of x following the position p; if no such position
exists, it returns eol(L)
retrieve(p, L)
Returns the element that is in position p; if this element does not exist, it returns ERROR
next(p, L)
previous(p, L)
Return the position that follows or precedes position p, respectively
If p ∉ [1, n], returns ERROR

Examples
◦ next(n,L) = eol(L)
◦ next(eol(L),L) = ERROR
◦ previous(1,L) = ERROR
makenull(L)
Converts the list L into an empty list and returns eol(L)
first(L)
Returns the first position in the list L.
If L is an empty list, it returns eol(L).
Outline
Lists
Operations on lists
Possible implementations of lists
Sentinel value
Double and multiple lists
Implementation of lists
Different implementations are possible.
The main distinction concerns the position of the next element, which can be
◦ Implicit:
◦ using vectors
◦ Explicit:
◦ using pointers
◦ using indexes.
Implementation using vectors
The elements of the list are stored in contiguous cells of a vector:
◦ cell i contains the element located at position i
◦ the position of the next element is obviously i + 1

A pointer to the last occupied cell is required.


Implementation using vectors
a1
a2
.
.
.

last an
.
.
.

maxlength
Implementation using vectors
Advantages and disadvantages:
◦ The vector must be sized according to the maximum number of elements the list has to contain. This
value must be known and specified in advance.
◦ This limitation can be easily overcome in those programming languages, such as C, which allow
dynamically allocating the space occupied by vectors.
◦ The insertion and deletion of any element requires the movement of all subsequent elements.
◦ Only the insertions and deletions at the end of the list are particularly easy
◦ It is used when the number of insertions and deletions in a given position is negligible with respect to
the number of these operation executed at the end of the list
Implementation using pointers
The list is implemented through a set of structs: the i-th struct contains both the i-th element of
the list and a pointer to the (i + 1)-th struct.
A variable (header) is required that points to the first element of the list.
Each item is allocated/deallocated separately
Each element is linked to the others (and therefore accessible) through pointers
Implementation using pointers
header

Data Data Data

NULL
Implementation using pointers
header
A struct contains data and
the pointer to the next
element (next)

Data Data Data

NULL
Implementation using pointers
header
Pointer to the first
element of the list
(header)

Data Data Data

NULL
Implementation using pointers
/* defining a list */
typedef struct list_el{
int code;
char *name; Data
char *surname;
struct list_el *succ; Pointer to the next element
}list_el;

...

/* defining the header */


list_el *header = NULL;
Typedef
list_el has been made explicit twice for
completeness of the definition. In fact, the /*New type definition*/
typedef syntax requires the definition of the
struct list_el{
following elements:
int code;
typedef <existing_type> <alias>
char * name;
The first list_el defines the existing type we char *surname;
want to assign an alias to, i.e., the second struct list_el* succ;
list_el.
};
For simplicity, we have referred to the same type /*Assign to an alias*/
name as an alias. The version used in the lists file is
typedef struct list_el list_el;
actually a compact version of:
Typedef
The notation without the first list_el is also valid, but in this case, we are dealing with the
"anonymous" version of the typedef, which has some constraints in the use of pointers. In fact, if
you try to delete the first list_el and start the program, errors will appear during execution.
Insertion
The insertion consists of:
◦ Create a new structure and fill it
◦ Link it with the list

There are three different insertion conditions:


◦ Insert at the beginning
◦ Insert at the middle
◦ Insert at the end
Insert at the beginning
header

Data Data Data

NULL
Insert at the beginning
t header

Data Data Data

NULL
Dati
p
1. Create a new item
2. Store the Data
Insert at the beginning
t header 3. Copy the reference
pointed by header in the
next field of the new struct

Data Data Data Data

NULL
p
Insert at the beginning
t header 4. Update the header with
the address of the new
struct

Data Data Data Data

NULL
p
Insert at the beginning
t header

Data Data Data Data

NULL
Insert at the beginning
int insert_head(list_el **t,int new_val, char *new_name, char *new_surname){

// 1. Create a new item


list_el *p;
p =(list_el *)malloc(sizeof(list_el));

if (p==NULL)
return (-1);

// 2. Store the data


p->code = new_val;
p->name = strdup(new_name);
p->surname = strdup(new_surname);
Insert at the beginning
// 3. Copy the reference pointed by header in the next field of the new struct
p->succ=*t;

// 4. Update the header with the address of the new struct


*t=p;

return (0);
}
Insert at the beginning
int main() {
/* defining the header */
list_el *header = NULL;

int ret, val;


char new_name[10],new_surname[10];

printf ("Specify the element to insert:\n");


scanf("%d %s %s",&val, new_name, new_surname);
ret = insert_head(&header, val, new_name, new_surname);
if(ret==-1)
printf("Error\n");
else
printf("Element inserted\n");

...
Why a double pointer?
There are two ways to pass parameters in C: Pass by Value, Pass by Reference.
Pass by Value: Pass by Value, means that a copy of the data is made and stored by way of the
name of the parameter. Any changes to the parameter have no affect on data in the calling
function.
Pass by Reference: A reference parameter "refers" to the original data in the calling function.
Thus any changes made to the parameter are also made to the original variable.
There are two ways to make a pass by reference parameter:
◦ Arrays
◦ Pointers
Using a double pointer

header
list_el* Data

main
Using a double pointer
insert_head(&header, …)

header
list_el* Data

list_el

t
pass the pointer list_el**

main insert_head
Using a double pointer
list_el *p;
p =(list_el*)malloc(sizeof(list_el));

header
list_el* Data

list_el

t p
list_el** list_el*
Data
main insert_head NULL
Using a double pointer
p->succ=*t

header
list_el* Data

list_el

t p
list_el** list_el*
Data
main insert_head
Using a double pointer
*t=p;

header
list_el* Data

list_el

t p
list_el** list_el*
Data
main insert_head
Using a double pointer
return(0);

header
list_el*
Data Data

list_el list_el
main
Using a single pointer

header
list_el* Data

main
Using a single pointer
insert_head(header, …)

header
list_el* Data

list_el t
Data

copy the pointer list_el*

main insert_head
Using a single pointer
list_el *p;
p =(list_el*)malloc(sizeof(list_el));

header
list_el* Data p
list_el*
Data
list_el t NULL
Data
list_el*

main insert_head
Using a single pointer
p->succ=t

header
list_el* Data p
list_el*
Data
list_el t
Data
list_el*

main insert_head
Using a single pointer
t=p;

header
list_el* Data p
list_el* Data

list_el t
list_el*
Data

main insert_head
Using a single pointer
return(0);

header
list_el* Data

main
Insert at the end
header

Data Data Data

NULL
Insert at the end
t header

Data Data Data

NULL

Data 1. Create a new item


p 2. Store the Data
NULL
Insert at the end
t header
3.1.Scan thealist
Create new toitem
the last
element
2. Store the Data

Data Data Data

NULL

Data
p
NULL
Insert at the end
t header

q
Data Data Data

Data 4. Update the next field


p of the last item in the list
NULL
Insert at the end
int insert_tail(list_el **t,int new_val, char *new_name, char *new_surname){
//1. Create a new item
list_el *p, *q;
p =(list_el *)malloc(sizeof(list_el));

if (p==NULL)
return (-1);

// 2. Store the Data


p->code = new_val;
p->name = strdup(new_name);
p->surname = strdup(new_surname);
p->succ = NULL;
Insert at the end
// 3. Scan the list to the last element
q= *t;
while(q->succ != NULL)
{
q = q->succ;
}

// 4. Update the next field of the last item in the list


q->succ = p;
return(0);
}
Insert in the middle
header

Data Data Data

NULL
Insert in the middle
t header

Data Data Data

NULL

Dati 1. Create a new item


p 2. Store the Data
Insert in the middle
t header
3. Check for head
insertion

Data Data Data

NULL

Dati
p
Insert in the middle
t header
4. Scan the list to the
desired position
Insertion position

Data Data Data

NULL

Dati
p
Insert in the middle
t header

Data Data Data

NULL

Data 4. Update the next field


p of the new element
Insert in the middle
t header

q p

Data Data Data Data

NULL

5. Update the next field


of the previous element
to the insertion point
Insert in the middle
int insert_order(list_el **t, int new_val, char *new_name, char *new_surname){
list_el *p, *q;
q = *t;

// 1. Create a new item


p=(list_el *)malloc(sizeof(list_el));
if (p==NULL)
return (-1);

// 2. Store the data


p->code = new_val;
p->name=strdup(new_name);
p->surname=strdup(new_surname);
Insert in the middle
// 3. Check for head insertion
if( (q == NULL) || (q->code > new_val))
{
/* insert head */
p->succ=*t;
*t=p;
return (0);
}
Insert in the middle
// 4. Scan the list to the desired position
while(q->succ != NULL){
if(q->succ->code > new_val){
/* insert in the middle */

//4. Update the next field of the new element


p->succ=q->succ;

//5. Update the next field of the


//previous element to the insertion point
q->succ=p;
return (0);
}
q = q->succ;
}
Insert in the middle
/* insert tail */
//4. Update the next field of the new element
p->succ = NULL;

//5. Update the next field of the previous element to the insertion point
q->succ = p;
return(0);

}
Delete
The deletion operations are executed to remove an element from the list
Delete
header

Data Data Data

NULL
Delete
t header 1. Check for
head deletion

Data Data Data

NULL
Delete
t header
2. Scan the list to the
desired position

Data Data Data

NULL

q p
Item to delete
Delete
header p
t Data

Data Data

NULL

q 3. Updates the next field of


the previous element
Delete
header p
t Data

NULL
4. Free the memory

Data Data

NULL

q
Delete
int delete (list_el **t, int val){
list_el *p, *q;
q = *t;

if (q==NULL) /* empty list*/


return (-1);

// 1. Check for head deletion


if (q->code == val){
/* delete head */
free (q->name);
free (q->surname);
*t=q->succ;
free (q);
return (0);
}
Delete
//2. Scan the list to the desired position
while(q->succ != NULL)
{
if(q->succ->code == val){
/* delete element in the middle or the tail */

/* move to the next element of the list */


p = q->succ;

//3. Update the next field of the previous element


q->succ=q->succ->succ;
Delete
//4. Free the memory
free (p->name);
free (p->surname);
free (p);
return (0);
}
q = q->succ;
}
}
Array vs List
Accessing elements
Array supports random access, so an element can be directly accessed using the index.

List supports sequential access, so an element can be accessed only sequentially by going
through each element until reaching the required element
/* defining an array */

Access to an element int array[5] = {19, 10, 8, 17, 9};

Array
// Access to the second element
...
printf("%d",array[1]);
Access to an element /* defining a list */
typedef struct list_el{
List int data;
// Access to the second element struct list_el *succ;
... struct list_el *prev;
while(q->succ != NULL) }list_el;
{
if(q->data == val){
// Element found do somethings ...
printf("%d", p->code);
}
q = q->succ; // Move to the next element
q = q->prev; // Move to the previous element
}
Implementation using pointers
Advantages and disadvantages:
◦ It is not necessary to know in advance the number of elements
◦ The space occupied is determined only by the number of elements
◦ Inserting and deleting requires only simple changes to the pointers
◦ Pointers take up space.
Implementation using indices
In those languages in which pointers are not available, it is possible to emulate the behavior of
linked lists by using a vector of structs: the second field of each struct contains the position in
the vector of the next element.

It is necessary to explicitly manage a list of free positions (free list)


Two additional variables are needed:
◦ one for the index of the cell that contains the first element of the list
◦ one for the index of the first cell of the free list.
Implementation using indices
0 d 6
header 4
1 3
2 c 0
available 8
3 5
4 a 7
5 -1
6 e -1
7 b 2
8 1
Implementation using indices
0 d 6
header 4
1 3
2 c 0
available 8
3 5
4 a 7
5 -1
6 e -1
Data stored in the element
7 b 2
8 1
Implementation using indices
0 d 6
header 4
1 3
2 c 0
available 8
3 5
4 a 7
5 -1
6 e -1
Index to the next element
7 b 2
8 1
Outline
Lists
Operations on lists
Possible implementations of lists
Sentinel value
Double and multiple lists
Sentinel values
Sentinel: Dummy elements added at the top
and/or at the end of the lists in order to speed
up some operations. No data are stored in
these element
Sentinels
In this regard, a distinction is made between:
◦ sentinels at the beginning: used to avoid changing the header in the operations of insertion and
deletion
◦ sentinels at the end: used to avoid executing two tests per element during search operations
Sentinels
header

Data Data
NULL

sentinel sentinel
Sentinels: unordered lists
In this case two sentinels are usually used (one in the tail and one in the head)
The sentinels allow to simplify the code (the cases of insertion / cancellation at the head and in
the tail no longer have to be considered separately).
Sentinels: ordered lists
In this case the two sentinels contain the minimum and maximum values that can be stored. In
this way:
◦ The code is simplified (you no longer have to consider separately the cases of insertion / cancellation at
the top and at the bottom)
◦ It makes execution faster, as the end-of-list test can be eliminated.
Sentinels
header

MIN_INT Data Data MAX_INT


NULL
Example of sentinel at the end
Searching in ordered lists: memorize in the sentinel a value higher than all the elements of the
list
Searching in unordered lists: memorize in the sentinel a value equal to the one to be found; the
scan ends in any case when an element equal to the one searched for is found. If this coincides
with the sentinel, the value is not in the list.
Outline
Lists
Operations on lists
Possible implementations of lists
Sentinel value
Double and multiple lists
Doubly linked list
In the cases in which it is necessary to frequently scan the list in the two directions of travel, it is
advisable to use a structure in which each element contains two pointers: one to the next
element and one to the previous one.
Two external pointers are required:
◦ one at the head of the list (header)
◦ one at the end of the list (tail)
Doubly linked list
header tail

Data Data Data Data


Insert in the middle
header tail

Data Data Data Data


NULL

prev_node
Insert in the middle 1. Create a new item
2. Store the Data

new
header node Data tail
NULL

NULL

Data Data Data Data


NULL

prev_node
Insert in the middle 3. Update the next
pointer of the new node

new
header node Data tail

NULL

Data Data Data Data


NULL

prev_node
Insert in the middle
new
header node Data tail

4. Update the next


pointer of the previous NULL
node

Data Data Data Data


NULL

prev_node
Insert in the middle
new
header node Data tail

5. Update the previous


pointer of the new
node

Data Data Data Data


NULL

prev_node
Insert in the middle
header tail

new node

Data Data Data Data Data


NULL

6. Update the previous


prev_node pointer of node after
the new node
Multiple lists
A multi-linked list is a special type of list that contains two or more logical key sequences.
In a multi-linked list, each node can have N number of pointers to other nodes. A multi-linked list
is generally used to organize multiple orders of one set of elements.

typedef struct node {


int data;
vector<struct node*> pointers;
} Node;
Multiple lists
Some use cases of a multi-linked list are:
◦ Multiple orders of one set of elements
◦ Representation of a sparse matrix
◦ List of List
Multiple lists
Multiple orders of one set of elements
Alessandro Carlo
Suppose a task in which a list in multiple orders
(e.g., age and name ) is required. It is possible to
define a Node that has two references, an age 32 29
pointer and a name pointer.
NULL
by name
by age
Federica Davide
NULL
25 27
Multiple lists
Representation of a sparse matrix col1 col2 col3
A sparse matrix is such a matrix
that has few non-zero values.
If we use a normal array to store
such a matrix, it will end up row1 5 2
wasting lots of space.

row2 3
5 0 2
0 3 0
1 0 0 row3 1
0 10 9
row4 10 9
References
◦ A.V. Aho, J.E. Hopcroft, J.D. Ullman:
“Data Structures and Algorithms,”
Addison Wesley, Reading MA (USA), 1983, pp 37-74
◦ J. Esakow. T. Weiss
“Data structure: an advanced approach using C,”
Prentice Hall, Englewood Cliffs NJ (USA), 1982, pp 54-237
◦ C.J. Van Wyk:
“Data Structures and C Programs,” Addison Wesley, Reading
MA (USA), 1988, pp 49-128
References
◦ R. Sedgewick:
“Algorithms in C,”
Addison Wesley, Reading MA (USA), 1990, pp 15-34
◦ E. Horowitz, S. Sahni:
“Fundamentals of Computer Algorithms,”
Pittman, London (UK), 1978, pp 65-202
◦ N. Wirth:
“Algorithms + Data Structures = Programs,”
Prentice Hall, Englewood Cliffs NJ (USA), 1976, pp 162-188
Riferimenti
Queste slide sono una rielaborazione del materiale realizzato dal prof. P. Prinetto per il corso di
Algoritmo e Programmazione A.A. 2020/2021 presso il Politecnico di Torino.

You might also like