You are on page 1of 50

Dades lineals en memria dinmica

Isidre Guix Miranda

Estructures de dades dinmiques

Estructures de dades dinmiques

Dades lineals en memria dinmica

ndex

Introducci ............................................................................................... Objectius ................................................................................................... 1. Assignaci dinmica de memria. Mecanismes en C ..................... 1.1. Aprofundiment en el tipus punter del llenguatge C ............... 1.1.1. Punters genrics ............................................................... 1.1.2. Els punters i lespecificador const .................................. 1.1.3. Importncia dels punters en la definici de taules ...... 1.1.4. Taules de punters ............................................................. 1.1.5. Punters a punters ............................................................. 1.1.6. Taula de punters a cadenes ............................................. 1.2. Assignaci dinmica de memria .............................................. 1.2.1. Funcions per a lassignaci dinmica de memria en C .............................................................. 1.2.2. Taules dinmiques en C ................................................... 1.2.3. Reassignaci de blocs de memria en C ........................ 2. Llistes .................................................................................................. 2.1. Llistes simplement encadenades .............................................. 2.2. Llistes simplement encadenades ordenades sense repetits ............................................................ 2.3. Llistes circulars o en anell ......................................................... 2.4. Llistes doblement encadenades ................................................ 3. Piles i cues ........................................................................................... 3.1. Piles .............................................................................................. 3.1.1. Implementaci del tipus de dada pila ............................ 3.1.2. Aplicacions de les piles .................................................... 3.2. Cues .............................................................................................. 3.2.1. Implementaci del tipus de dada cua ............................. 3.2.2. Aplicacions de les cues .....................................................

5 7 9 9 10 10 12 13 16 17 18 19 20 24 26 27 35 37 37 39 39 40 42 45 46 49

Estructures de dades dinmiques

Dades lineals en memria dinmica

Estructures de dades dinmiques

Dades lineals en memria dinmica

Introducci

En laprenentatge de la programaci estructurada i modular hem aprs a gestionar dades de diversos tipus: nhi ha de facilitades pel llenguatge de programaci i nhi ha de dissenyades per nosaltres mateixos. A ms, hem efectuat aquesta gesti en memria i tamb hem aprs com enregistrar les dades en suport extern amb la utilitzaci dels sistemes gestors de fitxers. Si pensem una mica en com desenvolupem un programa, ens adonarem que en el moment de dissenyar els algorismes, hem de fixar les variables que cal utilitzar i la seva grandria. Aix provoca problemes, ja que de vegades seria convenient decidir la gesti de la memria en temps dexecuci perqu daquesta manera es podria adequar a les necessitats de cada moment. Aquesta possibilitat, desconeguda per nosaltres, ens s facilitada amb laparici de dades dinmiques. No tots els llenguatges de programaci faciliten la utilitzaci de dades dinmiques. En aquesta unitat didctica, establirem els fonaments per poder utilitzar dades dinmiques en llenguatges que les suportin. El llenguatge C suporta dades dinmiques i la seva gesti depn de la utilitzaci correcta dels punters. Aix doncs, en el nucli dactivitat Assignaci dinmica de memria. Mecanismes en C, introdum les operacions que els llenguatges acostumen a facilitar per a la gesti dinmica de la memria i, en especial, com cal actuar en utilitzar el llenguatge C, el qual basa tota la gesti dinmica de la memria en la utilitzaci correcta dels punters. En laprenentatge de la programaci en C, sintrodueixen els punters com a recurs imprescindible en el pas per referncia en la crida de funcions. Us cal refrescar els coneixements introductoris sobre els punters en C. Una vegada coneguts els mecanismes per assolir lassignaci dinmica de la memria, cal aplicar-los per a una gesti eficient de les dades en memria. Per poder-les gestionar eficientment, caldr conixer les principals estructures de dades dinmiques emprades en la programaci informtica: llistes, piles, cues, arbres... En el nucli dactivitat Llistes presentarem lestructura de dada dinmica llista emprada quan cal emmagatzemar informaci de manera lineal (element darrere element) i els principals algorismes per a fer-ne la gesti: recorregut, inserci, recerca i eliminaci.

Estructures de dades dinmiques

Dades lineals en memria dinmica

En el nucli dactivitat Piles i cues presentarem aquestes estructures de dades dinmiques, de les quals ja podem avanar que no sn altra cosa que tipus especfics de llistes, per que per la seva important contribuci al mn de la informtica es mereixen un captol especfic. Per aconseguir els nostres objectius, heu de reproduir en el vostre ordinador i, si pot ser, en diverses plataformes tots els exemples incorporats en el text. Per a aix, trobareu tots els fitxers necessaris en la secci Recursos de contingut. Al web tamb trobareu activitats i exercicis dautoavaluaci. I un consell final: com que lassignaci dinmica de memria s una tcnica molt potent, ens cal conixer-la i aplicar-la, per tamb s complicada i, per tant, cal anar amb molt de compte. Penseu que sereu vosaltres els gestors de la memria de lordinador, passant per damunt del sistema operatiu. Per tant, si no ho fem de la manera adequada podem provocar el mal funcionament de lordinador. Evidentment, reiniciant la mquina tot queda solucionat. Per, i les dades que sestaven gestionant...?, i el programa que sestava executant...? Molt de compte!

Estructures de dades dinmiques

Dades lineals en memria dinmica

Objectius

En acabar la unitat didctica, heu de ser capaos del segent: 1. Utilitzar de manera correcta els punters en el llenguatge C. 2. Utilitzar, quan correspongui, memria dinmica en el disseny dalgorismes. 3. Dissenyar algorismes per gestionar de manera eficient taules dinmiques, llistes, piles i cues. 4. Identificar en quines situacions s convenient la utilitzaci de taules dinmiques, llistes, piles i cues.

Estructures de dades dinmiques

Dades lineals en memria dinmica

1. Assignaci dinmica de memria. Mecanismes en C

La majoria daplicacions informtiques gestionen dades que poden o no estar emmagatzemades en suport extern, per que mentre es gestionen, resideixen en la memria de lordinador. Els llenguatges de programaci faciliten mecanismes perqu el programador pugui declarar dades en memria. Aix, per exemple, estem acostumats a declaracions del tipus: En pseudocodi:
var edat: natural; sou: real; dni: cadena[10]; noms: taula[MAX_NOMS] de cadena[MAX_LONG_NOM]; fivar

En llenguatge C:
unsigned int edat; float sou; char dni[10]; char noms[MAX_NOMS] [MAX_LONG_NOM];

Les declaracions anteriors, tant si sn en pseudocodi com en llenguatge C, tenen en com que sn declaracions de dades esttiques. Recordem que: Les dades esttiques existeixen durant tota lexecuci del programa i/o acci/funci on estan declarades i tenen una capacitat fixa, mentre que les dades dinmiques es poden crear i destruir en temps dexecuci, segons evolucioni el programa, i poden acomodar la seva capacitat a les necessitats reals de lexecuci.

La majoria de llenguatges de programaci faciliten al programador mecanismes per a la gesti de dades dinmiques. El llenguatge C no ns una excepci i per gestionar amb aquest dades dinmiques ens cal la utilitzaci dels punters.

1.1. Aprofundiment en el tipus punter del llenguatge C


Aprofundirem ara en la gesti del tipus punter del llenguatge C. Recordem abans que ja coneixem el tipus punter a causa de la seva obligada

Estructures de dades dinmiques

10

Dades lineals en memria dinmica

utilitzaci per passar arguments per referncia en la crida a funcions en llenguatge C. Enumerem els continguts que suposem ja coneguts: Declaraci de punters. Assignaci de valor a punter. Accs a una dada via punter. Importncia del tipus dobjecte a apuntar. Punters en el pas darguments per referncia. Error daccs al punter NULL. Punters i taules. Punters a estructures. Partint de la base que tenim assumits els coneixements corresponents als continguts anteriors, continuem amb lestudi del tipus punter en el llenguatge C.

1.1.1. Punters genrics


Un punter genric s un punter de tipus void *, s a dir:
void *p;

Aquests punters sutilitzen per emmagatzemar adreces, per no es poden utilitzar per gestionar les dades emmagatzemades en aquestes adreces (amb loperaci dindirecci *, s clar) ja que no sn de cap tipus en concret, de manera que el llenguatge C no sap com interpretar les dades apuntades. Imaginem-nos que escrivim un codi semblant a:
void *p; int x = 1000; ... p = &x; /* p est apuntant ladrea de memria on hi ha x */ *p = 2000; /* voldrem canviar el valor emmagatzemat en x a travs de p */

El compilador es queixar en la darrera instrucci amb un missatge semblant a Not an allowed type, que vol indicar que no s perms loperador dindirecci amb un punter void *.

1.1.2. Els punters i lespecificador const


Recordeu que una dada constant, que es declara amb la partcula const (no la confongueu amb les constants simbliques que es declaren amb la

Estructures de dades dinmiques

11

Dades lineals en memria dinmica

directiva de precompilaci #define), s una dada que ocupa un espai de memria, al qual saccedeix a travs del nom de la constant i que pren un valor en el moment de la seva declaraci que es mant constant mentre la dada resideix en memria. Lafirmaci anterior s certa a mitges. Hi manca una observaci important. El valor emmagatzemat en una dada constant no s modificable a travs de la dada (constant), per com que s en memria, shi pot accedir a travs dun punter i, daquesta manera, modificar-ne el valor emmagatzemat. s a dir:
const int n=10; int *p; ... n = 20; /* error en temps de compilaci */ ... p = &n; *p = 20; /* sha modificat el contingut de n */

Per tant, podrem dir que lespecificaci const per a una dada fa constant laccs a la posici de memria a travs del nom de la dada. Lespecificaci const tamb s utilitzable en la declaraci de punters. Tenim dues possibles utilitzacions: 1) Punter a constant En aquest cas el valor apuntat pel punter es comporta com a constant (no modificable) quan shi accedeix a travs del punter. La seva sintaxi s:
const <tipus_dada> *<nom_punter>;

2) Punter constant En aquest cas ladrea de memria emmagatzemada en el punter s constant. La seva sintaxi s:
<tipus_dada> *const <nom_punter>;

Evidentment, les dues utilitzacions es poden combinar per aconseguir un punter amb dues particularitats: que ladrea que emmagatzema sigui constant i que la dada apuntada per aquesta adrea es comporti com a constant quan shi accedeix a travs del punter. Estem davant un punter constant a constant:
const <tipus_dada> *const <nom_punter>;

Estructures de dades dinmiques

12

Dades lineals en memria dinmica

Vegem-ne alguns exemples:

int i = 30; const int j = 20; /* inicialitzaci obligatria en aquest moment */ const int *pi = &i; int *const pj = &j; /* inicialitzaci obligatria en aquest moment */ const int *const pc = &i; /* inicialitzaci obligatria en aquest moment */

