Estructuras de Datos Implementación de Listas. 1. Mediante arreglos de tamaño fijo 2. Mediante...

Post on 07-Feb-2015

10 views 0 download

Transcript of Estructuras de Datos Implementación de Listas. 1. Mediante arreglos de tamaño fijo 2. Mediante...

Estructuras de Datos

Implementación de Listas

1. Mediante arreglos de tamaño fijo2. Mediante arreglos de tamaño variable3. Mediante listas ligadas

En todos los casos los elementos de la lista son de tipo stdelement (elementos con campo clave que los hace únicos)

Alternativas

Alternativas 1 y 2:

• En cada posición del arreglo se guarda un elemento de tipo stdelement

• En el caso del arreglo estático se define (reserva) un número de elementos suficiente desde el inicio del programa

• En el caso del arreglo dinámico, por medio de las función realloc() se puede expandir (debido

a una inserción) y contraer (debido a un borrado) el arreglo a medida que se requiera

• Ambas alternativas implican que se realicen “corrimientos” de elementos tanto en inserciones como en borrados

• Las operaciones de navegación con el cursor son bastante simples (se utiliza el índice del arreglo)

• Nos concentraremos en la alternativa 3 aunque a continuación se verán los fundamentos de la alternativa 2

Función Realloc()

• Es una potente función para el manejo de memoria dinámica que permite crear y manipular arreglos dinámicos (arreglos que cambian de tamaño durante la ejecución), su sintaxis es:

puntero = (tipo_de_variable *) realloc( direccion_de_mem, numero_de_bytes );

• Está en stdlib.h

• dirección_de_mem es la dirección del espacio de memoria cuyo tamaño se desea modificar, si dirección_de_mem es nulo, realloc() reservará un nuevo espacio de memoria

• El argumento numero_de_bytes es el nuevo tamaño que se desea asignar al bloque referenciado por dirección_de_mem. Si numero_de_bytes es 0 (cero) este bloque es liberado.

• realloc() devuelve la dirección del espacio de memoria ya contraído o expandido Esta dirección puede ser diferente a dirección_mem ¿por qué?

Veamos un ejemplo:

int *cambiar_tamano( int *arreglo, int n ){

int *r;r = (int *)realloc(arreglo, n*sizeof(int));return r;

}

Dirección inicial Número de elementosdeseados

