1 Capítulo 2. Definición de Estructuras de Datos en C. Se desea conocer las técnicas para diseñar estructuras de datos. Se repasan los conceptos de tipos básicos, para luego desarrollar las herramientas de creación de nuevas estructuras, haciendo énfasis en los conceptos de agrupación y vinculación. Dando ejemplos que posteriormente se emplearán en el texto. 2.1. Tipos primitivos. En los lenguajes de programación suele disponerse de tipos básicos o primitivos: caracteres, enteros con y sin signo, reales o flotantes de simple y doble precisión. Se dispone la forma de definir e inicializar variables de esos tipos, y también la forma de escribir constantes de esos tipos. Para los tipos primitivos existen numerosos y variados operadores que permiten construir expresiones con variables y constantes de dichos tipos. En determinados casos existen mecanismos automáticos para convertir valores de un tipo en otro, sin embargo es preferible la conversión explícita a través del molde o “cast”. Mediante las bibliotecas de entrada-salida pueden ingresarse o desplegarse valores de esos tipos. 2.2. Mecanismos de estructuración. Existen dos mecanismos básicos de estructuración de datos: la agrupación y la vinculación. 2.2.1. Grupos básicos. La estructura (struct) permite agrupar elementos de diferente tipo. El arreglo agrupa elementos de igual tipo. El string agrupa caracteres. Profesor Leopoldo Silva Bijit 20-01-2010 2 Estructuras de Datos y Algoritmos Estructura Arreglo String Figura 2.1. Agrupaciones de datos. Se dispone de una forma de definir variables de esos grupos básicos, y los mecanismos de acceso a las componentes: el discriminador del campo (un punto) para las estructuras; y los paréntesis cuadrados o corchetes para delimitar el índice de la componente del arreglo. Las operaciones que están definidas para estos grupos básicos son muy reducidas, el programador debe crear sus propias funciones o métodos para efectuar operaciones más complejas sobre estos grupos básicos. En el caso de strings existe una biblioteca estándar de funciones que los manipulan (ver Apéndice 2). Las agrupaciones pueden ser tan complejas como sea necesario: ya que es posible crear arreglos de estructuras, y también estructuras que contengan arreglos y strings. 2.2.2. Vínculos. Pueden establecerse relaciones entre elementos de datos. Los siguientes diagramas ilustran las formas básicas de vinculación: vínculos de orden o secuencia en listas, relaciones jerárquicas en árboles, e interconexiones complejas en grafos. árboles listas grafos Figura 2.2. Vínculos entre componentes de datos. Profesor Leopoldo Silva Bijit 20-01-2010 Definición de Estructuras de Datos en C. 3 El vínculo es el elemento de datos de una componente que permite accesar a otra componente. Una componente puede contener información de uno o más vínculos. El programador debe disponer de elementos del lenguaje que le permitan crear nuevos tipos de datos, y mediante estos tipos crear funciones o métodos que realicen operaciones sobre las componentes. Cuando el número máximo de componentes es conocido de antemano, las componentes pueden modelarse como una serie de arreglos agrupados en una estructura. En la cual se emplea uno o varios arreglos para establecer el o los vínculos entre las componentes. Se suele definir como cursor a una variable que contiene el valor del índice de una componente. Otro mecanismo para establecer vínculos es el puntero, que es una variable que contiene la dirección de otra componente. La vinculación a través de punteros suele emplearse preferentemente en situaciones dinámicas, en las cuales el número de las componentes varía durante la ejecución del programa; esto debido a que se crean, insertan y descartan componentes. 2.3. Ejemplos basados en arreglos. 2.3.1. Acceso a componentes del arreglo. El acceso a componentes se realiza vía indirección de un puntero constante que es el nombre del arreglo, con un offset dado por el índice del elemento. Se definen tipos de datos. typedef int Tipo; /* tipo de item del arreglo */ typedef int Indice; /* tipo del índice */ Es práctica usual, definir mediante una constante el tamaño máximo del arreglo #define MaxEntradas 10 Se definen e inician con valores dos arreglos Tipo A[MaxEntradas]={0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; Tipo B[MaxEntradas]={ 10,11,12,13,14,15,16,17,18,19}; A las funciones que manipulan arreglos se les suele pasar el arreglo por referencia. Por ejemplo la función que imprime en una línea las componentes de un arreglo a. void mostrar(Tipo *a, Indice inferior, Indice superior) { Indice i; for (i = inferior; i <= superior; i++) printf(" %d ", a[i]); putchar('\n'); } Note que el primer argumento de la función también puede escribirse: Tipo a[]. Profesor Leopoldo Silva Bijit 20-01-2010 4 Estructuras de Datos y Algoritmos Ejemplos de uso: mostrar(A, 0, 4); imprime 0 1 2 3 4 mostrar(A+2, 0, 4); imprime 2 3 4 5 6 La manipulación de arreglos no suele verificar el rango del índice. En el caso de haberse definido adyacentes los arreglos A y B, la referencia a A[12] está accesando efectivamente a B[2], por eso se muestra un 12; pero es un error por acceso fuera de rango. printf(" %d printf(" %d printf(" %d printf(" %d printf(" %d \n", A[12]); imprime 12 \n", B[-2]); imprime 8 \n", A[-1]); imprime cualquier cosa que estuviera almacenada antes de A \n", 1[-1+B] ); imprime 10 \n", -1[B] ); imprime -11 (es una referencia correcta) Similares errores pueden generarse accesando componentes indireccionando vía puntero: printf(" %d \n", *(A+12)); imprime 12 printf(" %d \n", *(-2+B)); imprime 8 Los ejemplos anteriores ilustran que el acceso a componentes de un arreglo se efectúa mediante indirección con el puntero que es el nombre del arreglo. La expresión: A[c] se interpreta como el contenido de la dirección: A + c*(tamaño del tipo del arreglo). Donde A es la dirección de la primera componente del arreglo. Empleando el lenguaje: A[c] equivale a: *(A+c) y A es equivalente a &A[0]. Definición de matrices. Puede definirse una matriz de caracteres, arr, de R renglones y C columnas mediante: #define R 8 //renglones #define C 10 //columnas char arr[R][C]; arr arr[3][5] arr[3] Figura 2.2.a. Matriz de caracteres Profesor Leopoldo Silva Bijit 20-01-2010 Definición de Estructuras de Datos en C. 5 Entonces: arr[3] es el cuarto renglón de la matriz arr, y es un arreglo de C caracteres. Puede considerarse que arr[3] es la dirección de inicio de ese arreglo; es decir, es un puntero constante que indica la primera componente de ese arreglo. Puede escribirse, usando notación de punteros como: *(arr+3). La expresión: a[3][5], denota el sexto carácter del cuarto renglón. La cual puede escribirse, usando notación con punteros, mediante: * ( *(arr+3) + 5 ). Puede concluirse que arr es un puntero a un puntero a carácter. Definición de arreglo de arreglos. Empleando definición de tipos puede crearse el tipo renglón, como un arreglo de C caracteres. Y mediante este nuevo tipo puede definirse una matriz ar, como un arreglo de renglones. Definiciones que se muestran a continuación. #define R 8 //renglones #define C 10 //columnas typedef char renglon[C]; renglon ar[R]; La expresión ar[3] es el cuarto renglón. La expresión: * ( *(ar+3) + 5 ) corresponde al sexto carácter almacenado en el cuarto renglón, y es equivalente a: ar[3][5]. Con el mismo procedimiento se pueden estructurar organizaciones más complejas. Por ejemplo una matriz cuyo contenido sea un arreglo. Los arreglos aseguran que las componentes quedan adyacentes en memoria. En el caso de matrices, el último elemento del primer renglón queda adyacente con el primer elemento del segundo renglón. Arreglo de punteros a renglones. Un ejemplo de creación de estructuras de datos, empleando el lenguaje C, es diseñar un arreglo de punteros pt, que apunten a los renglones de una matriz. pt ar (*pt[2])[5] Figura 2.2.b. Arreglo de punteros a arreglos de caracteres Profesor Leopoldo Silva Bijit 20-01-2010 6 Estructuras de Datos y Algoritmos Las siguientes declaraciones de tipos y definiciones de variables, crean el espacio que almacenará a la estructura de datos. typedef char renglon[C]; //definición de tipo renglón como un arreglo de C caracteres typedef renglon * prenglon; //definición del tipo puntero a renglón renglon ar[R]; //Matriz de caracteres como arreglo de renglones prenglon pt[R]; //Arreglo de punteros a los renglones. Es preciso vincular los punteros del arreglo con los renglones, lo cual se efectúa con: for(i=0; i<R; i++) pt[i]=&ar[i]; //llena arreglo de punteros a renglones. Entonces: pt[2] contiene un puntero al tercer renglón. Lo cual puede escribirse: *(pt +2 ). La expresión: *(pt[2]) es el tercer renglón, que en este caso es un arreglo de C caracteres; es decir: *( *(pt+2)) es el valor del puntero al inicio de ese renglón. Y consecuentemente: *( *(pt+2)) +5 es el puntero al sexto carácter del tercer renglón. Finalmente para accesar al elemento ubicado en el tercer renglón y en la sexta columna, debe indireccionarse el puntero anterior, lo cual puede escribirse: *( *( *(pt+2)) +5), o empleando notación de arreglos: (*pt[2])[5] . Una alternativa a lo anterior es convertir el puntero a renglón en un puntero a carácter, esto se logra mediante: (char *) pt[2]; entonces el acceso a un carácter, a través de punteros a renglones se logra con: ((char*) pt[i])[j]. Arreglo de punteros a caracteres. Puede lograrse una representación más simple, si se almacenan en un arreglo punteros a caracteres, en lugar de punteros a renglones. Esto puede modelarse según: typedef char renglon[C]; //definición de tipo renglon como un arreglo de C caracteres renglon ar[R]; // Arreglo de R renglones char * pch[R]; // Arreglo de punteros a caracteres Los vínculos de la estructura se producen con: for(i=0; i<R; i++) pch[i]=(char *) (&ar[i]); //llena arreglo de punteros a char La expresión: pch[i][j] accesa al carácter ubicado en el renglón (i+1) y en la columna (j+1). 2.3.2. Lista simplemente enlazada en base a cursores. #define MaxEntradas 4 #define fin_de_lista -1 Con los siguientes datos: Tipo Valor[MaxEntradas] ={1, 2, 3, 4}; Indice Cursor[MaxEntradas] ={2, 3, 4, fin_de_lista }; Puede visualizarse la siguiente lista. Profesor Leopoldo Silva Bijit 20-01-2010 Definición de Estructuras de Datos en C. 1 2 7 3 4 Figura 2.3. Lista simplemente enlazada. Se tiene, como ejemplo, que: Cursor[2] contiene el valor 4 de índice. 2.4. Ejemplos basados en estructuras. 2.4.1. Estructura para fecha. Agrupamos mediante la estructura fecha los campos: día, mes, año. //molde. Declaración. struct fecha { int dia; int mes; int agno; }; La declaración sólo establece un molde o patrón para la estructura. Se denominan instancias o definiciones de variables cuando se asocia un tipo a una variable. La definición considera una asignación de memoria para la variable. //instancias. struct fecha fecha1; //definición. struct fecha fecha2={1,5,2004}; //definición e inicialización Nótese que se requiere preceder con la palabra struct al nombre del molde. También se puede establecer una definición e inicialización de un puntero a la variable fecha1, mediante: struct fecha *ptfecha1=&fecha1; No puede accederse a los campos de una estructura no iniciada, para lectura. Se pueden copiar estructuras completas, mediante asignación: fecha1=fecha2; Puede escribirse en un campo determinado: fecha1.mes=11; // mediante el selector de campos ptfecha1->dia=25; //o a través del puntero La forma equivalente, vía indirección del puntero, no suele emplearse: *(ptfecha1.dia) = 25; Es preferible crear tipos, previo a la definición de variables de tipo estructura: Profesor Leopoldo Silva Bijit 20-01-2010 8 //Definiciones de tipos typedef struct fecha Fecha; typedef Fecha * pFecha; Estructuras de Datos y Algoritmos //hay diferencia entre mayúsculas y minúsculas. //define puntero a elemento de tipo Fecha. En este ámbito, las definiciones de las instancias para las variables, quedan: Fecha fecha3 = {1, 5, 2004}; //definición e inicialización. pFecha pfecha3=&fecha3; Observar que las definiciones no son precedidas por struct. Es preciso crear métodos para desplegar variables que son estructuras: Se ilustra pasar el argumento como una estructura. Lo cual implica: crear el espacio y copiar la estructura en el frame de la función: void printfecha(Fecha f) //paso por valor { printf(" Día = %d Mes = %d Año = %d \n", f.dia, f.mes, f.agno); //lectura de campos } Es preferible pasar una referencia a la estructura, ya que esto ocupa menos espacio en el frame. Sólo es preciso copiar un puntero. void printfecharef(pfecha p) //paso por referencia. { printf(" Día = %d Mes = %d Año = %d \n", p->dia, (*p).mes, p->agno); } Ejemplos de uso: printfecha(fecha3); printfecharef(pfecha3); También es útil disponer de un constructor de la estructura. Fecha setfecha (int dia, int mes, int agno) //retorno de estructura { Fecha f; f.dia=dia; f.mes=mes; f.agno= agno; //escritura en campos return(f); } Ejemplo de uso: fecha3 = setfecha(15, 6, 2005); También suele ser necesario desarrollar funciones para realizar operaciones de comparación entre estructuras. El siguiente prototipo ilustra un operador de comparación “mayor o igual que”, al cual se le pasan punteros a estructuras y devuelve un puntero a la estructura que es mayor o igual que la otra, el diseño de la función se deja como tarea. pFecha fechaGET(pFecha pf1, pFecha pf2); Profesor Leopoldo Silva Bijit 20-01-2010 Definición de Estructuras de Datos en C. 9 Se pueden crear arreglos de estructuras de tipo Fecha: Fecha ArrFecha[3]={ {1,1,2005},{30,6,2005},{31,12,2005}}; printf("mes %d \n", (ArrFecha+1)->mes); imprime 6 printf("dia %d \n", ArrFecha[2].dia); imprime 31 Es responsabilidad del programador no exceder los rangos del arreglo. 2.4.2. Lista simplemente enlazada en base a punteros. Es posible declarar el molde y definir tipos simultáneamente. En esta situación en lugar de moldenodo se podría haber puesto cualquier otro identificador. Esta forma suele describirse como declaración anónima. typedef struct moldenodo { int clave; struct moldenodo *proximo; } nodo, *pnodo; //instancias. Definiciones. nodo nodo1={1,NULL}; nodo nodo2={2,NULL}; nodo nodo3={3,NULL}; pnodo lista=&nodo1; Las siguientes asignaciones crean los vínculos. lista->proximo = &nodo2; nodo2.proximo = &nodo3; lista 1 nodo1 2 3 nodo2 nodo3 Figura 2.4. Lista simplemente enlazada, mediante punteros. En caso de definir la lista en forma dinámica, sólo es preciso definir la variable lista. El espacio para los nodos se solicita a través de llamados a malloc; debido a esto los nodos no tienen un nombre y sólo se los puede accesar vía punteros. En el caso del ejemplo, los nodos se han definido en forma estática, y tienen un nombre de variable asociado. Profesor Leopoldo Silva Bijit 20-01-2010 10 Estructuras de Datos y Algoritmos 2.5. Estructuras más complejas. 2.5.1. Arreglo de listas. Pueden concebirse estructuras más complejas. Por ejemplo se desea disponer de un arreglo de punteros, donde cada elemento del arreglo es el inicio de una lista simplemente enlazada. Consideraremos que cada celda debe estar asociada a un string, donde los strings pueden ser de diferente largo. Se decide que no es conveniente almacenar los strings como arreglos de caracteres, esto implicaría un tamaño fijo para cada arreglo, con lo cual se ocuparía mal el espacio cuando deba almacenarse un string de pequeño largo, y la estructura puede colapsar si el largo del string es mayor que el tamaño máximo del arreglo. Es preferible asociar al nodo de la lista un puntero a un string, lo cual ocupa un tamaño fijo (normalmente los bytes ocupados por un entero). Definimos entonces los siguientes tipos: typedef struct moldecelda { char *nombre; struct moldecelda *next; } celda, *pcelda; #define BALDES 10 /* 10 celdas */ static pcelda hashtable[BALDES]; /*tabla punteros */ La palabra static, que precede a la definición, explícitamente indica que la tabla debe almacenarse en un segmento estático de la memoria. Es decir estará disponible, durante toda la ejecución del programa. Se ilustra un ejemplo de la estructura. Índice Tabla String0 0 1 2 …. B-1 String i String j String k Figura 2.5. Arreglo de listas. Profesor Leopoldo Silva Bijit 20-01-2010 Definición de Estructuras de Datos en C. 11 Veremos que se pueden desarrollar algoritmos eficientes para buscar strings almacenados en la tabla, cuando estudiemos tablas de hash. 2.5.2. Arreglo de estructuras. El siguiente ejemplo de diseño de una estructura de datos, contempla un arreglo en el cual se califica el estado de cada celda. Suponemos que se almacena un entero, y para describir lógicamente el estado empleamos la definición de un tipo enumerativo, que permite el mapeo de nombres lógicos con enteros. Internamente se emplean enteros, pero el programador puede referirse a ellos mediante sus nombres equivalentes. typedef enum {vacio, ocupado, descartado} state; typedef struct hcelda { int clave; state estado; } celda; #define B 10 /* 10 celdas */ static celda hashtab[B]; /*arreglo de celdas */ static int ocupados; //ocupados de la tabla El programador puede escribir expresiones en términos del tipo enumerativo: (hashtab[i].estado != vacio && hashtab[i].estado != descartado) 2.5.3. Multiárboles. La descripción de un nodo que pueda tener un número variable de punteros que apunten a sus descendientes no es práctica. Ya que esto implica almacenar los vínculos en un arreglo, y si es arreglo debe tener un tamaño fijo. El cual debe escogerse considerando el máximo número de descendientes que soportará la estructura; mal empleando los recursos en el caso de un nodo sin hijos, o de nodos con un pequeño número de descendientes. Además siempre estará el riesgo de que aparezca un nodo con mayor número de descendientes que el tamaño máximo escogido. 1 2 5 3 6 7 8 4 9 10 11 Figura 2.6. Multiárbol. Profesor Leopoldo Silva Bijit 20-01-2010 12 13 12 Estructuras de Datos y Algoritmos El esquema anterior ilustra los vínculos. Este ejemplo refuerza el concepto que un vínculo puede ser un cursor o un puntero. El número variable de los vínculos asociados a un nodo pueden reemplazarse por sólo dos vínculos: uno que relaciona el nodo con el primer descendiente izquierdo, y otro que vincula a cada nodo con su hermano derecho. Nótese que en cada nivel puede observarse una lista de nodos que parte del nodo ubicado más a la izquierda y termina en un vínculo nulo después del último descendiente de un nodo. Figura 2.7. Primer descendiente izquierdo, hermano derecho. Con esta visualización se requieren sólo dos vínculos por nodo. 2.5.3.1. Descripción mediante punteros. Una representación mediante punteros requiere definir la estructura del nodo según: typedef struct moldenodo { int valornodo; struct moldenodo *hijoizquierdo; struct moldenodo *hermanoderecho; struct moldenodo *padre; potros * datos_periféricos; } nodo, *pnodo; Donde se han definido los tipos asociados al nodo y al puntero a nodo. Se ha considerado que se almacenará un entero como clave o valor distintivo del nodo. Se ha agregado un puntero a una estructura que podría almacenar otros datos asociados al nodo, con el campo datos_periféricos. También se agrega un puntero al padre, ya que esta información reduce el tiempo de buscar al padre de un nodo determinado. 2.5.3.2. Descripción mediante arreglos de cursores. Se describe a continuación los valores que describen el multiárbol, de la Figura 2.6, mediante arreglos de cursores para el Hijo izquierdo y Hermano Derecho. Se ha considerado que un valor cero indica que no hay hijo izquierdo o hermano derecho o padre; si se desea usar el nodo cero, podría elegirse -1 como el cursor nulo. Profesor Leopoldo Silva Bijit 20-01-2010 Definición de Estructuras de Datos en C. 13 Índice Hijo izquierdo Hermano derecho Padre Clave Otros datos 0 0 0 0 --1 2 0 0 1 -2 5 3 1 2 -3 7 4 1 3 -4 10 0 1 4 -5 0 6 2 6 -6 0 0 2 6 -7 0 8 3 7 -8 0 9 3 8 -9 0 0 3 9 -… Figura 2.8. Multiárbol mediante arreglo de cursores. Se ha agregado un arreglo de cursores para especificar el padre del nodo. Esta información facilita la búsqueda del padre de un nodo dado. Un diseño de la estructura de datos considera al multiárbol como una estructura cuyos campos son los diferentes arreglos. #define MAXNODOS 13 struct moldemultiarbol { int Hijo_izquierdo[MAXNODOS]; int Hermano_derecho[MAXNODOS]; int Padre[MAXNODOS]; int clave[MAXNODOS]; int dato1[MAXNODOS]; } arbol; arbol.Hijo_derecho[4] tiene valor 0; lo cual indica que es el último descendiente. La estructura puede contener más o menos información dependiendo de costo de las operaciones que se deseen implementar. Por ejemplo si sólo se desea conocer la conectividad de los nodos del multiárbol, basta que la estructura aporte información para conocer quien es el padre de cada nodo, con esta descripción si luego se desea conocer los hijos de cada nodo, resultará un algoritmo costoso. 2.5.3.3. Descripción por arreglo de padres. Profesor Leopoldo Silva Bijit 20-01-2010 14 Estructuras de Datos y Algoritmos 0 1 4 2 5 6 3 7 8 9 10 11 12 Figura 2.9. Multiárbol mediante arreglo de padres. En un ambiente de grafos, es de interés describir un árbol que interconecta todos los vértices sin formar circuitos. Se ilustra un árbol de un grafo orientado, en el que se muestra el número asociado a cada vértice; se ha considerado el vértice 0, esto debido a que los arreglos en C, parten de índice cero. La información que describe el árbol anterior es: Vértice 0 1 2 3 4 5 6 7 8 9 10 11 12 Padre del vértice 0 0 0 0 1 1 2 2 2 3 3 3 3 Figura 2.10 Arreglo de padres. Empleando lenguaje de programación, queda: int PadreDelVertice[MAXNODOS]={0, 0, 0, 0, 1, 1, 2, 2, 2, 3, 3, 3, 3}; 2.6. Un ejemplo real de estructuras. La realidad suele ser más compleja que los casos idealizados que se exponen en los cursos básicos. El diseño de una estructura más realista, es la que describe los encabezados de los paquetes ICMP e IP en un ambiente de red. Primero se definen dos tipos básicos, con fines de compatibilidad. Se mapean los tipos del lenguaje C, a tipos lógicos. Si los tamaños de los tipos básicos de un compilador para un procesador específico son diferentes, basta cambiar el nombre de los tipos básicos. Toda la codificación no es necesario modificarla, ya que queda en términos de u8_t y u16_t, que son los tipos usados en el programa. typedef unsigned char u8_t; //es el tipo byte. typedef unsigned short u16_t; //el entero de 16 bits Profesor Leopoldo Silva Bijit 20-01-2010 Definición de Estructuras de Datos en C. 15 /* The ICMP and IP headers. */ typedef struct { /* IP header. */ u8_t vhl, tos, len[2], ipid[2], ipoffset[2], ttl, proto; u16_t ipchksum; u16_t srcipaddr[2], destipaddr[2]; /* ICMP (echo) header. */ u8_t type, icode; u16_t icmpchksum; u16_t id, seqno; } uip_icmpip_hdr; Problemas resueltos. P2.1. Se tiene el siguiente programa: #include <stdlib.h> #include <stdio.h> #define MAX 5 typedef struct nn { int i1; int i2; struct nn * next; } nodo, * pnodo; pnodo lista1=NULL; pnodo lista2=NULL; pnodo getnodo(void) { pnodo p; if( (p=(pnodo)malloc(sizeof(nodo)))==NULL) return(NULL); else return(p); } void Push(pnodo *ref, int dato1, int dato2) { pnodo newnodo; if ( (newnodo=getnodo( )) == NULL) exit(1); newnodo->i1 = dato1; newnodo->i2 = dato2; newnodo->next = *ref; Profesor Leopoldo Silva Bijit 20-01-2010 16 Estructuras de Datos y Algoritmos *ref = newnodo; } void crealista(void) { int i; for(i=0; i<MAX; i++) { Push(&lista1, i, i+1); Push(&lista2, MAX-i, i-1); } } pnodo busca(pnodo pp) { pnodo qq=NULL; while (pp !=NULL) {qq = pp; pp = pp->next;} return (qq); } pnodo busca2(pnodo p, int j, pnodo q) { if(p !=NULL) { while( p->i1 != j ) p = p->next; if (p != NULL) while (q !=NULL) { if(p->i2 == q->i1) return(q); q = q->next; } return (NULL); } return(NULL); } int main(void) { pnodo tt; crealista(); printf(" %d \n", ( *(busca(lista1)) ).i1); printf(" %d \n", busca(lista2)->i2); if( (tt=busca2(lista1, 3, lista2)) !=NULL) printf(" %d \n", tt->i2); return(0); } a) Explicar, empleando un diagrama, el paso por referencia de Push. b) Diagrama de la estructura después de ejecutar crealista(); c) Explicar acción realizada por busca. d) Explicar acción realizada por busca2. e) Determinar qué imprime el programa. Solución. a) En la función crealista, se tiene un ejemplo de uso de Push. Consideremos el llamado: Push(&lista1, 0, 1). En la definición de Push, el primer argumento es un puntero a puntero a nodo. Profesor Leopoldo Silva Bijit 20-01-2010 Definición de Estructuras de Datos en C. 17 Dentro de la función, la ocurrencia de *ref, denota a la variable, cuya dirección es pasada en la invocación a la función, en el caso del ejemplo referencia a variable global lista1. Si *ref, aparece a la derecha, formando parte de una expresión, su valor es el almacenado en lista1; si aparece a la izquierda, se escribe en la variable lista. Luego de la invocación a la función, se tiene el siguiente esquema para las variables. Datos Stack Lista1 newnodo ? ref dato1 = 0 dato2 = 1 Figura P2.1. Nótese que los argumentos son variables almacenadas en el stack, iniciadas con los valores de los parámetros de la invocación. La variable local newnodo, al no estar inicializada en su definición apunta a cualquier lado. Razón por la cual, conviene definirlas e inicializarlas simultáneamente. Después de un llamado exitoso a getnodo(); es decir, malloc asignó una estructura en el heap, un diagrama de la situación es el siguiente: Datos Stack heap Lista1 newnodo i1 = ? ref i2 = ? dato1 = 0 next = ? dato2 = 1 Figura P2.2. El resto de la función, escribe en los campos de la estructura creada en el heap. Y además, en último término, sobreescribe en la variable global lista1(en el ejemplo que se analiza). Profesor Leopoldo Silva Bijit 20-01-2010 18 Estructuras de Datos y Algoritmos Datos Stack heap Lista1 newnodo i1 = 0 ref i2 = 1 dato1 = 0 next dato2 = 1 Figura P2.3 Al salir de Push, desaparecen las variables automáticas, ubicadas en el stack, y la situación queda: Datos Stack heap Lista1 i1 = 0 i2 = 1 next Figura P2.4 Si se hubiera pasado solamente un puntero a nodo como referencia, se tendría el diseño: void Push2(pnodo ref, int dato1, int dato2) { pnodo newnodo; if ( (newnodo=getnodo()) == NULL) exit(1); newnodo->i1 = dato1; newnodo->i2 = dato2; newnodo->next = ref; ref = newnodo; } Y un ejemplo de uso, sería: Push2(lista1, 0, 1); Lo cual no es equivalente al diseño anterior. Puede comprobarse efectuando un diagrama. Profesor Leopoldo Silva Bijit 20-01-2010 Definición de Estructuras de Datos en C. 19 Las listas con cabecera ( header ) permiten diseños alternativos. b) Después de crealista, el espacio queda: Datos Lista1 Lista2 heap i1=4 i1 = 3 i1 = 2 i1 = 1 i1 = 0 i2 = 5 i2 = 4 i2 = 3 i2 = 2 i2 = 1 next next next next next i1 =1 i1 = 2 i1 = 3 i1 = 4 i1 = 5 i2 = 3 i2 = 2 i2 = 1 i2 = 0 next next next next i2 = -1 next Figura P2.5. c) A la función busca, se le pasa la dirección del primer nodo de la lista. Si la lista es vacía, retorna un puntero nulo. Si no es vacía, con pp recorre la lista, dejando qq apuntado al nodo corriente y con pp al próximo. Cuando se llega con pp, al final de la lista, qq apunta al último nodo de la lista. d) La función busca2 intenta encontrar el valor j en el campo i1 del nodo apuntado por el argumento p. Si la lista es vacía retorna un puntero nulo; en caso de encontrar el valor j, busca a partir del nodo apuntado por q, el valor del campo i1 que sea igual al valor del campo i2 del nodo donde quedó apuntando p. Si lo encuentra, retorna un puntero al nodo que cumple la condición anterior; en caso contrario, retorna un puntero nulo. La invocación: busca2(lista1, 3, lista2), después del primer while, deja p apuntando al segundo nodo de la lista1, el segundo while deja q apuntando al cuarto nodo de la lista2; ya que busca en ésta el valor 4 en el campo i1. El diseño de la función incurre en un error frecuente, en este tipo de problemas. ¿Qué ocurre si el valor j no se encuentra en ningún campo i1 de la lista apuntada por p?. Cuando p tome valor nulo, intentará leer p->i1 en las primeras direcciones de memoria, las cuales suelen pertenecer a un segmento del sistema operativo. Lo más seguro que la ejecución del proceso aborte debido a un error de segmentación. Se podría corregir, cambiando: while( p->i1 != j ) p = p->next; por: while ((p != NULL)&&( p->i1 != j )) p = p->next; Lo cual funciona debido a que el and opera con cortocircuito. Profesor Leopoldo Silva Bijit 20-01-2010 20 Estructuras de Datos y Algoritmos e) En primer término imprime el campo i1 del último nodo de la lista1. Luego el campo i2 del último nodo de la lista2. Finalmente el campo i2 de la lista 2, cuyo campo i1 es 4; ya que en la lista1, el campo i2 es 4, cuando el campo i1 es 3. Imprimiría: 0 -1 0 P2.2. Escribir una función: int btoi(char *str, int *pvalor) que convierta un string binario pasado en str (Ej. "110101") a un entero a ser retornado en pvalor (Ej. 53). La función retorna 0 si no hay error y retorna -1 si el string binario tiene un carácter que no es '0' o '1'. Nota: 1101 (en binario) = 1x23+1x22+0x21+1x20 = 8 + 4 + 1 = 13 en decimal /* Uso de la funcion */ #include <stdio.h> #include <math.h> #include <string.h> int btoi(char *str, int *pvalor); main() { int valor=0; char str[10]; strcpy(str, "110101"); if( !btoi(str, &valor) ) printf("string binario: %s, int:%d\n", str, valor); } Solución. int btoi(char *str, int *pvalor) { int i=0, largo=0, temp=0; largo = strlen(str) - 1; //índice del dígito menos significativo while(i <= largo) { if (*(str + i) == '1') temp += (int) pow(2, largo - i); //suma las potencias presentes de dos, desde la más significativa. else if (*(str + i) == '0') ; else return -1; //no es 1 ó 0 i++; } Profesor Leopoldo Silva Bijit 20-01-2010 Definición de Estructuras de Datos en C. 21 *pvalor = temp; //retorno por referencia return 0; } Una alternativa es sumar las potencias presentes de dos, desde la menos significativa. int btoi(char *str, int *pvalor) { int i=0, temp=0, pot=1; i = strlen(str)-1; //índice del dígito menos significativo while(i >=0) { if (*(str + i) == '1') { temp += pot; printf(" %d %d \n",temp, pot);} else if (*(str + i) == '0') ; else return -1; //no es 1 ó 0 i--;pot*=2; } *pvalor = temp; return 0; } P2.3. Se tiene la siguiente función: char *f1(char *s, int c) { while( *s ) { if( *s == (char) c ) return (char *) s; s++; } return (char *) 0; } a) Explicar los cast que se emplean y su necesidad. b) Qué realiza la función. c) Si se tiene char *string="12345678"; explicar que despliega: printf("%s\n", f1(string, '3')) ; Solución. El cast (char) c se emplea, debido a que el segundo argumento de la función es de tipo entero, para convertirlo a carácter y efectuar la comparación con un carácter del string. No es necesario, ya que por defecto, los valores de tipo carácter son promovidos automáticamente a enteros con signo. Por ejemplo se puede pasar equivalentemente el valor 65 ó 0x45 ó „A‟. Esto permite pasar caracteres de control que no son imprimibles, como argumentos. La comparación también se podía haber explicado según: ( (int) *s == c ). Es preferible usar cast, de tal modo de comparar valores de igual tipo. El cast en return (char *) s; no es necesario, ya que s es de tipo puntero a char. Profesor Leopoldo Silva Bijit 20-01-2010 22 Estructuras de Datos y Algoritmos El cast return (char *) 0; es necesario para retornar un puntero nulo a carácter. b) Revisa y encuentra la primera ocurrencia del carácter (char) c, en el string apuntado por s; retornado un puntero al carácter de s que es igual a (char) c. Si el string es nulo, retorna un puntero nulo a carácter; también si no encuentra (char) c en el string. c) Despliega el string: 345678, seguido de un terminador de línea, ya que el llamado f1(string, '3') retorna un puntero al string “345678". P2.4. Se tiene la siguiente función: int f2(char *s1, char *s2) { while( *s1++ == *s2 ) if( *s2++ == '\0' ) return(1); return(0); } a) Explicar que realiza la función. Indicando las condiciones en las que retorna un cero o un uno b) Dar un ejemplo de uso. Definiendo las variables que sean necesarias. Solución. a) Compara dos strings. Retorna 1 sólo si los strings son iguales. Reconoce la igualdad de dos strings nulos. Retorna cero, si los strings son diferentes. Si los caracteres, en la misma posición, de s1 y s2 son iguales va recorriendo ambos strings. Si los primeros caracteres de los strings fuesen iguales, retorna un cero si string s1 es más corto que el string s2 (ya que no se cumple la condición del while); lo mismo sucede si string 1 es más largo que el string s2. b) char *s1="12345678"; char *s2="12345"; if ( f2(s1,s2)==0) printf("no son iguales\n"); else printf("son iguales\n"); P2.5. Se tiene la estructura para un nodo, con clave de tipo entera y tres punteros a nodo. clave p1 p2 p3 a) Definir los tipos: nodo y pnodo (puntero a nodo). b) Diseñar, empleando malloc, la función con prototipo: pnodo creanodo(int clave); que solicita espacio e inicializa el nodo con punteros nulos y la clave del nodo con el valor del argumento. c) Si se tiene la siguiente definición: pnodo pn; dibujar un diagrama con los datos, luego de ejecutada la secuencia: Profesor Leopoldo Silva Bijit 20-01-2010 Definición de Estructuras de Datos en C. 23 pn=creanodo(5); pn->p3=pn; pn->p1=creanodo(3); pn->p1->p3=pn; pn->p2=creanodo(8); pn->p2->p3=pn->p1; d) Escribir el segmento que forma el siguiente diagrama: pn 4 p1 6 p2 p3 p1 p2 p3 Figura P2.6. Solución. a) typedef struct moldenodo { int clave; struct moldenodo *p1; struct moldenodo *p2; struct moldenodo *p3; } nodo , *pnodo; b) pnodo creanodo(int clave) { pnodo p; if ((p = (pnodo) malloc(sizeof(nodo))) == NULL) { printf ("Memoria insuficiente para crear nodo\n"); exit(1); } p->clave=clave;p->p2=NULL;p->p1=NULL;p->p3=NULL; return(p); } c) Los diagramas se ilustran después de ejecutada la instrucción. Profesor Leopoldo Silva Bijit 20-01-2010 24 Estructuras de Datos y Algoritmos pn=creanodo(5); pn 5 Figura P2.7. pn->p3=pn; pn 5 Figura P2.8. pn->p1=creanodo(3); pn 5 3 Figura P2.9. pn->p1->p3=pn; pn 5 3 Figura P2.10. Profesor Leopoldo Silva Bijit 20-01-2010 Definición de Estructuras de Datos en C. 25 pn->p2=creanodo(8); pn 5 3 8 Figura P2.11. pn->p2->p3=pn->p1; pn 5 3 8 Figura P2.12. c) pn= creanodo(6); pn->p2=pn; pn->p3=pn; pn->p1=creanodo(4); pn->p1->p1=pn->p1; pn->p1->p3=pn; pn->p1->p2=pn; Profesor Leopoldo Silva Bijit 20-01-2010 26 Estructuras de Datos y Algoritmos pn 4 p1 6 p2 p3 p1 p2 p3 Figura P2.13. P2.6. Se tiene el siguiente programa. #include <stdlib.h> typedef struct nn { int x; struct nn * p; struct nn * q; int y; } t, *pt; t w, z; pt px=&w; void main(void) { px->p=&z; w.q = px->p; px->q->q=px; z.p = w.p; w.x = z.x = 2; (*px).y = 8; (*(w.q)).y =9; px=(pt) malloc(sizeof(t)); px->p=px->q = (pt ) 0; px->x=px->y=12; } Efectuar un diagrama de los datos después de ejecutar las instrucciones de main. Solución. Profesor Leopoldo Silva Bijit 20-01-2010 Definición de Estructuras de Datos en C. 27 Antes de main, el espacio de variables puede visualizarse según: px w z x x p p q q y y Figura P2.13. px w x p px->p =&z q w.q=px->p; y z x p q y Figura P2.14. Profesor Leopoldo Silva Bijit 20-01-2010 28 Estructuras de Datos y Algoritmos px w x p q y px->q->q=px z x z.p=w.p p q y Figura P2.15. px w x 2 p w.x=z.x=2 q y 8 (*px).y=8; z x 2 p q (*(w.q)).y=9 y 9 Figura P2.16. Profesor Leopoldo Silva Bijit 20-01-2010 Definición de Estructuras de Datos en C. 29 px px =(pt)malloc(sizeof(t)); w x 2 p x 12 p y y q 8 q z 12 x 2 px ->p=px->q=(pt)0; p y q 9 px ->x=px->y=12; Figura P2.17. Ejercicios propuestos. E1. Determinar qué imprimen los siguientes segmentos: a ) printf(“\n%o %x %d”, 17, 017, 0x17) ; b) n = 3 ; printf(“-05d %5d”, n, n) ; c) x = 1234E-2 ; printf(“%6.3f”, x) ; d) j =2; if ( ( '1' -1 ) == --j ) printf("verdad"); else printf("falso"); E2. Colocar paréntesis y evaluar las expresiones siguientes: Si es preciso puede indicar los resultados de expresiones intermedias. a) a != b && c + 1 == ! c + 2 con a1) a=2 ; b=3 ; c = 1; a2) a=3 ; b=2 ; c=2 ; b) 1 + 2 * ( n += 8) / 4 con n=3 ; c) a < b ? a < b ? a+1 : a+2 : a+3 con a=2 ; b= 3 ; Profesor Leopoldo Silva Bijit 20-01-2010 30 Estructuras de Datos y Algoritmos E3. Se tiene el siguiente programa: Qué efectúa la función, indicar tipo de parámetros. Escribir el prototipo y dónde debe ubicarse en el programa. Qué escribe el programa principal. void main() { int i=11, j ; j = funcion( i++, 5) ; printf(“\n%d %d”, j, i); j = funcion( j+i, j-3) ; printf(“\n%d %d”, j, i); } int funcion( int x, int y) { int s,t ; for( s=0 ; x != 0 ; x--) for ( t=0 ; t != y ; s++,t++) ; return( s ) ; } E4. Escribir programa. a) Que genere los primeros 20 números de la secuencia: 1, 4, 7, 10, 14, .... b) Que genere b elementos de la secuencia a partir del a-ésimo. Leer a y b. E5. Indicar que escribe el programa. #include <stdio.h> int func(int *, float *, char); void show(); float f1=1, f2=2.2; int i1=2, i2; void main() { i2 = func(&i1, &f2, '1'); show(); i1 = func(&i2, &f1, '2'); show(); for(;;); } void show() { printf("\ni1= %d i2= %d" ,i1,i2); printf("\nf1= %f f2= %f",f1, f2); } int func(int *i, float *f, char ch) { *f = (float)(*i+2); *i = (int)(*f)+2; printf("\n%c ", ch); return(*i+2); } Profesor Leopoldo Silva Bijit 20-01-2010 Definición de Estructuras de Datos en C. 31 E6. Indicar qué imprime el programa. #include <stdio.h> int a[5]; int i; void arreglo(int j){ for(i=0;i<5; a[i]=j+i, i++); } void show(void){for(i=0;i<5;i++) printf(" %3d" , a[i]); printf("\n");} void main(void) { arreglo(1), show(); arreglo(2), show(); arreglo(a[2]+*a+3), show(); while(1); } E7. Indicar qué imprime el programa. Indicar qué efectúa cada función. #include <stdio.h> void prt(int); void f2(int&); void f1(int&); void main(void) { int i=1024; prt(i); i = 0x10; f1(i); prt(i); i = -1; f2(i); prt(i); } void f1(int& i) { i = (i | 0x8000); } void f2(int& i) { i = (i>>1); i = (i&0x7fff); } void prt(int i) { int j; for (j=15; j>=0; j--)(1<< j ) &i ? printf( '1') : printf( '0'); printf( '\n'); } E8. Indicar qué imprime el programa. #include <stdio.h> int f1(int& , int); int f2(int *, int); void main() Profesor Leopoldo Silva Bijit 20-01-2010 32 Estructuras de Datos y Algoritmos { int k, m =1; k = f1(m,5); printf(“%d \n %d “, k, m); k = 2; m = 3; m= f2(&m, m); printf(“%d \n”, m ); k = 4; m = 6; k = f1( m, f1(m,3)+5 ); printf(“%d \n %d \n”, k, m); } int f1(int&i , int j) { j = j+i ; i = i+j; return(i+j); } int f2(int *i, int j) { *i = j+2+*i; return(j+*i); } E9. Indicar qué escribe el programa. #include <stdio.h> struct punto{ int x; int y; }; struct molde{ int a[2]; char c; punto p; }; void main() { molde m1={{2,4},'p',{3,5}}, m2={{5,6},'q',{7,1}}; molde *pm=&m2; int *pi=&(m1.p.y); printf(“%d \n”, *pi ) ; printf(“%d \n”, cout << m2.a[1] + m1.p.x ); printf(“%d \n”, pm->a[1] + pm->p.x ); printf(“%d \n “, *(pi-1)) ; pi = &m2.a[0]; printf(%d \n “, *(pi+1)); } E10. Determinar la salida. #include <stdio.h> int f2(int x, int y, int z) {int i; for(i=0; i<x; i++) y+=z; Profesor Leopoldo Silva Bijit 20-01-2010 Definición de Estructuras de Datos en C. 33 return(y); } int main(void) { printf("%d ", f2(5, 3, 2)); return(0); } E11. Determinar la salida. #include <stdio.h> int arr[10]; void f1(int x, int *y) { int i; for(i=0; i<x; i++) *(y+i) = i; } void f2(int x, int *y) { int i; for(i=0; i<x; i++) printf("%d, ", *(y+i)); putchar('\n'); } int main(void) { f1(5, arr); f2(5, arr); return(0); } E12. Determinar la salida. #include <stdio.h> int arr[10]; int *pi; #define NULL ( (char *) 0) void f1(int x, int *y) {int i; for(i=0; i<x; i++) *(y+i)=i; } int *f2(int x, int *y, int z) { int i; for(i=0; i<x; i++) if(*(y+i)== z ) return(y+i); return((int *) NULL); } Profesor Leopoldo Silva Bijit 20-01-2010 34 Estructuras de Datos y Algoritmos int main(void) { f1(5, arr); pi=f2(5, arr, 8); if(pi!=((int *) NULL)) printf("%d ", *pi); else printf("no se encuentra\n"); return(0); } #include <stdio.h> E13. Explicar que realizan las funciones. void prtlong(unsigned long int i) { int j; unsigned long int k; k=1L; for (j=31; j>=0; j--) if((k<<j)&i) putchar(‟1‟); else putchar(‟0‟); } void prtint(int i) { int j, k=1; for (j=15; j>=0; j--) if( (k<<j)&i ) putchar(‟1‟); else putchar(‟0‟); } void sp(int i) { int j; for(j=i; j>0;j--) putchar(‟ ‟); } void lrs(int *p, int *q) { int k,m; k=(*p)&0x0001; m=(*p)&0x8000; (*p)>>=1; if(m) *p=(*p)&0x7fff; (*q)>>=1; if(k) *q=(*q)|0x8000;else *q=(*q)&0x7fff; } void ars(int *p, int *q) { int k,m; extern int s; k=(*p)&0x0001; m=(*q)&0x0001; (*p)>>=1;(*q)>>=1;if(k) *q=(*q)|0x8000; else *q=(*q)&0x7fff; s=m; } E14. Describir un multiárbol Mediante un arreglo de listas de los hijos de cada nodo. El arreglo debe tener una entrada por cada nodo, además considerar que la raíz pueda se cualquier nodo. Profesor Leopoldo Silva Bijit 20-01-2010 Definición de Estructuras de Datos en C. 35 Índice general. CAPÍTULO 2. ............................................................................................................................................ 1 DEFINICIÓN DE ESTRUCTURAS DE DATOS EN C......................................................................... 1 2.1. TIPOS PRIMITIVOS. ............................................................................................................................. 1 2.2. MECANISMOS DE ESTRUCTURACIÓN. ................................................................................................. 1 2.2.1. Grupos básicos. ......................................................................................................................... 1 2.2.2. Vínculos. .................................................................................................................................... 2 2.3. EJEMPLOS BASADOS EN ARREGLOS. ................................................................................................... 3 2.3.1. Acceso a componentes del arreglo. ........................................................................................... 3 Definición de matrices. .................................................................................................................................... 4 Definición de arreglo de arreglos. .................................................................................................................... 5 Arreglo de punteros a renglones. ..................................................................................................................... 5 Arreglo de punteros a caracteres. ..................................................................................................................... 6 2.3.2. Lista simplemente enlazada en base a cursores. ....................................................................... 6 2.4. EJEMPLOS BASADOS EN ESTRUCTURAS. ............................................................................................. 7 2.4.1. Estructura para fecha. ............................................................................................................... 7 2.4.2. Lista simplemente enlazada en base a punteros. ....................................................................... 9 2.5. ESTRUCTURAS MÁS COMPLEJAS....................................................................................................... 10 2.5.1. Arreglo de listas. ..................................................................................................................... 10 2.5.2. Arreglo de estructuras. ............................................................................................................ 11 2.5.3. Multiárboles. ........................................................................................................................... 11 2.5.3.1. Descripción mediante punteros. ........................................................................................................ 12 2.5.3.2. Descripción mediante arreglos de cursores. ...................................................................................... 12 2.5.3.3. Descripción por arreglo de padres. .................................................................................................... 13 2.6. UN EJEMPLO REAL DE ESTRUCTURAS. .............................................................................................. 14 PROBLEMAS RESUELTOS. ........................................................................................................................ 15 P2.1. Se tiene el siguiente programa: ............................................................................................... 15 P2.2. Escribir una función: ............................................................................................................... 20 P2.3. Se tiene la siguiente función: ................................................................................................... 21 P2.4. Se tiene la siguiente función: ................................................................................................... 22 P2.5. Se tiene la estructura para un nodo, ........................................................................................ 22 P2.6. Se tiene el siguiente programa. ................................................................................................ 26 EJERCICIOS PROPUESTOS. ....................................................................................................................... 29 E1. Determinar qué imprimen los siguientes segmentos: ................................................................ 29 E2. Colocar paréntesis y evaluar las expresiones siguientes: ........................................................... 29 E3. Se tiene el siguiente programa: .................................................................................................. 30 E4. Escribir programa. ..................................................................................................................... 30 E5. Indicar que escribe el programa. ................................................................................................ 30 E6. Indicar qué imprime el programa. ............................................................................................. 31 E7. Indicar qué imprime el programa. .............................................................................................. 31 E8. Indicar qué imprime el programa. .............................................................................................. 31 E9. Indicar qué escribe el programa. ................................................................................................ 32 E10. Determinar la salida. ................................................................................................................ 32 E11. Determinar la salida. ................................................................................................................ 33 E12. Determinar la salida. ................................................................................................................ 33 Profesor Leopoldo Silva Bijit 20-01-2010 36 Estructuras de Datos y Algoritmos E13. Explicar que realizan las funciones. ..........................................................................................34 E14. Describir un multiárbol .............................................................................................................34 ÍNDICE GENERAL. ....................................................................................................................................35 ÍNDICE DE FIGURAS. ................................................................................................................................36 Índice de figuras. FIGURA 2.1. AGRUPACIONES DE DATOS. ........................................................................................................2 FIGURA 2.2. VÍNCULOS ENTRE COMPONENTES DE DATOS. ............................................................................2 FIGURA 2.2.A. MATRIZ DE CARACTERES ......................................................................................................4 FIGURA 2.2.B. ARREGLO DE PUNTEROS A ARREGLOS DE CARACTERES ........................................................5 FIGURA 2.3. LISTA SIMPLEMENTE ENLAZADA................................................................................................7 FIGURA 2.4. LISTA SIMPLEMENTE ENLAZADA, MEDIANTE PUNTEROS. ...........................................................9 FIGURA 2.5. ARREGLO DE LISTAS. ...............................................................................................................10 FIGURA 2.6. MULTIÁRBOL. ..........................................................................................................................11 FIGURA 2.7. PRIMER DESCENDIENTE IZQUIERDO, HERMANO DERECHO. .......................................................12 FIGURA 2.8. MULTIÁRBOL MEDIANTE ARREGLO DE CURSORES. ..................................................................13 FIGURA 2.9. MULTIÁRBOL MEDIANTE ARREGLO DE PADRES........................................................................14 FIGURA 2.10 ARREGLO DE PADRES. .............................................................................................................14 FIGURA P2.1. ...............................................................................................................................................17 FIGURA P2.2. ...............................................................................................................................................17 FIGURA P2.3 ................................................................................................................................................18 FIGURA P2.4 ................................................................................................................................................18 FIGURA P2.5. ...............................................................................................................................................19 FIGURA P2.6. ...............................................................................................................................................23 FIGURA P2.7. ...............................................................................................................................................24 FIGURA P2.8. ...............................................................................................................................................24 FIGURA P2.9. ...............................................................................................................................................24 FIGURA P2.10. .............................................................................................................................................24 FIGURA P2.11. .............................................................................................................................................25 FIGURA P2.12. .............................................................................................................................................25 FIGURA P2.13. .............................................................................................................................................26 FIGURA P2.13. .............................................................................................................................................27 FIGURA P2.14. .............................................................................................................................................27 FIGURA P2.15. .............................................................................................................................................28 FIGURA P2.16. .............................................................................................................................................28 FIGURA P2.17. .............................................................................................................................................29 Profesor Leopoldo Silva Bijit 20-01-2010