Imaginem que utilitzem les dades anteriors en les instruccions segents, suposant que en cada instrucci partim de la situaci donada en les declaracions anteriors.

i = 300; /* correcte */ j = 200; /* error ja que j s constant */ pi = &j; /* correcte */ *pi = 300; /* error ja que la dada apuntada a travs de pi s constant */ pj = &i; /* error ja que ladrea que guarda pj s constant */ *pj = 200; /* correcte */ pc = &j; /* error */ *pc = 300; /* error */

1.1.3. Importncia dels punters en la definici de taules


El nom duna taula de tipus T no s altra cosa que un punter a aquest tipus que apunta la primera posici de la taula. Podem accedir a la taula amb loperador dindexaci i amb loperador dindirecci.
unsigned int i; int t[100]; for (i=0; i<100; i++) t[i] = 2*i;

/* equivalent a *(t+i) = 2*i; */

Lemplenament de la taula anterior tamb shauria pogut fer amb lajut dun punter auxiliar:
int *pt = t; for (i=0; i<100; i++) { *pt = 2*i; pt++; }

Tamb cal tenir molt clar quin s lespai que ocupen en memria les declaracions dels exemples anteriors. Tal com podeu observar en la figura 1, en realitat la taula t genera un punter que cont ladrea inicial de lespai reservat per a les 100 posicions de la taula. El punter pt, que hem declarat posteriorment, en haver-lo inicialitzat amb el valor de t, tamb est apuntant la posici inicial de lespai reservat per a la taula. Sembla que t i pt sn equivalents, per en realitat no ho sn: el punter t s constant i no en pot modificar el contingut, ja que en cas de fer-ho es perdria ladrea de la primera posici de la taula; en canvi, pt pot anar modificant-ne el contingut, tal com sha vist en el darrer exemple.

Figura 1. Distribuci en memria duna taula

Estructures de dades dinmiques

13

Dades lineals en memria dinmica

Daltra banda, tenim clar que una cadena no s altra cosa que una taula de carcters amb una marca especial de final de cadena (el zero binari). Per tant, qualsevol tractament sobre les taules genriques es pot efectuar sobre cadenes. Fins aqu, les definicions de cadenes han estat del tipus segent:
char cad1[100]; /* cadena de 100 carcters */ char cad2[]="Hola"; /* cadena de 5 carcters (zero binari!) */

En les dues definicions, cad1 i cad2 han provocat la reserva de lespai corresponent al punter (de 100 i 5 octets, respectivament) en memria. s possible definir la cadena anterior cad2 amb la sintaxi segent:
char *cad2 = "Hola";

Aquesta definici provoca que internament shagin reservat lespai corresponent al punter i, a ms, 5 octets (quatre lletres i el carcter \0). Cal anar molt amb compte amb declaracions daquest tipus: !
char *s;

Fixeu-vos que sest produint la reserva de lespai corresponent al punter, per en canvi no hi ha cap reserva despai per als possibles carcters duna cadena apuntada per s. Aquest punter ens podr servir per a una assignaci dinmica de memria en temps dexecuci.

1.1.4. Taules de punters


De la mateixa manera que sabem gestionar taules dels tipus facilitats pel llenguatge C (int, float, char...) i de tipus definits pel programador (estructures), tamb podem gestionar taules de tipus punter:
<tipus_dada> *t[MAX]; /* on MAX s constant simblica */

El fet de tenir una taula que emmagatzemi adreces de memria pot ser til en diferents situacions, sobretot quan es vol simular amb una taula unidimensional una taula multidimensional. El llenguatge C permet definir taules multidimensionals. Fixeu-vos en aquesta declaraci:
int t[10][20];

Estructures de dades dinmiques

14

Dades lineals en memria dinmica

Correspon a una taula de dues dimensions, la qual hem dinterpretar com un tauler de 10 files (numerades de 0 a 9) i 20 columnes (numerades de 0 a 19), com sobserva en la figura 2. Cada casella est destinada a contenir un valor de tipus int.
Figura 2. Taula de 10 files i 20 columnes

En realitat, el llenguatge C crea una taula de 10 punters a taules de 20 celles cadascuna, com ens mostra la figura 3.
Figura 3. Distribuci en memria duna taula bidimensional t[10][20] en llenguatge C

Tanmateix, el llenguatge C tamb ens assegura que les 10 taules de 20 celles estan emmagatzemades en memria consecutivament. s a dir, en lexemple presentat, el llenguatge C provoca una reserva de 200 caselles consecutives en memria, corresponent a la seqncia de les caselles collocades en fila, luna al costat de laltra. s a dir: la casella que cont el valor A s accessible per t[0][0] o *t; la casella que cont el valor B s accessible per t[1][3] o *(t+1*20+3), ja que la casella de la fila 2 - columna 3 es troba en la posici 23 (comenant a numerar per zero) si situem les files en seqncia, luna darrere laltra;

Estructures de dades dinmiques

15

Dades lineals en memria dinmica

la casella que cont el valor C s accessible per t[9][18] i tamb per *(t+198), ja que 198 s el resultat de 9*20+18. Ara b, interessa tenir una estructura semblant les files de la qual no estiguin consecutives en la memria? O, fins i tot, volem que no totes les files tinguin la mateixa longitud? En aquest cas, utilitzarem una taula de punters on cada punter contindr ladrea duna taula unidimensional corresponent a cada fila. Aix, per exemple:
int fila0[20], fila1[10], ..., fila9[15]; int *t[10]; t[0]=fila0; t[1]=fila1; ..., t[9]=fila9;

Daquesta manera, aconseguim una situaci com la representada en la figura 4.


Figura 4. Exemple de taula de punters que apunten diverses taules

Observem que: la casella que cont el valor A s accessible per fila0[0] o t[0][0] o (*t)[0] o **t; (doble punter!) la casella que cont el valor B s accessible per fila1[8] o t[1][8] o(*(t + 1))[8]o*(*(t + 1) + 8); la casella que cont el valor C s accessible per fila9[18] o t[9][18] o(*(t + 9))[18]o *(*(t + 9) + 18). Totes les possibilitats anteriors sn el resultat de combinar dos fets: que els noms de les taules siguin punters a la primera posici de memria corresponent a la taula i que sutilitzi laritmtica de punters. Quina s la millor utilitzaci? Noms hi ha una resposta: la que a vosaltres us sigui ms planera. Aix no impedeix que hgiu de conixer totes

Doble punter Un doble punter a un tipus T no s res ms que un punter destinat a apuntar un punter destinat a apuntar una dada de tipus T.

Estructures de dades dinmiques

16

Dades lineals en memria dinmica

les possibilitats i que les sapigueu utilitzar, ja que de vegades us podeu trobar un codi desenvolupat per altres programadors que han fet servir altres tcniques. Una darrera pregunta: t sentit utilitzar taules de punters? La resposta s afirmativa. En moltes ocasions necessitem una estructura bidimensional per emmagatzemar valors. Suposem que en temps de codificaci del programa coneixem el nombre de files, per que desconeixem el nombre de columnes, que fins i tot pot variar per a cada fila en temps dexecuci. En aquest cas, podrem definir una taula de punters amb una dimensi equivalent al nombre de files i deixar per al moment de lexecuci la definici dinmica de les columnes a apuntar per a cada fila, s a dir, acomodarem la seva dimensi a les necessitats de lexecuci. Evidentment, per poder fer aix necessitem utilitzar lassignaci dinmica de memria.

1.1.5. Punters a punters


En treballar amb taules bidimensionals, utilitzant els punters apareixen expressions del tipus **t o *(*(t+2)+3). Podem observar que estem utilitzant doblement loperador dindirecci per a accedir certes dades de tipus int des de la variable t. Aix vol dir que la variable t s doble punter al tipus int. Si davant duna declaraci del tipus:
int t[10];

diem que t s un punter al tipus int, per coherncia davant duna declaraci com:
int *x[10];

haurem de convenir que x s un punter al tipus int *, s a dir, un doble punter al tipus int. De la mateixa manera que davant la declaraci anterior de la taula t, podem definir que:
int *pt = t; /* pt cont la mateixa adrea que cont t */

hem dentendre que davant la declaraci de la taula x podem definir que:


int **px = x; /* px cont la mateixa adrea que cont x */

Estructures de dades dinmiques

17

Dades lineals en memria dinmica

I tamb, en taules bidimensionals:


int mat[10][20]; int **pmat = mat; /* pmat cont la mateixa adrea que mat */

de manera que podrem utilitzar pmat en tots els llocs on apareix mat. Finalitzem aquesta breu introducci dels punters a punters amb una pregunta: t sentit utilitzar un doble punter? La resposta s afirmativa. Sovint necessitem una estructura bidimensional per emmagatzemar valors. Suposem que en temps de codificaci del programa ni tan sols coneixem el nombre de files, el qual pot variar segons lexecuci. En aquest cas, podrem definir un doble punter i deixar per al moment de lexecuci la definici dinmica de les files. Posteriorment, podrem definir, tamb dinmicament, les columnes corresponents a cada fila. Evidentment, necessitem utilitzar lassignaci dinmica de memria.

1.1.6. Taula de punters a cadenes


s clar que en moltes ocasions ens caldr emmagatzemar cadenes en una taula. Suposem que volem emmagatzemar els dies de la setmana. Utilitzant una taula bidimensional haurem de fer:
char d[7][10]; /* Cap dia ocupa ms de 10 carcters */ strcpy(d[0],"Dilluns"); strcpy(d[1],"Dimarts"); ... strcpy(d[6],"Diumenge");

Hem hagut de definir una taula de 7 files per 10 columnes, quan hi ha noms que ocupen menys de 9 carcters. Per tant, desaprofitem espai. La soluci s que cada fila tingui tantes posicions com siguin necessries. El llenguatge C ens permet declarar una taula de punters a cadenes ocupant lespai necessari i imprescindible amb una declaraci com aquesta:

char *d[]={"Dilluns","Dimarts","Dimecres","Dijous","Divendres","Dissabte","Diumenge"};

En la compilaci, es crea una taula d de set punters a carcter, s a dir, char *, on cada punter cont ladrea a una posici de memria on trobem els continguts Dilluns, Dimarts, Dimecres, Dijous, Divendres, Dissabte i Diumenge. El valor del nombre de posicions de la taula es pot forar en la codificaci del programa escrivint:

char *d[7]={"Dilluns","Dimarts","Dimecres","Dijous","Divendres","Dissabte","Diumenge"};

Estructures de dades dinmiques

18

Dades lineals en memria dinmica