void main(){

int *arreglo; //Se crea

arreglo = cambiar_tamano( NULL, 5 ); arreglo[2] = 4;

//Se expandearreglo = cambiar_tamano( arreglo, 10 );

// Se destruye arreglo = cambiar_tamano( arreglo, 0 );}

Supóngase que se va a manejar una lista de estudiantes:

class estudiante{public: int aCarne;public: estudiante(); // Constructor por defecto estudiante(int carne);};

estudiante::estudiante(){ aCarne = 0;}

estudiante::estudiante(int carne){ aCarne = carne;}

class lista{public: estudiante *primero; // Puntero para el manejo del vector que

// representa la lista de estudiantes int n; // Número de estudiantes int cursor; // Posición del elemento actualpublic:

//Constructorlista();//Destructoravoid destruir_lista(lista *l);//Inserciónvoid inserte_antes(estudiante e);

};

void lista::destruir_lista(lista *l){ if(n == 0) delete l; else cout<<"La lista debe ser vacia";}

lista::lista(){ primero = NULL; n = 0; cursor = 0;}

void lista::inserte_antes(estudiante e){ int i; primero=(estudiante*)realloc((primero),

(n+1)*sizeof(estudiante)); if(n == 0) { primero[0] = e; cursor++; // El cursor queda valiendo 1 pero se ocupa la // posición 0 } else { for(i=0; i<=(n-cursor); i++){ primero[(n-i)] = primero[((n-1)-i)]; } primero[cursor-1] = e; }n++;}

void main(){ estudiante e1(100); lista milista;

cout << milista.n << milista.cursor; milista.inserte_antes(e1); cout<< milista.primero[0].aCarne; estudiante e2(200); milista.inserte_antes(e2); cout<< milista.primero[0].aCarne; cout<< milista.primero[1].aCarne;}

Alternativa 3

• Las listas pueden ser:

–Simplemente ligadas–Simples circulares–Doblemente ligadas–Dobles circulares–Todas las anteriores y con registro cabeza

• Nos concentraremos en las listas doblemente ligadas no circulares sin registro cabeza

• En la implementación doblemente ligada, cada elemento de la lista está representado por un nodo

• Un nodo contiene, además del stdelement que se desea almacenar en la lista un par de punteros, uno de ellos apunta al siguiente nodo de la lista y el otro al nodo anterior

• Una lista doblemente enlazada es un conjunto de nodos conectados entre sí

• Cada nodo tiene asociado una dirección de memoria

• Los nodos no necesariamente tienen que estar en posiciones contiguas de memoria

NULL NULLe2 e3e1

Una lista doblemente enlazada de estudiantes:e1,e2, e3 son estudiantes

[43] [81][76]

[76] [43] [81] [76]

Direcciones de los nodos

El operador flecha ->#include <iostream.h>

struct persona{ int ced;

};

void main(void){ struct persona p1,*p2;

p1.ced=4;p2 = &p1;cout<< p2->ced;cout<< (*p2).ced;

}

La clase nodoclass nodo{ public: nodo *siguiente; nodo *anterior; estudiante e; public: //Constructor nodo(nodo *sig, nodo *ant, estudiante e1);};

//Implementación Constructornodo::nodo(nodo *sig, nodo *ant, estudiante e1){siguiente=sig;anterior=ant;e=e1;}

class lista{ public: nodo *primero; // Apunta al primer elemento de la

lista nodo *cursor; // Apunta a un nodo de la lista int n; // Número de elementos de la listapublic://Constructor lista();//Destructora void destruir_lista(lista *l);//Construcción void inserte_antes(estudiante e);};

lista::lista(){ primero = NULL; n = 0; cursor = NULL;}

void lista::destruir_lista(lista *l){ if(l->cursor == NULL) delete l; else cout<<"Error: la lista debe ser vacia";}

void lista::inserte_antes(estudiante e){nodo *nnodo;nnodo = new nodo(NULL, NULL, e); int cmb_primero = 0;

if(cursor !=NULL && cursor->anterior != NULL)//Si hay anterior

{ nnodo->anterior=cursor->anterior; (cursor->anterior)->siguiente=nnodo;}else //Ya sea que cursor = NULL o que cursor esté en el primero{ nnodo->anterior=NULL; cmb_primero=1; //Activa bandera para primero}

CONTINUA

if(primero != NULL) //Si la lista era no vacía{ nnodo->siguiente=cursor; cursor->anterior=nnodo;}else //Si la lista era vacía{ nnodo->siguiente=NULL; cmb_primero=1;}if( cmb_primero){ primero=nnodo; //Cambio de primero} cursor=nnodo; //El cursor queda en el actual n++; //Incrementa el número de elementos}

void main(){ estudiante e1(100); lista milista;

cout << milista.n <<endl; milista.inserte_antes(e1); cout << (milista.primero)->e.aCarne<<endl; cout << (milista.cursor)->e.aCarne<<endl; cout << milista.n << endl; estudiante e2(200); milista.inserte_antes(e2); cout << (milista.primero)->e.aCarne<<endl; cout << (milista.cursor)->e.aCarne<<endl; cout << milista.n<<endl; }

Imprime: 0 100 100 1 200 200 2

//Veamos otro main() que hace uso de la lista anterior:void main(){ estudiante eaux; lista milista; int carnet, n;

cout<< "Nro de estudiantes a ingresar: "; cin >> n; for(int k=1; k<=n; k++) { cout<<"Carnet: " << endl; cin>> carnet; eaux.aCarne = carnet; milista.inserte_antes(eaux); } El main()

Continúa

//Recorrer la lista desde el primero hasta el final: nodo *ptnodo; ptnodo = milista.primero; while(ptnodo != NULL) {

cout << ptnodo->e.aCarne << endl;ptnodo = ptnodo ->siguiente;

}/*Este ciclo imprime los datos en el orden

inverso a la entrada...*/}

Fin del main()

Ahora se va a añadir a la clase lista la función ultimo():Se agrega a la clase la siguiente función:

nodo *ultimo();

Y la implementación correspondiente es:

nodo *lista::ultimo(){

nodo *ult=NULL,*aux=primero;//aux también podría iniciarse en el cursor...while (aux != NULL){ ult = aux; aux = aux ->siguiente;}return ult;

}

• Ahora se puede recorrer la lista desde el último hasta el primero así:

cout<<"Del último al primero"<<endl; ptnodo = milista.ultimo(); while(ptnodo != NULL) {

cout << ptnodo->e.aCarne << endl;ptnodo = ptnodo->anterior;

}

Se pueden crear igualmente funciones recursivas para el recorrido por ejemplo:

void imprimir1(nodo *ptnodo){

if (ptnodo != NULL ){

cout << ptnodo ->e.aCarne << endl;imprimir1(ptnodo ->siguiente);

}}

void imprimir2(nodo *ptnodo){

if (ptnodo != NULL ){

imprimir2(ptnodo ->siguiente);cout << ptnodo ->e.aCarne << endl;

}}

Desde el main() se pueden invocar así:

void main(){ estudiante eaux; lista milista; int carnet, n;

cout<< "Nro de estudiantes a ingresar: "; cin >> n; for(int k=1; k<=n; k++) { cout<<"Carnet: " << endl; cin>> carnet; eaux.aCarne = carnet; milista.inserte_antes(eaux); } imprimir1(milista.primero); imprimir2(milista.primero);}

Ahora agregará a la lista la función existe(k), se asumirá el carnet como la clave. Pero primero se agrega a la clase estudiante la función clave() así:

class estudiante{ public: int aCarne; public: estudiante(); estudiante(int carne); int clave();};

int estudiante::clave(){ return aCarne; }

Clave

Ahora se agrega a la clase lista la función existe(k) así: int existe(int k);

Y la implementación es:lista::existe(int k){

nodo *pt=primero;while(pt != NULL){

if(pt ->e.clave() == k ){return 1;

} //verdadero, encontradopt=pt->siguiente;

}return 0; // no encontrado

}

Agregar a la clase la siguiente función y su correspondiente implementación:

lista::existe(int k){

nodo *pt=primero;while(pt != NULL){

if(pt ->e.clave() == k ){return 1; //Verdadero, encontrado

}pt=pt->siguiente;

}return 0; // No encontrado

}

El main() para ejemplificar la invocación es:

void main(){ estudiante eaux; lista milista; int carnet, n;

cout<< "Nro de estudiantes a ingresar: "; cin >> n; for(int k=1; k<=n; k++) { cout<<"Carnet: " << endl; cin>> carnet; eaux.aCarne = carnet; milista.inserte_antes(eaux); }cout << milista.existe(3);cout << milista.existe(8);}