Si el valor que defineix la grandria de la taula de punters s superior a la quantitat de valors inicials existents entre les claus, el llenguatge inicialitza convenientment les primeres posicions de la taula i deixa les altres amb el valor NULL. En canvi, si el valor que defineix la grandria de la taula de punters s inferior a la quantitat de valors inicials, el compilador detectar lerror i no generar el codi objecte. Observem que la variable d s un doble punter a char, per les declaracions char *d[7] i char **d no sn equivalents, ja que en el primer cas es genera una taula de set punters a char (i, evidentment, el doble punter a char corresponent al nom de la taula) i en el segon cas es genera nicament el doble punter a char sense cap altre tipus de reserva.

1.2. Assignaci dinmica de memria


Finalment, ha arribat el moment esperat de conixer els mecanismes que faciliten els llenguatges, i en especial el llenguatge C, per a lassignaci dinmica de memria.

Lassignaci dinmica de memria consisteix a reservar, la quantitat de memria necessria per emmagatzemar dades en temps dexecuci.

Us hem de fer una observaci molt important: no tots els llenguatges de programaci disposen daquesta opci. El nostre bon amic C s que la t. ! Abans dentrar, per, en les particularitats de C, cal remarcar que els llenguatges que permeten lassignaci dinmica de memria ho fan principalment dacord amb dues accions i/o funcions: la primera intenta assignar la memria necessria i la segona allibera la memria assignada prviament, de manera que pugui ser utilitzada en peticions posteriors. Per poder treballar amb assignaci dinmica de memria, s indispensable la utilitzaci de punters. Fins ara els hem treballat detingudament en llenguatge C, per no en pseudocodi. A continuaci els presentarem en pseudocodi. Declaraci duna variable p punter a un tipus T; T pot ser un tipus predefinit del llenguatge o un tipus de dada definida pel programador:
var p : ^T;

Assignaci de valor a una variable p de tipus punter a partir de ladrea duna variable d del tipus T, ja existent en memria:
p = ^d;

Estructures de dades dinmiques

19

Dades lineals en memria dinmica

Accs al valor de la dada apuntada per un punter p:


p^

Cal comentar lexistncia dun valor especial, el valor NULL, per indicar que un punter no cont cap adrea de memria. Tamb s important notar que continua essent responsabilitat del programador inicialitzar convenientment les variables de tipus punter a NULL perqu no continguin valors porqueria, que podrien ser interpretats com a adreces de memria incorrectes. Les dues accions i/o funcions bsiques que tenen tots els llenguatges que permeten lassignaci dinmica de memria en pseudocodi sn les segents:
acci assignar_memria (var p: ^T);

Intenta assignar lespai de memria necessari per a una dada de tipus T, ladrea de la qual quedar enregistrada en la variable punter p. Si el valor retornat a travs del parmetre p s NULL, vol dir que no sha pogut assignar lespai de memria necessari.
acci alliberar_memria (var p: ^T);

s el procs contrari i intenta alliberar la memria assignada per p perqu quedi disponible per a altres necessitats. Suposarem que en cas de tenir xit, el valor p passa a ser NULL (per a aix ha de ser un argument per referncia). Evidentment, s imprescindible que la memria assignada per p que volem alliberar hagi estat assignada per una instrucci dassignar_memria. La comesa del programador s alliberar la memria assignada dinmicament. Cal anar amb compte! Si sassigna memria dins una acci o funci, i en sortir de lacci o funci no es mant cap lligam amb la zona de memria assignada, aquesta ja no podr ser alliberada, ja que no t manera daccedir-hi. Haurem deixat una zona de memria inutilitzada fins que es reinici la mquina.

1.2.1. Funcions per a lassignaci dinmica de memria en C


Les funcions que aporta el llenguatge C per facilitar lassignaci dinmica de memria sn fonamentalment dues (malloc i free), tot i que nhi ha dues ms no imprescindibles (calloc i realloc).
#include <stdlib.h> void *malloc (size_t n); /* Intenta assignar un bloc de memria de n bytes per emmagatzemar dades de qualsevol tipus. Si ho aconsegueix, retorna ladrea de memria a partir del lloc on ha assignat lespai com un punter a void, el qual cal assignar a la variable punter que correspongui. En cas de no aconseguir lespai retorna un valor NULL. */

Estructures de dades dinmiques

20

Dades lineals en memria dinmica

#include <stdlib.h> void free (void *p); /* Intenta alliberar el bloc de memria apuntat per largument p sempre que hagi estat assignat per les funcions malloc, calloc o realloc. */

s convenient acostumar-se a alliberar la memria assignada prviament quan ja no sigui necessria. Un bon sistema operatiu hauria de garantir que, en finalitzar lexecuci dun programa, la memria assignada dinmicament i no alliberada shauria dalliberar de manera automtica. Aix, malauradament, no s aix. Sistemes operatius mpliament estesos no fan aquesta comprovaci i la memria queda amb forats fins que es reinicia la mquina.

1.2.2. Taules dinmiques en C


En multitud docasions ens apareixer la necessitat de poder gestionar la grandria de les taules de manera dinmica. Ara ja estem en disposici de fer-ho. I la millor manera s executar algun programa que ens ho permeti. Ens interessa executar un programa que possibiliti a lusuari anar introduint valors enters fins que vulgui finalitzar, de manera que pugui anar comptant els diferents valors. Deixarem que lusuari decideixi la mxima quantitat de valors diferents a introduir. La manera ms eficient que coneixem en aquest moment per donar soluci a aquest problema consisteix en el segent: una vegada lusuari ha indicat la mxima quantitat de valors diferents a introduir, sha de crear una taula denters duna dimensi igual al mxim nombre indicat per lusuari. Vegem-ho.
/* u1n1p01.c */ #include #include #include #include <stdio.h> <conio.h> <stdlib.h> "verscomp.h"

!!
Trobareu el fitxer u1n1p01.c i la resta darxius necessaris en la secci Recursos de contingut del web daquest crdit, concretament a Codi font en C.

void main(void) { int *p=NULL; unsigned int unsigned int unsigned int int k; char c;

/* punter a taula dinmica per guardar els enters introduts */ *q=NULL; /* punter a taula dinmica per comptadors */ qmax; /* quantitat mxima de valors diferents */ qn;/* quants enters diferents hi ha a la taula */

clrscr(); printf ("Quina quantitat mxima de valors diferents vol introduir?"); do { k = scanf ("%u",&qmax); neteja_stdin(); } while (k == 0); if (qmax == 0) { printf ("No t sentit l'execuci d'aquest programa.\n");

Estructures de dades dinmiques

21

Dades lineals en memria dinmica

exit (0); } p = (int *) malloc ((qmax+1) * sizeof(int)); /* Tcnica sentinella */ if (p == NULL) { printf ("Memria insuficient per gestionar tal quantitat de valors.\n"); exit (1); } q = (unsigned int *) malloc (qmax * sizeof(unsigned int)); if (q == NULL) { printf ("Memria insuficient per gestionar tal quantitat de valors.\n"); free (p); exit (1); } qn = 0; do { printf ("Introdueixi un enter.\n"); do { k = scanf ("%d",&p[qn]); neteja_stdin(); } while (k == 0); for (k=0; p[k] != p[qn]; k++); if (k < qn) q[k]++; else if (qn == qmax) printf ("No emmagatzemat per haver sobrepassat la quantitat mxima de valors diferents.\n"); else { q[qn] = 1; qn++; } printf ("Vol continuar (S/N)?"); do { k = scanf ("%c",&c); neteja_stdin(); } while (k == 0 || (c!='S' && c!='s' && c!='N' && c!='n')); } while (c=='S' || c=='s'); if (qn == 0) printf ("No s'ha introdut cap valor\n"); else { clrscr(); for (k = 0; k < qn ; k++) printf ("Valor %d: %u\n",p[k],q[k]); } free (p); free (q); }

Observem que, tot i utilitzar punters, hem tractat les zones de memria assignades a p i q com si fossin taules, s a dir, amb loperador dindexaci. Observem, tamb, la utilitzaci de loperador sizeof dins la crida de la funci malloc per calcular el nombre de bytes necessaris. Aquesta utilitzaci s necessria per mantenir la compatibilitat del codi en diferents plataformes. Aix, en Turbo C, els int ocupen 2 bytes, mentre que en gcc nocupen 4. Cal utilitzar loperador sizeof perqu el llenguatge en calculi el nombre que correspongui. En lexemple anterior hem utilitzat les funcions malloc i free. El llenguatge C incorpora una instrucci que es pot utilitzar especialment per assignar memria a una taula dinmica. s la funci calloc.
#include <stdlib.h> void *calloc (size_t qelem, size_t n);

Estructures de dades dinmiques

22

Dades lineals en memria dinmica

/* Intenta assignar un bloc de memria per a qelem elements de n bytes cadascun, per emmagatzemar dades de qualsevol tipus. Si ho aconsegueix, retorna ladrea de memria a partir del lloc on ha assignat lespai com un punter a void, que cal assignar a la variable punter que correspongui. En cas de no aconseguir lespai retorna un valor NULL. */

Amb la utilitzaci daquesta funci, les crides a la funci malloc que apareixien en lexemple anterior:
malloc ((qmax+1) * sizeof(int)); malloc (qmax * sizeof(unsigned int));

es podrien substituir per:


calloc (qmax+1, sizeof(int)); calloc (qmax, sizeof(unsigned int));

Vegem ara un nou exemple de gesti de taules dinmiques. s un cas similar a lexemple anterior, per en aquesta ocasi es tracta de comptabilitzar paraules. Per gestionar la memria eficientment interessa emmagatzemar les paraules en lespai adequat. Per tant, en aquest cas, hem de definir la taula que enregistra les paraules com a taula de punters a char, de manera que a mesura que lusuari vagi introduint les paraules anirem sollicitant memria al sistema operatiu per tal de crear dinmicament les cadenes on emmagatzemarem les paraules.
/* u1n1p02.c */ #include #include #include #include #include <stdio.h> <conio.h> <stdlib.h> <string.h> "verscomp.h"

!!
Trobareu el fitxer u1n1p02.c i la resta darxius necessaris en la secci Recursos de contingut del web daquest crdit , concretament a Codi font en C.

void main() { char **p=NULL; /* punter a taula dinmica per guardar les paraules introdudes */ unsigned int *q=NULL; /* punter a taula dinmica per comptadors */ unsigned int qmax; /* quantitat mxima de paraules diferents */ unsigned int qn;/* quantes paraules diferents hi ha a la taula */ int k; char s[255]; /* cadena reservada per a llegir les paraules */ char c; clrscr(); printf ("Quina quantitat mxima de paraules diferents vol introduir?"); do { k = scanf ("%u",&qmax); neteja_stdin(); } while (k == 0); if (qmax == 0) { printf ("No t sentit l'execuci d'aquest programa.\n"); exit (0); } p = (char **) calloc (qmax, sizeof(char *)); /* Tcnica sentinella */ if (p == NULL) { printf ("Memria insuficient per gestionar tal quantitat de paraules.\n"); exit (1);

Estructures de dades dinmiques

23

Dades lineals en memria dinmica

} q = (unsigned int *) calloc (qmax, sizeof(unsigned int)); if (q == NULL) { printf ("Memria insuficient per gestionar tal quantitat de paraules.\n"); free (p); exit (1); } qn = 0; do { printf ("Introdueixi una paraula.\n"); do { k = scanf ("%[^,;.:?!\"\n]",s); neteja_stdin(); } while (k == 0); for (k=0; k<qn && strcmp (p[k],s)!=0; k++); if (k < qn) q[k]++; /* La paraula ja estava enregistrada */ else if (qn == qmax) printf ("No emmagatzemat per haver sobrepassat la quantitat mxima de paraules diferents.\n"); else { p[qn] = (char *) calloc (strlen(s)+1, sizeof (char)); if (p[qn] == NULL) printf ("Memria insuficient per introduir la paraula.\n"); else { strcpy(p[qn],s);/* s'ha emmagatzemat la paraula */ q[qn] = 1; qn++; } } printf ("Vol continuar (S/N)?"); do { k = scanf ("%c",&c); neteja_stdin(); } while (k == 0 || (c!='S' && c!='s' && c!='N' && c!='n')); } while (c=='S' || c=='s'); if (qn == 0) printf ("No s'ha introdut cap valor\n"); else { clrscr(); for (k = 0; k < qn ; k++) { printf ("Valor %s: %u\n",p[k],q[k]); free(p[k]); } } free (p); free (q); }

Hi ha algunes observacions importants a fer a partir de lexemple que acabem de veure: Per poder llegir la paraula per teclat, es necessita una variable de tipus cadena i aquesta ha dhaver estat declarada com a esttica. s la cadena s. A diferncia de lexemple anterior, no hem emprat la tcnica de sentinella, ja que per a aix necessitem definir una zona dinmica per guardar la paraula que acaba dintroduir lusuari i, daltra banda, aquesta sollicitud de memria al sistema operatiu pot resultar estril si la paraula ja estava incorporada a la taula, ja que llavors haurem de procedir a alliberar la memria sollicitada.

Estructures de dades dinmiques

24

Dades lineals en memria dinmica

En lassignaci despai per emmagatzemar la paraula amb el punter p[qn], la quantitat de carcters necessaris s strlen(s)+1, ja que hem de pensar en el carcter nul. En finalitzar el programa, abans dalliberar lespai assignat a travs dels punters p i q, cal alliberar lespai assignat a travs de cada punter de la taula apuntada per p, s a dir, lespai assignat a travs de p[0], p[1], p[2]... Una atenci especial mereix lassignaci despai a cadenes que fa la taula de punters a travs del punter p:
p = (char **) calloc (qmax+1, sizeof (char *));

La importncia radica en el fet que, com que p s un doble punter a char, les posicions que es generen en aquesta assignaci estan destinades a apuntar cadenes, s a dir, a ser punters a char, amb la qual cosa la grandria en bytes necessria per a cada una daquestes posicions s la grandria que necessita un punter a char. Per aix sutilitza sizeof (char *) per saber lespai que ocupa un punter a char.

1.2.3. Reassignaci de blocs de memria en C


Sabem que les funcions malloc i calloc permeten sollicitar al sistema operatiu, en temps dexecuci, espai per a un conjunt de dades dun determinat tipus consecutives en la memria o, dit duna altra manera, espai per a una taula. Una vegada el sistema operatiu assigna lespai de memria necessari, aquest es mant inamovible al llarg de lexecuci del programa fins que el mateix programa nefectua lalliberament. I si lespai que sha previst i sha sollicitat esdev massa gran o massa petit segons levoluci de lexecuci del programa? Estaria molt b poder modificar lespai dinmic ja assignat en funci de les necessitats del programa.

Suposem que el punter p a un tipus T apunta un espai dinmic assignat pel sistema operatiu. Disposem de dos mtodes per modificar la grandria daquest espai apuntat per p: mtode manual i mtode automtic. 1) Mtode manual Consisteix a sollicitar al sistema operatiu lassignaci dinmica dun nou espai via un punter auxiliar paux amb la grandria adequada, on copiem les dades apuntades per p, en cas que el sistema operatiu ens faciliti el nou espai. En aquest cas, desprs de la cpia cal alliberar lespai assignat a p i fer una simple assignaci p = paux, de manera que p apunti la nova zona de grandria adequada.

Estructures de dades dinmiques

25

Dades lineals en memria dinmica

Aquest mtode t un problema: durant un cert interval de temps dexecuci, el sistema ha de tenir prou memria per mantenir lespai amb les dades originals i lespai on copiar aquestes dades. Podria donar-se el cas que el sistema no disposs de memria suficient per poder efectuar-ne el trasps. 2) Mtode automtic Consisteix a utilitzar la funci realloc que incorpora el llenguatge C.
#include <stdlib.h> void *realloc (void *p, size_t n); /* El parmetre p s un punter que apunta un bloc de memria pel qual volem canviar la quantitat de memria assignada. Si p s NULL, aquesta funci es comporta exactament igual que la funci malloc. Si p no s NULL i el bloc de memria apuntat actualment ha estat assignat amb les funcions malloc, calloc o la mateixa realloc, aquesta funci intenta modificar la seva grandria actual i adequar-la al valor n. Les dades emmagatzemades no canvien en el tros de bloc conservat. Aquesta funci retorna un punter al nou espai assignat, el qual pot residir en un altre lloc de la memria. En cas que no hi hagi prou espai en la memria o si n s zero, la funci retorna el valor NULL. En aquest cas, el bloc original s alliberat. */

Aquest mtode t un problema: si el sistema no pot efectuar-ne el trasps, hem perdut la zona de memria assignada! Molt perills, no us sembla?

Estructures de dades dinmiques

26

Dades lineals en memria dinmica

2. Llistes

Iniciem lestudi dels tipus de dades dinmiques lineals que cal conixer, ja que es poden utilitzar en diversos mbits. El tipus de dada dinmica lineal per excellncia s la llista. Hi ha dos tipus de dades dinmiques lineals ms les piles i les cues que no deixen de ser tipus especials de llista, per acostumem a tenir captols especfics en tots els manuals de programaci per la seva importncia.

Llistes i taules En el crdit Estructures de dades dinmiques les llistes tenen un tractament encadenat i dinmic. Hi ha autors i llibres que presenten el concepte de llista com una taula en la qual es pot accedir al primer element i, a partir daquest, es pot accedir als segents. Aquest enfocament no deixa de ser un tractament seqencial duna taula, tractament que coneixem en profunditat. Per aquest motiu, tractarem el concepte de llista en lmbit ms ests, com un conjunt seqencial dinformaci encadenada amb tractament dinmic.

Una llista simplement encadenada (anomenada llista la majoria de les vegades) s una seqncia delements, anomenats generalment nodes, enllaats mitjanant punters continguts en els mateixos nodes, punters que apunten altres nodes. Una llista sidentifica per un punter al primer node de la seqncia. En particular, una llista pot ser buida (no hi ha seqncia delements) i es reconeix perqu el seu punter al primer node cont el valor NUL. En general, un node consta de dues parts: Una part que cont la informaci que es vol emmagatzemar a la llista. Una part que cont el lligam cap a un altre node de la llista. El darrer node de la llista cont el valor NUL com a lligam.

Per tant, si volem gestionar una llista simplement encadenada de dades dun cert tipus T, caldr tenir en compte la definici segent de node:
tipus node = tuple inf : T; /* cont la informaci */ seg : ^node; /* punter a segent node */ fituple

La figura 5 ens mostra una llista simplement encadenada de tipus T.


Figura 5. Exemplificaci duna llista simplement encadenada

Estructures de dades dinmiques

27

Dades lineals en memria dinmica

B, ja sabem com s una llista simplement encadenada. De la figura 5 es dedueix que per anar a un node no hi ha ms remei que efectuar un recorregut seqencial des del punter inici i que, una vegada estem en un node, noms es pot avanar cap als nodes posteriors per no es pot retrocedir als nodes anteriors. Hi ha altres tipus de llistes amb millors prestacions, per ms complicades de gestionar; per aix primer ens hem de centrar en els algorismes de gesti de llistes simplement encadenades i, quan ja hgim adquirit experincia, ja podrem intentar la incursi en la gesti de llistes ms complicades.

2.1. Llistes simplement encadenades


A continuaci analitzem els algorismes de tractament de llistes simplement encadenades que cal tenir sempre ben presents. 1) Crear una llista buida
var llista: ^node; llista = NUL;

Evidentment, crear la llista noms consisteix a crear el punter inicial. Cal tenir, per, la precauci dinicialitzar-lo amb el valor NUL. 2) Afegir un element per linici La figura 6 ens ajudar a fer el seguiment del procs dafegir un element per linici de la llista. El procs dafegir per linici consisteix a crear un nou node N, emplenar-lo amb la informaci que es vol afegir a la llista i afegir-lo a la llista, de manera que el punter inicial, que en un principi est apuntant el node A (fletxa E), passi a apuntar el nou node N (fletxa E2), i aquest passi a apuntar el node A (fletxa E1).
Figura 6. Exemplificaci del procs dafegir un element per linici duna llista

Estructures de dades dinmiques

28

Dades lineals en memria dinmica

Lalgorisme en pseudocodi seria:


funci afegirInici (var inici:^node, element : T) retorna natural s /* Arguments: inici: Punter que apunta linici de la llista element: Dada a afegir a la llista Retorna: 0 : Operaci correcta 1 : Error per manca de memria */ var aux:^node; fivar assignar_memria (aux); si aux == NUL llavors retorna 1; fisi aux^.inf = element; aux^.seg = inici; inici = aux; retorna 0; fifunci

Per poder crear el node N, ha calgut utilitzar una variable aux punter a tipus T. No podem fer aquesta tasca amb el punter inici ja que perdria la informaci que cont, i que s la que ens adrea al node A. Podeu observar tamb que largument inici que es passa a la funci afegirInici est declarat com a pas per referncia. Aix s degut al fet que la funci afegirInici ha de modificar el valor que contenia el punter inici. Noteu, a ms, que aquesta funci t un bon funcionament si la llista s buida, ja que en aquest cas afegirInici procedeix a inserir el primer element a la llista. Finalment, cal deixar constncia que lordre en qu sefectuen les assignacions s fonamental. Una vegada el nou node est creat i tenim la seva adrea en el punter auxiliar aux, emplenem el camp contenidor dinformaci amb lelement que sha passat per valor i. El segon pas s emplenar el seu punter al node segent, cosa que saconsegueix copiant a aux^.seg el contingut dinici (hem assolit la fletxa E1). El tercer pas s emplenar inici amb ladrea del nou node, la qual tenim a aux (hem assolit la fletxa E2). Si executssim el tercer pas abans que el segon, en aplicar el segon ja no disposarem del valor que ens porta el node A a inici. 3) Recrrer la llista Ja sabem que el recorregut en una llista s sempre seqencial i que en les llistes simplement enllaades noms es pot anar cap endavant. Observem que per poder recrrer la llista cal utilitzar un punter auxiliar, ja que si utilitzssim el punter inici perdrem ladrea on es troba el primer node. Fixeu-vos tamb que el recorregut finalitza quan es troba un node que t el valor NUL en el seu apartat seg. El recorregut ja no comena si el punter inici t el valor NUL (llista buida).

Estructures de dades dinmiques

29

Dades lineals en memria dinmica

Lalgorisme en pseudocodi seria:


/* Suposem que inici:^node s la variable punter que apunta el primer node de la llista */ var aux:^node; fivar aux = inici; mentre aux != NUL fer tractar_element (aux^.inf); aux = aux^.seg; fimentre

4) Afegir un element pel final La figura 7 ens ajudar a fer el seguiment del procs dafegir un element pel final de la llista.
Figura 7. Procs dafegir un valor pel final duna llista simplement encadenada

Afegir un element pel final implica necessriament un recorregut per la llista per arribar al darrer node i procedir a afegir el nou element. El procs dafegir pel final consisteix a crear un nou node N i emplenar-lo amb la informaci que es vol afegir a la llista, de manera que el punter del darrer node (F en el grfic) deixi de tenir el valor NUL com a punter al node segent i passi a apuntar el nou node N. Lalgorisme en pseudocodi seria:
funci afegirFinal (var inici:^node, element : T) retorna natural s /* Arguments: inici: Punter que apunta linici de la llista element: Dada a afegir a la llista Retorna: 0 : Operaci correcta 1 : Error per manca de memria */ var aux1, aux2:^node; fivar assignar_memria (aux1); si aux1 == NUL llavors retorna 1; fisi aux1^.inf = element; aux1^.seg = NUL; si inici == NUL llavors inici = aux1; sin aux2 = inici^.seg; mentre aux2^.seg != NUL fer aux2 = aux2^.seg fimentre aux2^.seg = aux1; fisi retorna 0; fifunci

Estructures de dades dinmiques

30

Dades lineals en memria dinmica

Perqu aquest algorisme sigui correcte, cal utilitzar dues variables punters auxiliars. En efecte, una primera variable aux1 punter a tipus T s necessria per crear el nou node abans de fer cap recorregut i poder detectar daquesta manera que no hi ha problemes deguts a la manca de memria. Una vegada tenim el node N creat i emplenat adequadament amb lelement que es vol afegir a la llista i apuntant NUL, hem de fer que el darrer element de la llista deixi de ser-ho i passi a apuntar el nou node. Per, quin s el darrer element de la llista? Cal tenir present que la llista pot ser buida i, en aquest cas, el darrer element no existeix i noms ens hem de preocupar que el punter inici passi a apuntar el nou node, cosa que aconseguim emplenant-lo amb ladrea que cont aux1. Si el punter inici no t el valor NUL, t sentit preguntar pel contingut dinici^.seg, valor que situem en la segona variable aux2 punter a T. Utilitzem aquest punter per situar-nos en el darrer node F de la llista. Una vegada all, noms ens falta fer que el seu punter al segent passi a apuntar el node N, ladrea del qual tenim en la variable aux1. Finalment, cal fer notar que aqu tamb s imprescindible que largument inici es passi per referncia, ja que el seu contingut ser modificat en cas que la llista sigui buida. 5) Cercar un element Per poder realitzar una operaci daquest estil, cal tenir un criteri de recerca. Cal prendre algunes decisions. Com podem detectar lelement a cercar en un recorregut per la llista? Hi ha diverses possibilitats: coincidncia en el valor dun, alguns o tots els camps de lelement. En el darrer cas seria una recerca destinada noms a saber si lelement s a la llista, ja que no tindria sentit cercar-lo si ja coneixem totes les dades. Com que estem plantejant el problema en una situaci terica, considerarem que hi ha un conjunt de camps que formen un tipus T*, subtipus de T, pels quals sha defectuar la recerca. Per tant, caldr passar com a argument un element del tipus T* que contingui els camps de recerca. Com podem actuar si hi ha la possibilitat delements repetits en la llista? Prenem la decisi de considerar la primera ocurrncia trobada de lelement cercat. Sota aquests supsits, tenim el segent:
funci recerca (inici:^node; var element: T; referncia: T*) retorna natural s /* Arguments: inici: Punter que apunta linici de la llista element: Variable retornada plena amb el valor trobat

Estructures de dades dinmiques

31

Dades lineals en memria dinmica

referncia: Dada de tipus T* a cercar Retorna: 0 : Recerca efectuada amb xit. 1 : Recerca efectuada sense xit. */ mentre inici != NUL i no coincideix (inici^.inf, referncia) fer inici = inici^.seg; fimentre si inici == NUL llavors retorna 1; sin element = inici^.inf; retorna 0; fisi fifunci funci coincideix (element: T, referncia: T*) retorna boole s /* Codi que comprovi si element correspon a referncia */ fifunci

Observeu que dins la funci no utilitzem cap variable punter auxiliar per recrrer la llista, ja que emprem el mateix punter inici. s lcit aix? No estarem perdent ladrea del primer node de la llista? Evidentment s que la perdem dins la funci, per en finalitzar-ne lexecuci el punter continua tenint el mateix valor inicial, ja que sha passat per valor i no per referncia. 6) Inserir un element Per poder realitzar una operaci daquest estil cal tenir com a referncia un element de la llista respecte al qual sinsereix el nou element (abans o desprs). Per poder realitzar aquest tipus de tractament considerem els mateixos supsits que hem tingut en compte en la recerca dun element.
funci inserirAbans (var inici:^node; element: T, referncia: T*) retorna natural s /* Arguments: inici: Punter que apunta linici de la llista element: Dada a afegir a la llista referncia: Dada de tipus T* a cercar per inserir, abans d'aquesta, lelement Retorna: 0 : Operaci correcta 1 : Error per manca de memria 2 : No es troba la referncia */ var pref, ant, aux:^node; fivar pref = inici; ant = NUL; mentre pref != NUL i no coincideix (pref^.inf, referncia) fer ant = pref; pref = pref^.seg; fimentre si pref == NUL llavors retorna 2; fisi assignar_memria (aux); si aux == NUL llavors retorna 1; fisi aux^.inf = element; aux^.seg = pref; si pref == inici llavors inici = aux; /* lelement referncia era el primer de la llista */ sin ant^.seg = pref; /* lelement referncia no era el primer de la llista */ fisi retorna 0; fifunci

Estructures de dades dinmiques

32

Dades lineals en memria dinmica

Observem que cal passar largument inici per referncia, ja que pot quedar modificat si la dada referenciada s la que estava en primer lloc.
funci inserirDesprs (inici:^node; element: T, referncia: T*) retorna natural s /* Arguments: inici: Punter que apunta linici de la llista element: Dada a afegir a la llista referncia: Dada de tipus T* a cercar per inserir, desprs d'aquesta, lelement Retorna: 0 : Operaci correcta 1 : Error per manca de memria 2 : No es troba la referncia */ var pref, aux:^node; fivar pref = inici; mentre pref != NUL i no coincideix (pref^.inf, referncia) fer pref = pref^.seg; fimentre si pref == NUL llavors retorna 2; fisi assignar_memria (aux); si aux == NUL llavors retorna 1; fisi aux^.inf = element; aux.seg = pref^.seg; pref^.seg = aux; retorna 0; fifunci

Observem que no cal passar largument inici per referncia, ja que mai no pot resultar modificat per un inserirDesprs. 7) Esborrar el primer element Quan es parla desborrar un element duna llista (sigui el primer, el darrer o un dintermedi), podem considerar la funci que noms esborra o la funci que esborra i retorna lelement eliminat. De fet, aquesta segona opci s molt ms interessant i no suposa cap cost de programaci. En les operacions desborrament que presentem considerarem un argument per a recuperar lelement esborrat.
funci esborrarPrimer (var inici:^node, var element: T) retorna natural s /* Arguments: inici: Punter que apunta linici de la llista element: Variable on sha de recollir lelement esborrat Retorna: 0 : Operaci correcta 1 : Error ja que la llista s buida */ var aux:^node; fivar si inici == NUL llavors retorna 1; fisi aux = inici^.seg; element = inici^.inf; alliberar_memria (inici); inici = aux; retorna 0; fifunci

8) Esborrar el darrer element

funci esborrar_darrer (var inici:^node, var element: T) retorna natural s /* Arguments: inici: Punter que apunta linici de la llista

Estructures de dades dinmiques

33

Dades lineals en memria dinmica

element: Variable on sha de recollir lelement esborrat Retorna: 0 : Operaci correcta 1 : Error ja que la llista s buida */ var ult, ant: ^node; fivar si inici == NUL llavors retorna 1; fisi ult = inici; ant = NUL; mentre ult^.seg != NUL fer ant = ult; ult = ult^.seg; fimentre si ult == inici llavors inici = NUL; sin ant^.seg = NUL; fisi element = ult^.inf; alliberar_memria (ult); retorna 0; fifunci

9) Esborrar un element Ens trobem en la mateixa situaci que en lalgorisme corresponent a inserir un element. Cal tenir una referncia per saber quin element (un en concret, el dabans o el de desprs) sha desborrar. Tindrem en compte els mateixos supsits daquella situaci.

funci esborrarElement (var inici:^node; var element: T, referncia: T*) retorna natural s /* Arguments: inici: Punter que apunta linici de la llista element: Variable on sha de recollir lelement esborrat referncia: Dada de tipus T* corresponent a lelement a esborrar Retorna: 0 : Operaci correcta 1 : No es troba la referncia */ var pref, ant: ^node; fivar pref = inici; ant = NUL; mentre pref != NUL i no coincideix (pref^.inf, referncia) fer ant = pref; pref = pref^.seg; fimentre si pref == NUL llavors retorna 1; fisi si pref == inici llavors inici = pref^.seg; /* lelement referncia era el primer de la llista */ sin ant^.seg = pref^.seg; /* lelement referncia no era el primer de la llista */ fisi element = pref^.inf; alliberar_memria (pref); retorna 0; fifunci funci esborrarAnterior (var inici:^node; var element: T, referncia: T*) retorna natural s /* Arguments: inici: Punter que apunta linici de la llista element: Variable on sha de recollir lelement esborrat referncia: Dada de tipus T* a cercar per esborrar lelement anterior Retorna: 0 : Operaci correcta 1 : No es troba la referncia 2 : No existeix un element anterior a la referncia */ var pref, ant1, ant2: ^node; fivar pref = inici; ant1 = NUL; ant2 = NUL; mentre pref != NUL i no coincideix (pref^.inf, referncia) fer ant2 = ant1; ant1 = pref; pref = pref^.seg;

Estructures de dades dinmiques

34

Dades lineals en memria dinmica

fimentre si pref == NUL llavors retorna 1; fisi si ant1 == NUL llavors retorna 2; fisi si ant1 == inici llavors inici = pref; /* lelement a esborrar era el primer de la llista */ sin ant2^.seg = pref; /* lelement a esborrar no era el primer de la llista */ fisi element = ant1^.inf; alliberar_memria (ant1); retorna 0; fifunci funci esborrarSegent (inici:^node; var element: T, referncia: T*) retorna natural s /* Arguments: inici: Punter que apunta linici de la llista element: Variable on sha de recollir lelement esborrat referncia: Dada de tipus T* a cercar per esborrar lelement posterior Retorna: 0 : Operaci correcta 1 : No es troba la referncia 2 : No existeix un element posterior a la referncia */ var pref, aux:^node; fivar pref = inici; mentre pref != NUL i no coincideix (pref^.inf, referncia) fer pref = pref^.seg; fimentre si pref == NUL llavors retorna 1; fisi si pref^.seg == NUL llavors retorna 2; fisi aux = pref^.seg; pref^.seg = aux^.seg; element = aux^.inf; alliberar_memria (aux); retorna 0; fifunci

Observem que no cal passar largument inici per referncia, ja que mai no pot resultar modificat per un esborrarSegent. 10)Eliminar la llista s molt important tenir un algorisme que ens elimini tota una llista. Aquest algorisme lexecutarem quan ja no ens interessi tenir la llista dins una execuci i, sobretot, en finalitzar lexecuci dun programa per eliminar totes les llistes creades dinmicament. Lalgorisme s, evidentment, molt senzill. Simplement cal fer un recorregut per tots els nodes i alliberar la memria assignada corresponent.
acci eliminar_llista (var inici:^node) s /* Argument inici: Punter que apunta linici de la llista */ var aux: ^node; fivar mentre inici != NUL fer aux = inici^.seg; alliberar_memria (inici); inici = aux; fimentre fiacci

Fixeu-vos que en sortir de lacci, el punter inici t el valor NUL, com s lgic.

Estructures de dades dinmiques

35

Dades lineals en memria dinmica

2.2. Llistes simplement encadenades ordenades sense repetits


Coneixem, doncs, els algorismes de gesti duna llista simplement encadenada genrica en la qual no hi ha establert cap tipus dordre entre els seus elements ni tampoc cap tipus didentificaci unvoca, s a dir, hi pot haver elements repetits. Tenen sentit tots els algorismes sobre llistes simplement encadenades en cas que la llista es mantingui ordenada sota algun criteri? Evidentment no, ja que no t sentit afegir per linici ni pel final, ni abans ni desprs dun cert element. Si es tracta dafegir ordenadament, noms hi ha una possibilitat: el lloc que li correspon segons lordre establert. De manera semblant es pot raonar el sentit dels algorismes de recerca i deliminaci. Estudiem ara els algorismes dinserci, recerca i eliminaci, suposant que no hi pot haver elements repetits en el camp o conjunt de camps que formen el criteri dordenaci. s evident que amb alguna modificaci es poden dissenyar els algorismes per gestionar llistes simplement encadenades ordenades amb elements repetits. 1) Cercar un element Considerarem que tenim un tipus T* que cont els camps que identifiquen els elements i pels quals est definit lordre establert en la llista.

funci recerca (inici:^node; var element: T; referncia: T*) retorna natural s /* Arguments: inici: Punter que apunta linici de la llista element: Variable retornada plena amb el valor trobat, si existeix referncia: Dada de tipus T* corresponent a lelement a cercar Retorna: 0 : Recerca efectuada amb xit. 1 : Recerca efectuada sense xit. */ mentre inici != NUL i s_menor (inici^.inf, referncia) fer inici = inici^.seg; fimentre si inici == NUL o s_major (inici^.inf, referncia) llavors retorna 1; fisi element = inici^.inf; retorna 0; fifunci funci s_menor (element: T, referncia: T*) retorna boole s /* Codi que comprovi si la part T* delement s ms petita que referncia */ fifunci funci s_major (element: T, referncia: T*) retorna boole s /* Codi que comprovi si la part T* delement s ms gran que referncia */ fifunci

Estructures de dades dinmiques

36

Dades lineals en memria dinmica

2) Inserir un element
funci inserir (var inici:^node; element: T) retorna natural s /* Arguments: inici: Punter que apunta linici de la llista ordenada element: Dada a inserir a la llista Retorna: 0 : Operaci correcta 1 : Error per manca de memria 2 : Ja existeix un element amb el mateix identificador */ var seg, ant, aux:^node; fivar seg = inici; ant = NUL; mentre seg != NUL i no s_menor* (seg^.inf, element) fer ant = seg; seg = seg^.seg; fimentre si seg == NUL o s_major* (seg^.inf, element) llavors assignar_memria (aux); si aux == NUL llavors retorna 1; fisi aux^.inf = element; aux^.seg = seg; si ant == NUL llavors inici = aux; /* lelement sinsereix a linici de la llista */ sin ant^.seg = aux; fisi retorna 0; sin retorna 2; fisi fifunci funci s_menor* (ele1: T, ele2: T) retorna boole s /* Codi que comprovi si la part T* dele1 s ms petita que la dele2 */ fifunci funci s_major* (ele1: T, ele2: T) retorna boole s /* Codi que comprovi si la part T* dele1 s ms gran que la dele2 */ fifunci

3) Esborrar un element
funci esborrar (var inici:^node; var element: T; referncia: T*) retorna natural s /* Arguments: inici: Punter que apunta linici de la llista element: Variable retornada plena amb lelement a esborrar, si existeix referncia: Dada de tipus T* corresponent a lelement a esborrar Retorna: 0 : Esborrament efectuat amb xit. 1 : Esborrament no efectuat perqu no sha trobat lelement. */ var pref, ant: ^node; fivar pref = inici; ant = NUL; mentre pref != NUL i s_ms petit (pref^.inf, referncia) fer ant = pref; pref = pref^.seg; fimentre si pref == NUL o s_ms gran (pref^.inf, referncia) llavors retorna 1; fisi si pref == inici llavors inici = pref^.seg; /* lelement esborrat s linicial */ sin ant^.seg = pref^. seg fisi element = pref^.inf; alliberar_memria (pref); retorna 0; fifunci

Estructures de dades dinmiques

37

Dades lineals en memria dinmica

2.3. Llistes circulars o en anell


Les llistes simplement encadenades no permeten, a partir dun element, accedir directament als anteriors. Per solucionar aquest inconvenient es pot fer que el darrer element apunti el primer i, daquesta manera, es pot obtenir una llista circular. Per accedir a una llista circular caldr un punter, com en les llistes simplement encadenades. En aquest cas s ms avantatjs substituir el punter inicial que considervem en les llistes simplement encadenades per un punter final que apunti el darrer element de la llista circular. Daquesta manera, sobt un fcil accs a lelement inicial i a lelement final, ja que suposem que final: ^node s el punter al darrer element de la llista. Llavors: Per accedir al darrer element de la llista noms cal considerar el node final^. s a dir, el darrer element de la llista s final^.inf. Per accedir al primer element de la llista noms caldr que considerem el node (final^.seg)^. s a dir, el primer element de la llista s (final^.seg)^.inf. Les operacions en llistes circulars sn similars a les operacions de les llistes simplement encadenades, excepte en all que fa referncia al primer o darrer element de la llista circular. Deixem per a vosaltres el disseny dels algorismes corresponents.

2.4. Llistes doblement encadenades


En les llistes simplement encadenades, el recorregut noms es pot efectuar en un nic sentit: de linici cap al final. Per solucionar aquest inconvenient i permetre un recorregut en ambds sentits, es poden construir llistes doblement encadenades. En aquestes llistes, cada node cont dos punters, anterior i segent, que apunten, respectivament, cap enrere i cap endavant. s a dir, si per a un node Y hi ha un node X que lapunta amb el punter segent, llavors el node X s apuntat pel punter anterior del node Y, i si un node Y apunta un node Z amb el punter segent, llavors el node Y est apuntat pel punter anterior del node Z. Com que cada element cont dos punters, una llista doblement encadenada ocupa ms espai en memria que una llista simplement encadenada per a una mateixa quantitat dinformaci.

Estructures de dades dinmiques

38

Dades lineals en memria dinmica

Per accedir a una llista doblement encadenada tenim dos punters, inici i final. Com que el primer element de la llista no t un element anterior, el seu punter anterior cont el valor NUL. De la mateixa manera, com que el darrer element de la llista no t un element segent, el seu punter segent cont el valor NUL. Les operacions en llistes doblement encadenades sn similars a les operacions de les llistes simplement encadenades, per cal tenir en compte que hi ha els dos punters per accedir a la llista, els dos punters de cada node i que el recorregut es pot fer en tots dos sentits. Deixem per a vosaltres el disseny dels algorismes corresponents. Tamb es pot considerar el cas de llistes doblement encadenades circulars, de manera que el darrer node apunta el primer amb el punter segent i el primer node apunta el darrer amb el punter anterior. No necessitem, per, dos punters per accedir a la llista, amb un nhi ha prou. En aquest cas, ens preguntem quina s la millor elecci com a nic punter daccs en una llista doblement encadenada circular: ha dapuntar el primer o el darrer element de la llista? Qu en penseu?

Estructures de dades dinmiques

39

Dades lineals en memria dinmica

3. Piles i cues

Passem ara a estudiar dos tipus de dades fonamentals en informtica: les piles i les cues. En la majoria dels nostres programes les hem estat utilitzant de manera implcita, sense ser-ne conscients. Les piles sn, com el seu nom indica, recipients on emmagatzemar dades apilades i, com en una pila de qualsevol tipus delements (plats, llibres...), les niques operacions que es poden fer sn: posar un element damunt la pila i treure lelement del damunt (darrer element posat a la pila). Les cues sn, com el seu nom indica, recipients on semmagatzemen dades en lordre en qu van arribant i, com en una cua qualsevol (de persones a lentrada dun cinema, de cotxes en una carretera...), les niques operacions que es poden fer sn: posar un element al final de la cua i treure lelement de linici de la cua (lelement que fa ms temps que s a la cua). Per a cadascun daquests tipus en veurem, en primer lloc, la definici i la implementaci amb la utilitzaci dels tipus de dades coneguts; posteriorment, en presentarem diferents camps daplicaci.

3.1. Piles

Una pila s un conjunt lineal (ja que els seus components ocupen llocs successius dins el conjunt) delements en el qual es poden inserir o eliminar elements noms per un dels dos extrems. En conseqncia, els elements duna pila seran eliminats en ordre invers al qual es van inserir.

A causa del funcionament dinserci i eliminaci, les piles es coneixen com a estructures LIFO (last in - first out), ja que el darrer element que va ser inserit (last in) s el primer a ser eliminat (first out). La noci de pila ens s familiar: pila de plats, de llibres... Per afegir un plat es colloca dalt de la pila; per agafar-lo es pren de la part superior de la pila. Hi ha un concepte angls molt utilitzat en la gesti de piles: top. Aquesta paraula indica al capdamunt de la pila i, evidentment, la gesti de piles treballa sempre amb lelement top de la pila.

Estructures de dades dinmiques

40

Dades lineals en memria dinmica

Amb les piles noms es poden efectuar dues operacions: posar (molt ests el terme angls push) i treure (en angls pop). ! Hi ha autors que permeten una tercera operaci: capdamunt (en angls, top), pensada per consultar el valor que hi ha en el top de la pila. De fet, cada vegada que els autors volen consultar quin s lelement que hi ha al capdamunt de la pila, aquells que no tenen en compte aquesta operaci han defectuar un treure per veure lelement i un posar per tornar a posar lelement a la pila. Nosaltres permetrem utilitzar aquesta tercera operaci. Tornem-ho a repetir. Tot i que les implementacions de pila ho permetin, el concepte de tipus de dada pila porta implcit que no shi pugui fer altra cosa que les dues (o tres) operacions esmentades, s a dir, no es pot realitzar un recorregut per una pila, no es pot efectuar una recerca per una pila, no es pot treure lelement de sota (ja que no shi t accs)... !

3.1.1. Implementaci del tipus de dada pila


Tradicionalment hi ha dues implementacions del tipus de dada pila: a) Mitjanant taules En aquest cas, en la creaci de la pila cal definir la grandria mxima de la pila i tenir una variable entera per apuntar el darrer element inserit, lelement top. La figura 8 ens mostra la implementaci duna pila mitjanant una taula de grandria M+1 que ja cont dos elements. Observem que la variable top apunta la casella que cont el darrer element afegit. Malgrat que les taules permeten laccs a qualsevol element, la gesti de piles noms permet accedir a lelement de dalt de tot, s a dir, lelement apuntat pel valor que cont top. En una pila noms podem afegir un nou element (el valor de top augmentaria en una unitat) o recuperar lelement de dalt de tot (el valor de top disminuiria en una unitat). La implementaci de piles mitjanant taules presenta un problema: la pila t una capacitat fixa que ocupa un espai en memria i, per tant, es desaprofita espai si la pila no s plena i no hi ha la possibilitat daugmentarne la capacitat quan ja s plena. En el cas del llenguatge C, que permet la definici de taules dinmiques, la implementaci de piles mitjanant taules no seria tan deficient com en
Figura 8. Implementaci duna pila mitjanant una taula

Estructures de dades dinmiques

41

Dades lineals en memria dinmica

el cas de llenguatges que no permeten taules dinmiques. Tot i aix, coneixem els problemes que t el llenguatge C per augmentar i/o disminuir la grandria duna taula dinmica i el problema de desaprofitament despai quan la pila no fos plena. A vegades es pot intentar compartir espai de memria per minorar tant com es pugui el problema presentat despai de memria fix i esttic. Aquesta gesti s possible quan cal tractar amb dues piles del mateix tipus delement de grandries M+1 i N+1. Podem decidir gestionar-les amb una nica taula de grandria M+N+2. La figura 9 ens en mostra el muntatge.
Figura 9. Implementaci de dues piles mitjanant una taula

Si en algun punt del procs la pila1 necessita ms de M+1 posicions i en aquell moment la pila2 no t ocupades N+1 posicions, aquesta pot cedir espai a laltra. b) Mitjanant llistes simplement encadenades ! La figura 10 ens en mostra una exemplificaci.
Figura 10. Implementaci de pila mitjanant llista simplement encadenada

En aquest cas, es tracta de crear una llista simplement encadenada on lelement top s el punter a linici de la pila. Llavors, les dues operacions sobre piles consisteixen en dues de les operacions sobre llistes simplement encadenades: Posar un element a la pila (push) equival a afegir un element per linici de la llista (per la zona OP de la figura 10). Recuperar un element de la pila (pop) equival a esborrar un element per linici de la llista (per la zona OP de la figura 10) i recollir lelement eliminat de la pila via parmetre per referncia.

Estructures de dades dinmiques

42

Dades lineals en memria dinmica

A ms, s interessant tenir una operaci per eliminar la pila, la qual equival a loperaci corresponent per eliminar una llista. En aquesta implementaci lnica limitaci es troba en la quantitat de memria disponible. La pila pot crixer i disminuir segons les necessitats. Per tant, s la millor opci.

3.1.2. Aplicacions de les piles


Les piles sutilitzen molt en informtica. Enumerarem algunes de les seves aplicacions. a) Crides a subprogrames (accions i funcions) Quan es t un programa que crida un subprograma, internament sutilitzen piles per guardar lestat de les variables del programa en el moment que es fa la crida. Aix, quan sacaba lexecuci del subprograma, els valors emmagatzemats a la pila es poden recuperar i es pot continuar lexecuci del programa en el punt on va ser interromput. A ms de les variables, sha demmagatzemar ladrea del programa on es va fer la crida, ja que s en aquesta posici on retorna el control del procs. Evidentment, cal fer aquesta gesti amb una estructura de tipus pila, ja que lestat de les diverses crides es va apilant en lordre en qu es van efectuant (imagineu-vos diversos nivells de crides). A mesura que van finalitzant (ordre invers en qu shan produt), es van desapilant els corresponents estats que indiquen la situaci existent abans defectuar les dites crides. b) Recursivitat La recursivitat, tema que per la seva importncia es mereix un captol especfic, consisteix en la resoluci dun problema complex en termes de si mateix, per en un grau de complexitat menor, de tal manera que, a la fi, el problema queda resolt del tot. Des del punt de vista informtic, la recursivitat implica que un subprograma es cridi a si mateix (de manera directa o indirecta) i, per tant, la recursivitat no deixa de ser un cas particular de crides a subprogrames. Hi ha llenguatges de programaci que no faciliten la utilitzaci de recursivitat. En aquests casos, resulta convenient tenir algun procediment per simular la recursivitat. La utilitzaci del tipus de dada pila ho permetr. c) Tractament dexpressions aritmtiques Un problema usual en computaci s avaluar una expressi aritmtica. Com actua lordinador quan ha davaluar una operaci tan simple com 10 + 3 * 5?

Estructures de dades dinmiques

43

Dades lineals en memria dinmica

Evidentment, el resultat ha de ser 25. Quin raonament segueix lsser hum per obtenir aquest resultat? En primer lloc, fent una rpida ullada, la nostra capacitat visual ens permet captar lexpressi globalment (ja que s molt curta) i la nostra capacitat intellectual ens permet deduir amb rapidesa que primer cal efectuar el producte i posteriorment la suma, ja que matemticament un producte t prioritat davant una suma. Ara b, i si us demanem que em calculeu el resultat duna expressi com 3 ^ 2 * log 25 43 div 4 / 25 + 2 * 43 mod 21? Ens sembla que no sereu tan rpids com abans i efectuareu una avaluaci acurada de la prioritat dels operadors. Lordinador actua sempre de la mateixa manera; tant li fa si lexpressi s curta o si s llarga. Per resoldre lavaluaci es pot treballar amb dues piles: en la primera semmagatzemen els valors dels operands i de les avaluacions parcials i en la segona semmagatzemen els operadors pendents dexecuci. Fem-ne el seguiment amb lexpressi 10 + 2 3 * 5 + 4. Considerarem que Val (...) s la pila que cont els valors i que Ope (...) s la pila que cont els operadors. Partim de la situaci inicial en qu les dues piles sn buides: Val () i Ope (). Recepci del valor 10. No es pot fer altra cosa que apilar-lo a la pila de valors. Val (10) Ope()

Recepci de loperador +. No es pot fer altra cosa que apilar-lo a la pila doperadors i esperar el valor segent. Val (10) Ope (+)

Recepci del valor 2. No es pot fer altra cosa que apilar-lo a la pila de valors i esperar loperador segent, ja que si s ms prioritari que loperador que hi ha actualment en el top de la pila Ope no es pot operar amb el valor que hi ha actualment en el top de la pila Val. Val (10,2) Ope (+)

Recepci de loperador -. Com que el top de la pila Ope t un smbol +, que t la mateixa prioritat que el smbol que est arribant, estem en condicions dexecutar loperaci pendent + amb els valors existents en la pila Val. Per aquest motiu desapilem dos valors de la pila

Estructures de dades dinmiques

44

Dades lineals en memria dinmica

Val (2 i 10) i un operador de la pila Ope (+), fem loperaci corresponent (10 + 2) i emmagatzemem el resultat (12) en la pila Val. Si analitzem el top de la pila Ope, observem que no hi ha cap execuci pendent possible. Cal apilar el nou operador en la pila de valors i esperar el valor segent. Val (12) Ope (-)

Recepci del valor 3. No es pot fer altra cosa que apilar-lo en la pila de valors i esperar loperador segent, ja que si s ms prioritari que loperador que hi ha actualment en el top de la pila Ope, no es pot operar amb el valor que hi ha actualment en el top de la pila Val. Val (12,3) Ope (-)

Recepci de loperador *. Com que el top de la pila Ope t un smbol , que t menys prioritat que el smbol que est arribant, sha demmagatzemar loperador entrant i executar-lo quan es pugui, per sempre abans que lactual top de la pila Ope. Val (12,3) Ope (-,*)

Recepci del valor 5. No es pot fer altra cosa que apilar-lo en la pila de valors i esperar loperador segent, ja que si s ms prioritari que loperador que hi ha actualment en el top de la pila Ope, no es pot operar amb el valor que hi ha actualment en el top de la pila Val. Val (12,3,5) Ope (-,*)

Recepci de loperador +. Com que el top de la pila Ope t un smbol *, que t ms prioritat que el smbol que est arribant, estem en condicions dexecutar loperaci pendent * amb els valors existents en la pila Val. Per aquest motiu, desapilem dos valors de la pila Val (5 i 3) i un operador de la pila Ope (*), fem loperaci corresponent (3 * 5) i emmagatzemem el resultat (15) en la pila Val. Ens queda aquesta situaci: Val (12,15) Ope (-)

Encara ara, el top de la pila Ope t un smbol que t la mateixa prioritat que el smbol + que est arribant i, per tant, estem en condicions dexecutar loperaci pendent amb els valors existents en la pila Val. Per aquest motiu, desapilem dos valors de la pila Val (15 i 12) i un operador de la pila Ope (), fem loperaci corresponent (12 15) i emmagatzemem el resultat (3) en la pila Val. Si analitzem el top de la pila Ope, observem que no hi ha cap execuci pendent possible. Cal apilar el nou operador + en la pila de valors i esperar el valor segent. Val (-3) Ope (+)

Estructures de dades dinmiques

45

Dades lineals en memria dinmica

Recepci del valor 4. Com que detectem que s el darrer element de lexpressi, cal operar-lo a travs de loperador existent en el top de la pila Ope amb el valor existent en el top de la pila Val. Per aix desapilem loperador (+) de la pila Ope i loperand (3) de la pila Val, efectuem la darrera operaci (3 + 4) i obtenim el resultat (1) final de lexpressi inicial. Val () Ope ()

Evidentment, abans defectuar lavaluaci en temps dexecuci, el compilador haur efectuat una anlisi sintctica de lexpressi en temps de compilaci per garantir que en temps dexecuci no hi haur problemes sintctics. El procs davaluaci del compilador tamb utilitza piles. En el procs anterior comentvem, a cada pas, que mirvem el valor del top de les piles. Aix noms ho podrem fer si disposem de loperaci capdamunt. En cas que no es faciliti aquesta operaci, no t sentit executar les operacions dapilar i desapilar per consultar el valor del top. En aquest cas, podrem mantenir en altres variables els valors dels tops de les piles i desapilar noms quan fos estrictament necessari per efectuar els clculs corresponents. d) Algorisme dordenaci rpida El mtode dordenaci rpida, conegut com a quicksort, s un mtode fcilment programable utilitzant recursivitat. Per tant, es pot tenir una versi no recursiva amb la utilitzaci de piles.

3.2. Cues

Una cua s un conjunt lineal (ja que els seus components ocupen llocs successius dins el conjunt) delements en el qual aquests sintrodueixen per un extrem i seliminen per laltre. En conseqncia, els elements duna cua seran eliminats en el mateix ordre en qu es van inserir.

A causa del funcionament dinserci i eliminaci, les cues es coneixen com a estructures FIFO (first in - first out), ja que el primer element que va ser inserit (first in) s el primer a ser eliminat (first out). La noci de cua ens s familiar: cua en una taquilla dun cinema, en un taulell duna botiga... Per obtenir una entrada ens colloquem al final de la cua

Estructures de dades dinmiques

46

Dades lineals en memria dinmica

i esperem arribar a linici. Quan ens donen lentrada, abandonem la cua. En aquest punt cal deixar clar que entenem que hi pugui haver persones que pensin que linici de la cua s lextrem on elles se situen quan sincorporen a la cua i que el final sigui lextrem pel qual abandonen la cua. No cal discutir. Aqu, considerarem que lextrem final de la cua s aquell pel qual un element sincorpora a la cua i lextrem inici s aquell pel qual un element abandona la cua. Dacord? Amb les cues solament es poden efectuar dues operacions: encuar i desencuar. ! Hi ha autors que permeten dues operacions ms: inici, pensada per consultar lelement que s a punt dabandonar la cua (lelement que es troba a linici de la cua) i final, pensada per consultar el darrer element que ha arribat a la cua (lelement que es troba al final de la cua). Nosaltres permetrem la utilitzaci daquestes dues operacions. Tornem-ho a repetir. Tot i que les implementacions de cua ho permetin, el concepte de tipus de dada cua porta implcit que no shi pugui fer altra cosa que les dues (o quatre) operacions esmentades, s a dir, no es pot realitzar un recorregut per una cua, no es pot efectuar una recerca per una cua, no es pot treure lelement del final... !

3.2.1. Implementaci del tipus de dada cua


Tradicionalment hi ha dues implementacions del tipus de dada cua: a) Mitjanant taules En aquest cas, en la creaci de la cua cal definir la grandria mxima de la cua i tenir dues variables enteres per apuntar linici i el final de la cua. La figura 11 ens mostra la implementaci duna cua mitjanant una taula de grandria M+1 que ja cont dos elements. Observem que la variable inici apunta la casella que cont el primer element incorporat a la cua i la variable final apunta la casella que cont el darrer element incorporat a la fila. Aquesta implementaci t, de manera semblant a les piles, un problema: lespai reservat de memria s fix i no pot augmentar ni disminuir. A ms, en les cues, com que interessa un s eficient de la memria, cal tractar la taula amb la qual implementem la cua com una estructura circular i fer que les eliminacions deixin espai per a futures insercions. s a dir, suposant que implementem la cua amb una taula de M+1 posicions, tenim que: Si inici==0, llavors obligatriament final>=inici, i resulta que la cua est ocupant el tros de taula que va des de la posici 0 fins a la poFigura 11. Implementaci duna cua mitjanant una taula

Estructures de dades dinmiques

47

Dades lineals en memria dinmica

sici final. Notem-ho [0,...,final]. s el cas que sobserva en la figura 11. Si inici>1, llavors tenim dues possibilitats respecte al valor de final: Si final>=inici, llavors la cua est ocupant el tros de taula que va des de la posici inici fins a la posici final. s a dir [inici,...,final]. s el cas exemplificat en la situaci 1 de la figura 12. Si final<inici, llavors la cua est ocupant dos trossos de taula: el que va des de la posici inici fins a la posici M i el que va des de la posici 0 fins a la posici final. O sia, [inici,...,M] U [0,...,final]. s el cas exemplificat en la situaci 2 de la figura 12. a) Mitjanant llistes simplement encadenades ! En aquest cas, es tracta de crear una llista simplement encadenada, per amb un segon punter que apunti el darrer element de la llista. La figura 13 ens ho exemplifica. Les dues operacions sobre cues consisteixen en dues de les operacions sobre llistes simplement encadenades: Encuar un element s equivalent a afegir un element pel final de la llista (zona F de la figura 13). Aquesta operaci podr ser codificada de manera molt ms eficient que en les llistes simplement encadenades amb un nic punter a linici (abans calia fer el recorregut de tota la llista per arribar al final) grcies a laparici del punter al node final. Desencuar un element s equivalent a esborrar un element per linici de la llista (per la zona I de la figura 13) i recollir lelement eliminat de la cua via parmetre per referncia. A ms, s interessant tenir una operaci per eliminar la cua, la qual s equivalent a loperaci corresponent per eliminar una llista.
Figura 13. Implementaci duna cua mitjanant una llista simplement encadenada i 2 punters Figura 12. Estat duna cua, implementada mitjanant una taula, al llarg del temps

En aquesta implementaci lnica limitaci es troba en la quantitat de memria disponible. La cua pot crixer i disminuir segons les necessitats. Per tant, s la millor opci.

Estructures de dades dinmiques

48

Dades lineals en memria dinmica

Com que les dues operacions no seran exactament iguals que en el tractament de llistes simplement encadenades a causa de laparici del segon punter, en presentarem el disseny.

funci encuar (var inici:^node, var final:^node, element:T) retorna natural s /* Arguments: inici: Punter que apunta linici de la cua /* per on es desencua */ final: Punter que apunta el final de la cua /* per on sencua */ element: Dada a afegir a la cua Retorna: 0 : Operaci correcta 1 : Error per manca de memria */ var aux:^node; fivar assignar_memria (aux); si aux == NUL llavors retorna 1; fisi aux^.inf = element; aux^.seg = NUL; si final == NUL llavors inici = aux; final = aux; /* la cua era buida */ sin final^.seg = aux; final = aux; /* la cua no era buida */ fisi retorna 0; fifunci

Observeu que, tot i que lencuament es produeix pel final de la cua, cal passar tots dos punters, inici i final, per referncia. El punter final sempre queda modificat desprs duna execuci de la funci encuar excepte si es produeix un error per manca de memria. En canvi, el punter inici quedar modificat desprs duna execuci de la funci encuar en cas que la cua sigui buida.

funci desencuar (var inici:^node, var final:^node, var element:T) retorna natural s /* Arguments: inici: Punter que apunta linici de la cua /* per on es desencua */ final: Punter que apunta el final de la cua /* per on sencua */ element: Variable on recollir la dada desencuada Retorna: 0 : Operaci correcta 1 : Error perqu la cua s buida */ var aux:^node; fivar si inici == NUL llavors retorna 1; fisi aux = inici; element = inici^.inf; inici = inici^.seg; si inici == NUL llavors final = NUL; fisi alliberar_memria (aux); retorna 0; fifunci

Observeu que, tot i que el desencuament es produeix per linici de la cua, cal passar tots dos punters, inici i final, per referncia. El punter inici sempre queda modificat desprs duna execuci de la funci desencuar excepte si la cua era buida. En canvi, el punter final quedar modificat desprs duna execuci de la funci desencuar en cas que la cua noms tingui un element. Llavors es convertir en una cua buida.

Estructures de dades dinmiques

49

Dades lineals en memria dinmica

Podrem considerar el tipus cua que contingus els dos punters de la forma segent:
tipus cua: tuple inici, final: ^node; fituple

En aquest cas, les dues funcions per a la gesti de cues tindrien les declaracions segents:
funci encuar (var c: cua, element:T) retorna natural s funci desencuar (var c: cua, var element:T) retorna natural s

Evidentment, cal adequar la definici de les dues funcions als nous arguments. Aquesta manera de fer t un avantatge: largument c porta incorporats els dos punters a linici i al final de la cua. Daquesta manera, si en un programa es treballa amb diverses cues, no hi ha possibilitat dequivocar-se en el pas dels parmetres ni es poden estar barrejant punters a diferents cues.

3.2.2. Aplicacions de les cues


Les cues sutilitzen molt en informtica. Sacostumen a aplicar en els processos que necessiten una gesti dordre FIFO, com per exemple: a) Aplicacions informtiques especfiques que gestionen reserves (hotels, avions, trens, preinscripci universitria...). b) Processos dels mateixos sistemes operatius per resoldre les demandes dassignaci de recursos en lexplotaci diria dels sistemes (sistemes dimpressi, treballs a realitzar, assignaci de memria als processos...). Tot i que aquest segon camp daplicaci pot fer la sensaci que existeix noms en grans sistemes informtics, cal tenir en compte que en qualsevol xarxa drea local per petita que sigui i per infrautilitzada que estigui en la qual hi hagi alguna impressora compartida, lordinador que gestiona la impressora ha de tractar adequadament les peticions dimpressi dels diferents ordinadors de la xarxa. Fins i tot en un sistema monousuari com el DOS hi havia un sistema dimpressi que gestionava les cues dimpressi de les diferents impressores connectades a lordinador.

Estructures de dades dinmiques

50

Dades lineals en memria dinmica

You might also like