Manua Estructura de Datos

85
Pag 1 MANUAL ESTRUCTURA DE DATOS 1. Representación de datos e introducción a el lenguaje C Tipos de datos Estructura de un programa Operadores aritméticos Operadores relacionales y lógicos Estructuras de control 2. Ordenamientos y búsquedas Arreglos Ordenamiento Burbuja y Burbuja Mejorado (Bubble Sort) Ordenamiento por Inserción Directa (InsertSort) Ordenamiento por selección (SelectSort) Ordenamiento Shell (ShellSort) Ordenamiento Mezcla (MergeSort) Búsquedas Búsqueda Secuencial Búsqueda Binaria 3. Estructuras de datos lineales, representaciones secuenciales. Conceptos fundamentales: Tipo de Dato, Tipo de Dato Abstracto, Estructura de datos, Registros. T.D.A. Lista Modelo Matemático Operaciones Implementación con Arreglos T.D.A Pila Modelo Matemático Operaciones Implementación con arreglos Notación Polaca Recursividad Ordenamiento Rápido (Quick Sort) Torres de Hanoi T.D.A Cola Modelo Matemático Operaciones Implementación con arreglos Concepto de Apuntador Listas implementadas con cursores 4. Estructuras de Datos lineales, representaciones ligadas. Teoría de lista ligada Listas con encabezado Lista simplemente ligada Lista simplemente ligada circular Lista doblemente ligada Lista doblemente ligada circular Listas sin encabezado Lista simplemente ligada

Transcript of Manua Estructura de Datos

Page 1: Manua Estructura de Datos

Pag 1

MANUAL ESTRUCTURA DE DATOS

1. Representación de datos e introducción a el lenguaje C Tipos de datos Estructura de un programa Operadores aritméticos Operadores relacionales y lógicos Estructuras de control

2. Ordenamientos y búsquedas Arreglos Ordenamiento Burbuja y Burbuja Mejorado (Bubble Sort) Ordenamiento por Inserción Directa (InsertSort) Ordenamiento por selección (SelectSort) Ordenamiento Shell (ShellSort) Ordenamiento Mezcla (MergeSort) Búsquedas Búsqueda Secuencial Búsqueda Binaria

3. Estructuras de datos lineales, representaciones secuenciales. Conceptos fundamentales: Tipo de Dato, Tipo de Dato Abstracto, Estructura de datos, Registros. T.D.A. Lista Modelo Matemático Operaciones Implementación con Arreglos T.D.A Pila Modelo Matemático Operaciones Implementación con arreglos Notación Polaca Recursividad Ordenamiento Rápido (Quick Sort) Torres de Hanoi T.D.A Cola Modelo Matemático Operaciones Implementación con arreglos Concepto de Apuntador Listas implementadas con cursores

4. Estructuras de Datos lineales, representaciones ligadas. Teoría de lista ligada Listas con encabezado Lista simplemente ligada Lista simplemente ligada circular Lista doblemente ligada Lista doblemente ligada circular Listas sin encabezado Lista simplemente ligada

Page 2: Manua Estructura de Datos

Pag 2

Pilas Colas

5. Estructuras de datos no lineales, representaciones secuencial y ligada.

Teoría general de Árboles Árboles binarios T.D.A. Árbol Binario Representaciones secuenciales Representación ligada Recorridos En-Orden, Pre-Orden y Post-Orden

6. Grafos.

Representación de datos e Introducción a lenguaje C

Tipos de datos. Los diferentes objetos de información con los que un programa C trabaja se conocen colectivamente como datos. Todos los datos tienen un tipo asociado con ellos

La asignación de tipos a los datos tiene dos objetivos principales:

1. Detectar errores de operaciones en programas. 2. Determinar como ejecutar las operaciones. El tipo de un dato determina la naturaleza del conjunto de valores que puede tomar una variable. Tipos predefinidos Tipo Largo Rango unsigned char 8 bits 0 to 255 Char 8 bits -128 to 127 enum 16 bits -32,768 to 32,767 unsigned int 16 bits 0 to 65,535 short int 16 bits -32,768 to 32,767 int 16 bits -32,768 to 32,767 unsigned long 32 bits 0 to 4,294,967,295 long 32 bits -2,147,483,648 to 2,147,483,647 float 32 bits 3.4 * (10**-38) to 3.4 * (10**+38) double 64 bits 1.7 * (10**-308) to 1.7 * (10**+308) long double 80 bits 3.4 * (10**-4932) to 1.1 * (10**+4932) Tipos de datos definidos por el usuario Constantes Las constantes se presentan en expresiones como c= ‘Y’ 2* (x + 7) –33 kilómetros = 1.609344 * millas Además de tener un valor, una constante también tiene un tipo de dato inherente. Los tipos de datos posibles en las constantes dependen de la máquina. Lo normal es que no existan constantes de los tipos short, unsigned y float. Aunque esto puede ser distinto en máquinas pequeñas, se supondrá que la situación normal prevalece.

Page 3: Manua Estructura de Datos

Pag 3

El tipo de dato asociado a una constante depende de la forma en que ésta se escribe. La siguiente lista contiene todos los tipos de datos que admiten constantes, asi como algunos ejemplos: int 0 77 5013 long 0L 77L 5013L double 0.003 1.0|| 0.5013e-2 char ‘a’ ‘b’ ‘c’ cadena “está es una constante de cadena” Una expresión constante sólo contiene constantes, y el compilador C las evaluará en el momento de la compilación, en lugar de hacerlo en la ejecución. Por ejemplo, en la proposición: Segundos = 60 * 60 * 24 * días; Segundos = 86400 * días; Uso de #define y de #include El compilador C tiene un preprocesador incorporado. Si las lineas #define LIMITE 100 #define PI 3.14159 se encuentran en un archivo que se está compilando el preprocesador cambia primero todos los identificadores LIMITE por 100 y todos los PI por 3.14159, excepto los que estén en cadenas entre comillas. Una línea #define puede estar en cualquier lugar del programa, pero debe empezar en la columna 1 y solo tendrá efecto en las líneas de archivo que le sigue. El preprocesador solo cambiará los identificadores que se escriben con mayúsculas. Si en un programa hay una línea como: #include “mi_archivo” Se trata también de una orden al procesador. Una linea #include puede presentarse en cualquier parte de un archivo, pero debe empezar en la columna uno. Las comillas que encierran el nombre de archivo son necesarias. El efecto de esta línea es incluir en ese punto de la codificación una copia del contenido del archivo mi_archivo Usos de printf() Y scanf() La función printf() se usa para la salida; en forma similar; la función scanf() se usa para la entrada. Estas funciones no son parte del lenguaje C, sino del sistema C; residen en una biblioteca estándar y están disponibles para usarlas donde quiera que haya un sistema C. Las f de printf() y de scanf() significan “con formato”. Ambas funciones tienen una lista de parámetros con dos partes: Cadena_de_control y lista_de_argumentos La primera es una cadena y puede contener especificaciones de conversión de formatos. Una especificación de conversión se inicia con un carácter % y termina con un carácter de conversión; por ejemplo, en el formato %d la letra de es el carácter de conversión. Este formato se emplea para imprimir el valor de una expresión como un entero decimal. Para imprimir las letras ABC en la pantalla, podría usarse la proposición: printf(“ABC”); otra manera de hacer esto es mediante la proposición: printf(“%s”,”ABC”);

Page 4: Manua Estructura de Datos

Pag 4

El formato %s hace que el argumento “ABC” se imprima en el formato de una cadena de caracteres. Esto mismo puede realizarse también con la proposición: printf(“%c%c%c”,’A’,’B’,’C’); Los apóstrofos que encierran cada letra se emplean para designar constantes de caracteres de acuerdo con esto, ‘A’ es la constante de carácter que corresponde a la letra A mayúscula. El formato %c imprime el valor de una expresión como un carácter. printf() Carácter de conversión

Cómo se describe el argumento correspondiente

C Como carácter D Como un entero decimal E Como número de punto flotante en notación

científica F Como un número de punto flotante G En el formato-e o el formato-f el que sea más corto S Como una cadena de caracteres

La función scanf() es análoga a la función printf(), pero se usa para la entrada. Su primer argumento es una cadena de control con formatos que corresponden a las diferentes formas en que pueden interpretarse los caracteres de la entrada como valores para los diferentes tipos de variables. La lista de argumentos está formada por direcciones de variables. El símbolo & representa el operador de dirección; por ejemplo, la proposición scanf(“%d”,&x); Contiene el formato %d, el cual provoca que scanf() interprete los caracteres de entrada como un entero decimal y que coloque el valor en la dirección x. Carácter de conversión

Los caracteres de la entrada se convierten en

c Un carácter d Un entero decimal f Un número de punto flotante (float) lf Un número de punto flotante (double) s Una cadena

Funciones La definición de una función consiste en un encabezamiento y un cuerpo. De manera explicita, podríamos decir que es un bloque o una proposición compuesta. Si hay declaraciones deben aparecer al inicio del bloque antes de cualquier proposición ejecutable. El encabezamiento puede ser tan sólo un identificador y unos paréntesis. Un ejemplo sencillo es :

/****Una función con un encabezamiento y un cuerpo sencillos ***/ escr_direcc() /* encabezamiento*/ /* el cuerpo es cuanto está entre llaves */

Page 5: Manua Estructura de Datos

Pag 5

printf(“\n\n%s\n%s\n%s\n%s\n%s\n\n”, “ ********************”, “ ** SAN NICOLAS *”, “ ** El POLO NORTE *”, “ ** LA TIERRA *”, “ *******************”);

Donde quiera que un programa identifique a esta función, la expresión hará que se invoque la función. Una definición de función tiene un nombre y unos paréntesis que contienen cero o más parámetros y un cuerpo. Para cada parámetro debe de haber una declaración correspondiente antes del cuerpo, cualquier parámetro que no se declaré se considera un int por omisión, a estos parámetros se les llama parámetros formales de función. El cuerpo de la función es un bloque o una proposición compuesta, y también puede contener declaraciones, todas las variables declaradas en el cuerpo de una función se dice que son variables locales. Otras variables no declaradas ni como argumentos ni en el cuerpo de la función se consideran como “globales” a la función y deben definirse en forma externa. La proposición return La proposición return puede incluir u omitir una expresión.

Proposición _return ::= return; \ return expresión; Algunos ejemplos son: return; return (377); return (a * b); return (++x); return ++x; Invocación y llamada por valor La funciones se declaran como objetos individuales que no pueden anidarse. Por tanto un programa consiste en una serie de una o más definiciones de funciones. Estas funciones están disponibles para usarse en el cuerpo de las demás. Pueden emplearse donde quiera que sea apropiado la expresión del tipo dado para el especificador de tipo de la función ( Recuérdese que si un especificador de tipo de una función está ausente, entonces por omisión es int.) Las funciones se invocan al escribir su nombre y una lista adecuada de argumentos entre paréntesis. Todos los argumentos pasan con una “llamada por valor”. Se evalúa cada argumento y su valor se utiliza de forma local en lugar del parámetro formal. Invocación y llamada por referencia En la practica moderna de la programación, el empleo de una variable externa global como vía de comunicación entre una función y el medio que la llama se considera indeseable. El proceso de declarar un parámetro de función como apuntador y utilizar en forma consistente el parámetro desreferenciado en el cuerpo de la función se le denomina “llamada por referencia”. Cuando se pasa una dirección como argumento, puede hacerse que la función cambie el valor de la variable direccionada en el medio que la llama. Para indicar que un parámetro de función es pasado por referencia, sólo coloque ampersand (&) después del tipo del parámetro en el prototipo de función.

Page 6: Manua Estructura de Datos

Pag 6

int ordena(int &lista) La invocación de una función significa

1. Se evalúa cada expresión de la lista de argumentos. 2. Al principio del cuerpo de la función se asigna el valor de la expresión a su parámetro

formal correspondiente. 3. Se ejecuta el cuerpo de la función. 4. Si se ejecuta una proposición return, el control regresa al medio que hizo la llamada. 5. Si la proposición return incluye una expresión, el valor de la expresión se convierte (si es

necesario) al tipo dado por el especificador de tipo de la función y ese valor también regresa al medio que hizo la llamada.

6. Si no hay una proposición return el control regresa al medio que hizo la llamada cuando se llega al final del cuerpo de la función.

7. Si se ejecuta una proposición return que no tiene una expresión o si no hay una proposición return, entonces no regresa ningún valor útil al medio que hizo la llamada

Especificador de tipo void El especificador de tipo void se usa para declarar funciones que no se pretende que devuelvan valores. Emplear una función void en una expresión que requiere un valor hace que el compilador produzca un mensaje de error. Estructura de un programa La base de la programación en C es la función, pues constituye una parte fundamental de la codificación en el proceso de solución de problemas. Todas las funciones están en el mismo nivel externo; no se pueden anidar unas en otras. Un programa contiene una o más funciones en uno o más archivos. Una de las funciones es main() donde se inicia la ejecución del programa. El resto de las funciones se llaman desde main() y desde el interior de otras main()

Todo programa tiene una función main donde inicia la ejecución; los paréntesis que van después de main indican al compilador que se trata de una función

Las llaves encierran al cuerpo de la función; también se usan para agrupar varias proposiciones. printf()

El sistema contiene una biblioteca estándar con funciones que pueden usarse en programas; está es una función de la biblioteca estándar que imprime en pantalla. “ de mar en mar C\n” En C una constante de cadena es una serie de caracteres entre comillas. Esta cadena es un argumento de la función printf() y controla lo que se escribirá los dos caracteres \n al final de esta cadena representan un carácter sencillo llamado nuevalínea; éste es un carácter que no se imprime, su efecto es hacer que el cursor avance hacia una línea nueva.

La llave derecha hace pareja con la llave de una función y da por terminada la función main

Page 7: Manua Estructura de Datos

Pag 7

Identificadores. Los identificadores representan objetos de un programa ( constantes, variables, tipos de datos procedimientos, funciones, unidades, programas y campos de registros). Un identificador es una secuencia de caracteres que pueden ser de cualquier longitud, pero sólo los primeros 63 caracteres son significativos. Un identificador se caracteriza por estas reglas:

1. Debe comenzar con una letra (A a Z mayúsculas o minúsculas) y no puede contener blancos.

2. Letras dígitos y caracteres subrayados están permitidos después del primer carácter. 3. No se puede utilizar una palabra reservada como identificador, sin embargo, los

identificadores estándar se pueden redefinir Palabras reservadas. Las palabras reservadas en C tienen un significado especial y no se pueden utilizar para otros propositos

Operadores aritméticos Los operadores aritméticos (+,-,*) pueden ser utilizados con tipos enteros o reales, si ambos son enteros el resultado es entero si uno es real el resultado es real. 2 + 3 = 5 2 +3.0 = 5.0 2.0 + 3 = 5.0 2.0 + 3.0 = 5.0

Operador Significado Ejemplo Resultado + Suma a + b Suma de a y b - Resta a – b Diferencia de a y b * Multiplicación a * b Producto de a por b / División a / b Cociente de a por b % Módulo a % b Resto de a por b

Operadores Incrementales y decrementales ++ ++a Se incrementa a en 1 y a continuación se utiliza el

nuevo valor de a en la expresión en la cual resida a.

++ a++ Utiliza el valor actual de a en la expresión en la cual reside a, y después se incrementa a en 1

-- --b Se decrementa b en 1 y a continuación se utiliza el nuevo valor de b en la expresión en la cual reside

auto break case char const continue default do double else enum extern float for goto if int long register return short signed sizeof static struct switch typedef union unsigned void volatile while

Page 8: Manua Estructura de Datos

Pag 8

b. -- b-- Se utiliza el valor actual de b en la expresión en la

cual reside b, y después se decrementa a b en 1 Operadores de asignación

+= C += 7 c = c +7 -= D -= 4 d = d - 4 *= e*=5 e = e* 5 /= F /= 3 f = f / 3 %= g %= 9 g = g % 9

Prioridad de operadores Cuando una expresión aritmética se evalúa, el resultado es siempre un número. Cuando en una expresión aparecen dos o más operadores, ¿qué operación se realiza primero?. Reglas de evaluación de expresiones.

El orden de las operaciones es: x - ( A + B % 2 ) + y * z

1. Todas las subexpresiones entre paréntesis se evalúan primero. Las subexpresiones con paréntesis anidados se evalúan de dentro a afuera; el paréntesis más interno se evalúa primero.

2. Prioridad de operaciones. Dentro de una misma expresión o subexpresión, los operadores se evalúan en el siguiente orden: *,/,% primero +,- último

3. Regla asociativa izquierda. Los operadores en una misma expresión o subexpresión con igual nivel de prioridad (tal como * y /) se evalúan de izquierda a derecha.

+

% *

+

-

Page 9: Manua Estructura de Datos

Pag 9

Escritura de formulas matemáticas en C. En C las formulas matemáticas se deben escribir en formato lineal. Esto obliga al uso frecuente de paréntesis que indiquen el orden de evaluación correcto de los operadores. M = y2 – y1 m = (y2 - y1) / (x2 – x1) x2 –x1 Operadores relacionales y lógicos. Operadores de relación Se utilizan para expresar condiciones y describen una relación entre dos valores. Operador Significado Equivalente matemático

< Mayor que > > Menor que <

== Igual que = >= Mayor o igual que ≥ <= Menor o igual que ≤ != Distinto a ≠

Los operadores de relación se utilizan en condiciones cuyo formato tiene una de las siguientes formas: 1. variable operador relacional variable 2. constante operador relacional constante El resultado de la expresión lógica es un valor tipo lógico: verdadero o falso.

Expresión valor 6.7315 < 6.7342 0 -124.2 < 0.003 1 8 == 8.0 1 ‘A’ < ‘B’ 1 ‘Z’ < ‘H’ 1 Operadores lógicos Las expresiones lógicas pueden combinarse para formar expresiones más complejas utilizando los operadores lógicos: &&, || y !. Estos operadores se utilizan con constantes lógicas de forma similar al modo en que los operadores aritméticos se utilizan con las constantes numéricas, estos operadores trabajan con operandos que son expresiones lógicas. [operando1] operador operando2

Según el tipo de operador puede no existir Orden de evaluación de operadores lógicos.

1. ! 2. && 3. ||

Page 10: Manua Estructura de Datos

Pag 10

Operador Prioridad ! Mas alta (se evalúa primero) *,/,%,&& - +,-,|| - < , <= , == , <> , >= , > Mas baja se evalúa al ultimo Si existen paréntesis las expresiones de su

Interior se evalúan primero

((x * 2 > y - 3) || (x > y - 1)) && (y < 5)

* - -

> >

||

<

&&

Page 11: Manua Estructura de Datos

Pag 11

Operador &&

Operando1 Operando2 Operando1 && Operando2

1 1 1 1 0 0 0 1 0 0 0 0 Operador || Operando1 Operando2 Operando1 || Operando2 1 1 1 1 0 1 0 1 1 0 0 0 Operador !

Operando1 ! Operando1

1 0

Page 12: Manua Estructura de Datos

Pag 12

Estructuras de control Selectivas. La sentencia if. Sentencia de control que dirige a la computadora para ejecutar una sentencia si la expresión es verdadera, y otra en caso de ser falsa. Formato

En muchos casos se desea que una determinada acción sólo ejecute si una cierta condición es verdadera y no realizar ninguna acción si la condición es falsa. if (condición) sentencia Sentencia compuesta. sentencia 1; sentencia 2; sentencia 3; - - sentencia n; Ejemplo: #include <stdio.h> main() if (grado >=90) Printf(“A\n”); else if (grado >=80) Printf(“B\n”); else if (grado >=70) Printf(“C\n”); else if (grado >=60) Printf(“D\n”); else if (grado >=50) Printf(“F\n”); La sentencia switch La sentencia switch se utiliza para elegir entre diferentes alternativas.

if (expresión lógica) proposicion1 else proposición2 proposición siguiente

Page 13: Manua Estructura de Datos

Pag 13

El siguiente ejemplo utiliza switch para contar el número de cada distinta letra de calificación que los estudiantes alcanzarón en un examen. /*Contando calificaciones*/ #include <stdio.h> main() int Letra; int acontador = 0, bcontador =0, contador = 0, dcontador =0, fcontador = 0; printf(“Mete la Letra de su calificación.\n”); printf(“Mete el carácer EOF para finalizar las entradas.\n”); while ((Letra = getchar() ) != EOF) switch (Letra) case ‘A’: case ‘a’: acontador; break; case ‘B’: case ‘b’: bcontador; break; case ‘C’: case ‘c’: ccontador; break; case ‘D’: case ‘d’: dcontador; break; case ‘F’: case ‘f’: fcontador; break; default printf(“ Letra de entrada incorrecta”); printf(“ Meta una nueva calificación.\n”); break;

printf(“\nTotales de cada calificacion\n”); printf(“A: %d\n”, acontador); printf(“B: %d\n”, bcontador); printf(“C: %d\n”, ccontador);

switch (expresion_entera) case 1 : sentencia1;break; case 2 : sentencia2;break; case 3 : sentencia3;break; - - case n : sentencian;break; [default :sentencia x] case

Page 14: Manua Estructura de Datos

Pag 14

printf(“D: %d\n”, dcontador); printf(“F: %d\n”, fcontador); return 0; EOF es una constante simbólica entera, definida en el archivo de cabecera <stdio.h>

La sentencia while. La estructura repetitiva while (mientras) es aquella en la que el número de iteraciones no se conoce por anticipado y el cuerpo del bucle se repite mientras se cumple una determinada condición.

1. La condición (expresión lógica) se evalúa antes y después de cada ejecución del bucle. Si

la condición es verdadera se ejecuta el bucle y si es falsa el control pasa a la sentencia siguiente al bucle.

2. Si la condición se evalúa falso cuando se ejecuta el bucle por primera vez el cuerpo del bucle no se ejecutará nunca.

3. Mientras la condición sea verdadera el bucle se ejecutará, esto significa que el bucle se ejecutará indefinidamente a menos que algo en el interior del bucle modifique la condición haciendo que su valor pase a falso.

while (expresión lógica) Sentencia 1 - - Sentencia n

Mete la letra de su c Mete el carácer EOF para finalizar las entradas A B C C A D F C E Letra de entrada incorrecta meta una nueva calificación D A B Totales de cada calificación A: 3 B: 2 C: 3 D: 2 F: 1

Page 15: Manua Estructura de Datos

Pag 15

Ejemplo: El promedio de la clase es igual a la suma de calificaciones dividida por el número de alumnos. El algoritmo para resolver este problema en una computadora, debe introducir cada una de las calificaciones, ejecutar el cálculo de promedio e imprimir el resultado. #include <stdio.h> main() int, contador, grado, total, promedio; total = 0; /*inicializacion*/ counter = 1; /*procesamiento*/ while (counter <=10) printf (“Mete grado:”); scanf(“%d”,&grado); total = total +grado; counter++; promedio = total / 10; printf(“El promedio de la clase es %d\n”, promdio); return 0; La sentencia do while. Una variante de la sentencia while es la sentencia do while, esta sentencia especifica un bucle condicional que se repite hasta que la condición se hace falsa.

1. La condición se evalúa al final del bucle, después de ejecutarse todas las sentencias 2. Si la expresión lógica es verdadera, se vuelve a repetir el bucle y se ejecutan todas las

sentencias. 3. Si la expresión lógica es verdadera, se sale del bucle y se ejecuta la siguiente sentencia a

while. 4. La sintaxis no requiere .

El siguiente ejemplo utiliza una estructura do while para imprimir los números del 1 al 10. #include <stdio.h> main() int contador = 1; do printf (“%d “, contador);

while (++contador <= 10); return 0;

do Sentencia 1 - - - sentencia n while (expresión lógica);

Page 16: Manua Estructura de Datos

Pag 16

La sentencia for. Esta sentencia requiere que sepamos por anticipado el número de veces que se ejecutan las sentencias del interior del bucle.

#include <stdio.h> main() int contador; /*inicialización, condición de repetición, e incremento*/ for (contador=1; contador <=10; contador++) printf(“%d\n”,contador); return 0; ejemplo utillizando la estructura for

a) Varíe la variable de control de 1 a 100 en incrementos de 1. for (i=1;i<=100; i++)

b) Varíe la varible de control de 100 a 1 en incrementos de –1 (decrementos de 1). for ( i = 7; i <=77;i+=7)

c) Variar la variable de control a lo largo de la siguiente secuencia de valore: 2, 5, 8, 11, 14, 17, 20. for (j= 2; j <=20; j+=3)

d) Variar la variable de control de acuerdo a la siguiente secuencia de valores: 99, 88, 77, 66, 55, 44, 33, 22, 11, 0. for (j=99;j>=0;j-=11)

Los siguientes ejemplos proporcionan aplicaciones simples de la estructura for. El programa siguiente utiliza la estructura for para sumar todos los enteros pares desde 2 hasta 100 /*Sumación con for*/ #include <stdio.h> main() int sum = 0, number; for (number = 2; number <=100; number +=2) sum += number; printf(“Sum es %d\n”,sum); return 0;

for ( expresion1; expresion2; expresion3 ) Sentencia 1; Sentencia 2; - - - Sentencia n

Sum es 2550

Page 17: Manua Estructura de Datos

Pag 17

Ordenamientos y búsquedas Arreglos Un arreglo es una estructura de datos en la que se almacena una colección de datos del mismo tipo, es una lista de un número finito n de elementos del mismo tipo que se caracteriza por:

1. Almacenar los elementos del arreglo en memoria continua. 2. Tener un único nombre de variable que representa todos los elementos y estos a sus vez

se diferencian por un índice o subíndice. 3. Acceso directo o aleatorio a los elementos individuales del arreglo.

Para referirse a una posición en particular o elemento dentro del arreglo, especificamos el nombre del arreglo y el número de posición del elemento particular dentro del mismo. Los arreglos se clasifican en: Unidimensionales (vectores o listas) Multidimensionales (tablas o matrices) En la figura se muestra un arreglo de números reales llamado x . Este arreglo contiene 8 elementos. Cualquiera de estos elementos puede ser referenciado dándole el nombre del arreglo seguido del número de posición de dicho elementos en particular en paréntesis cuadrados o corchetes ([]). El primer elemento de cualquier arreglo es el elemento cero. Entonces el primer elemento de un arreglo x se conoce como x[0], el segundo como x[1], el séptimo como x[6] y en general, el elemento de orden i del arreglo x se conoce como x[i-1]. Los nombres de los arreglos siguen las mismas reglas convencionales que los demás nombres de la variables. Ejemplo: float x[8]; x[0] x[1] x[2] x[3] x[4] x[5] x[6] x[7] 45.21

12.0 3.45 4.32 0.314

513.6

2.65 13.04

El número de posición que aparece dentro de los corchetes se conoce más formalmente como subíndice. Un subíndice debe de ser un entero o una expresión entera. Si un programa utiliza una expresión como subíndice, entonces la expresión se evalúa para determinar el subíndice. Por ejemplo, si a = 3 y b = 4, entonces el enunciado C[a + b] +=2; Añade 2 al elemento del arreglo c[11]. Note que un nombre de arreglo con subíndice es un Ivalue que puede ser utilizado al lado izquierdo de una asignación. Examinaremos el arreglo x. Sus 8 elementos se conocen como x[0], x[1], x[2],....,x[7]. El valor de x[0] es 45.21, el valor de c[1] es 12.0, el valor de c[2] es 3.45, el valor de x[6] es de 2.65 y el valor de x[7] es 13.04. Para imprimir la suma de los valores contenidos en los primeros tres elementos del arreglo x, escribiríamos Printf (“%f”,x[0] +x[1] + x[2]); Para dividir el valor del séptimo elemento del arreglo x entre 2 y asignar el resultado a la variable c escribiríamos C= x[6] / 2;

Page 18: Manua Estructura de Datos

Pag 18

Como declarar arreglos Los arreglos ocupan espacio en memoría. El programador especifíca el tipo de cada elemento y el número de elementos requerido por cada arreglo, de tal forma que la computadora pueda reservar la cantidad apropiada de memoría. Para indicarle a la computadora que reserve 12 elementos para el arreglo entero c, la declaración int c[12]; es utilizada. La memoria puede ser reservada para varios arreglos dentro de una sola declaración, Para reservar 100 elementos para el arreglo entero b y 27 elementos para el arreglo entero x, se puede utilizar la siguiente declaración int b[100], x[27]; Los arreglos pueden ser declarados para que contengan otro tipo de datos. Por ejemplo un arreglo de tipo char puede ser utilizado para almacenar una cadena de caracteres. Ejemplos utilizando arreglos El programa siguiente utiliza la estructura de repetición for para inicializar los elementos de un arreglo entero de diez elementos n a ceros, e imprime el arreglo en formato tabular. El enunciado printf muestra los encabezados de columnas de las dos columnas impresas en la estructura for. /* inicializa el arreglo*/ #include <stdio.h> main() int n[10], i; for (i = 0; i <= 9; i++) /*inicializa el arreglo*/ n[i] = 0; printf(“%s%13s\n”, “Elemento”, “Value”); for(i= 0; i <= 9; i++) /*imprime arreglo*/ printf(“%7d%13d\n”,i, n[i]); return 0;

Elemento Value 0 0 1 0 2 0 3 0 4 0 5 0 6 0 7 0 8 0 9 0

Page 19: Manua Estructura de Datos

Pag 19

El programa siguiente suma los valores contenidos en un arreglo entero a de doce elementos. El enunciado en el cuerpo del ciclo for se ocupa de la totalización. /* Calcula la suma de los elementos del arreglo*/ #include <stdio.h> #define SIZE 12 main() int a [SIZE] = 1, 3, 5, 4, 7, 2, 99, 16, 45, 67, 89, 45 i, total =0; for (i=0; i <=SIZE – 1;i++) total += a[i] printf(“El total del valor de los elementos del arreglo es %d\n”,total); return 0;

El siguiente ejemplo utiliza arreglos para resumir los resultados de datos recopilados en una investigación. Deseamos resumir el número de respuestas de cada tipo (es decir del 1 hasta el 10). El arreglo respuestas es un arreglo de 40 elementos correspondiente a las respuestas de los alumnos. Utilizamos un arreglo de 11 elementos, frecuencia para contar el número de ocurrencias de cada respuesta. Ignoramos el primer elemento frecuencia[0], por que es más lógico tener un incremento de respuesta 1 frecuencia[1] que frecuencia[0]. Esto nos permite utilizar cada respuesta directa como subíndice en el arreglo frecuencia. El primer ciclo for toma las respuestas del arreglo respuestas una por una e incrementa uno de los diez contadores (frecuencia[1] hasta frecuencia [10] ) en el arreglo frecuencia. El enunciado clave es ++frecuencia[respuestas[pregunta] Este enunciado incrementa el contador frecuencia apropiado, dependiendo del valor de respuestas[pregunta]. Por ejemplo cuando la variable del contador pregunta es 0, respuestas[pregunta] es 1 y por lo tanto, ++frecuencia [respuesta[pregunta]]; se interpreta en realidad como ++frecuencia[1]; #include <stdio.h> #define RESPUESTAS_SIZE 40 #define FRECUENCIA_SIZE 11 main() int answer, rating;

int respuestas[RESPUESTAS_SIZE] = 1, 2, 6, 4, 8, 5, 9, 7, 8, 10, 1, 6, 3, 8, 6, 10, 3, 8, 2, 7, 6, 5, 7, 6, 8, 6, 7, 5, 6, 6, 5, 6, 7, 5, 6, 4, 8, 6, 8, 10);

int frecuencia[FRECUENCIA_SIZE] = 0; for (pregunta = 0; pregunta <= RESPUESTA_SIZE – 1; answer++) ++frecuencia[respuestas[pregunta]]; printf (“%s%17s\n”,”Rating”, “Frecuencia”); for (rating = 1; rating <= FRECUENCIA_SIZE –1; rating++) printf(“%6d%17d\n”, rating, frecuencia[rating]); return 0;

El total del valor de los elementos del arreglo es 383

Page 20: Manua Estructura de Datos

Pag 20

Cómo pasar arreglos a funciones Para pasar cualquier argumento de arreglo a una función, especifique el nombre del arreglo, sin corchete alguno. Por ejemplo, si el arreglo Temperaturas ha sido declarado como int Temperaturas[24]; El enunciado de llamada a la función ModificaArreglo(Temperaturas, 24); Pasa el arreglo Temperaturas y su tamaño, a la función ModificaArreglo, al pasar un arreglo esl tamaño del arreglo se pasa a una funcion, de tal forma que pueda procesar el número especifico de elementos incluidos en dicho arreglo. C pasa de forma automática los arreglos a las funciones utilizando simulación de llamadas por referencia –las funciones llamadas pueden modificar los valores de los elementos en los arreglos originales de los llamadores. El nombre del arreglo de hecho es la dirección del primer elemento de dicho arreglo. Dado que ha sido pasada la dirección inicial del arreglo, la función llamada sabe precisamente dónde está el arreglo almacenado. Por lo tanto, cuando en su cuerpo de función, la función llamada modifica los elementos del arreglo, está modificando los elementos reales del arreglo, en sus localizaciones de memoria originales. #include <stdio.h> main() char array[5]; printf (“ array = %p\n&array[0] = %p\n”, array, &array[0]); return 0;

Arreglos multidimensionales Aunque los elementos de los arreglos se almacenan en forma contigua, con frecuencia resulta útil imaginar que un arreglo bidimensional es un conjunto rectangular de elementos con filas y columnas. Por ejemplo si se declara: int b[3] [5];

Rating Frecuencia 1 2 2 2 3 2 4 2 5 5 6 11 7 5 8 7 9 1 10 3

array = FFF0 &array = FFF0

Page 21: Manua Estructura de Datos

Pag 21

Puede imaginarse que los elementos del arreglo están ordenados de la manera siguiente: Col 1 Col 2 Col 3 Col 4 Col 5 Fila 1 B[0] [0] B[0] [1] B[0] [2] B[0] [3] B[0] [4] Fila 2 B[1] [0] B[1] [1] B[1] [2] B[1] [3] B[1] [4] Fila 3 B[2] [0] B[2] [1] B[2 [2] B[2] [3] B[2] [4] Los arreglos de doble subíndice se referencian con los subíndice dentro de un mismo par de corchetes separando por comas los subíndices. t[x,y]; y no t[x] [y]; Los arreglos de doble subíndice pueden ser declarados e inicializados de la siguiente manera int b[2][2] = 1,2,3,4; Muchas manipulaciones comunes con arreglos utilizan estructuras de repetición for. Por ejemplo, la siguiente estructura define todos los elementos en el tercer renglón del arreglo a int a[4][4]; for (column = 0;column 3; column++) A[2] [column] =0 Especificamos el tercer renglón, por lo tanto, sabemos que el primer subíndice será siempre 2 (o es el primer renglón y 1 el segúndo). El ciclo for varía sólo en el segundo subíndice (es decir, el subíndice de columnas). La estructura for anterior es equivalente a los enunciados de asignación siguientes: a[2][0] = 0; a[2][1] = 0; a[2][2] = 0; a[2][3] = 0; En el arreglo a, la siguiente estructura for anidada determina el total de todos los elementos. Total = 0; for (renglon =0; renglon <=3; renglon++) for (column = 0;column <=3; column++) total +=[renglon] [column]; Cadenas Una cadena es una secuencia de caracteres encerrada entre comillas “. Obsérvese que el símbolo “es un solo carácter, no dos. Si el carácter “ ha de aparecer en una cadena, debe ir precedido del carácter \. A continuación se presentan varios ejemplos de cadenas. “una cadena de texto” “ /*una cadena de caracteres blancos */ “z” “,.1kM87tt - basura” “una cadena con \” comillas incorporadas” “a = b + c; x =sin(y); “ nada se ejecuta “” /* la cadena nula */

Page 22: Manua Estructura de Datos

Pag 22

Las cadenas son arreglos unidimensionales de tipo char que tinen varías características únicas. Un arreglo de caracteres puede ser inicializado utilizando una literal de cadena. Por ejemplo, la declaración char string1[] = “first”; Inicializa los elementos de la cadena string1 a los carácteres individuales de la literal de cadena “first”. El tamaño del arreglo string1 en la declaración anterior queda determinada por el compilador, basado en la longitud de la cadena. Es importante hacer notar que “first” contiene 5 caracteres, más un carácter especial de terminación de cadena, conocido como carácter nulo. Entonces el arreglo string1 de hecho contiene 6 elementos, la representación de la constante de caracteres del carácter nulo es ‘\0’. En C todas las cadenas terminan con este carácter. Un arreglo de caracteres representando una cadena debería declararse siempre lo suficientemente grande, para contener el número de caracteres de la caden incluyendo el carácter nulo de terminación.. Los arreglos de caracteres también pueden ser inicializados con constantes individuales de caracteres en una lista de inicialización. La declaración anterior es equivalente a char string1[] = ‘f’,’i’,’r’,’s’,’t’,‘\0’; Dado que la cadena es un arreglo de caracteres podemos tener acceso directo a los caracteres individuales de una cadena, utilizando la notación de subíndices de arreglos. Por ejemplo, string1[0], es el carácter ‘f’ y string[3] es el carácter ‘s’. También podemos introducir desde el teclado directamente una cadena en un arreglo de caracteres, utilizando scanf y la especificación de conversión %s. Por ejemplo, la declaración . char string2[20]; Crea un arreglo de caracteres capaz de almacenar una cadena de 19 caracteres y un carácter nulo de terminación. El enunciado scanf (“%s”, string2); Lee una cadena del teclado y la coloca en string2. Note que el nombre del arreglo se pasa a scanf sin colocar el & precedente, que en otras variables se utiliza. El & es utilizado por lo regular para darle a scanf una localización de variable en memoria, a fin de que se pueda almacenar un valor ahí. El nombre de un arreglo es la dirección del inicio del arreglo y, por tanto, & no es necesario. La función scanf lee caracteres del teclado hasta que se encuentra con el primer carácter de espacio en blanco sin impotarle que tan grande es el arreglo. Por lo tanto, scanf podría escribir más allá del final del arreglo. Un arreglo de caracteres que represente una cadena puede ser sacado utilizando printf y el especificador de convesión %s. El arreglo string2 se imprime utilizando el enunciado printf (“%s\n”, string2); #include <stdio.h> main() char string1[20], string2[] = “string literal”; int i; printf(“Mete un string: “); scanf(“%s”,string1); printf(“string1 es: %s\nstring2 es %s\n “string1 con espacios entre caracteres es: \n”, string1, string2);

Page 23: Manua Estructura de Datos

Pag 23

for (i = 0; string1[i] != ‘\0’; I++) printf(%c ”, string1[i]);

printf(“\n”); return 0;

Uniones Una union es un tipo de datos derivado –como lo es una estructura- cuyos miembros comparten el mismo espacio de almacenamiento. Para distintas situaciones en un programa, algunas variables pudieran no ser de importancia, pero otras variables lo son –por lo que una union comparte el espcacio, en vez de desperdiciar almacenamiento en variables que no esten siendo utilizadas. Los miembros de la union pueden ser de cualquier tipo el número de bytes utilizados para almacenar una unión, deben ser por lo menos suficientes para contener al miembro mas grande. En la mayor parte de casos las uniones contienen dos o más tipos de datos. Unicamente un miembro y, por lo tanto, únicamente un tipo de datos, puede ser referenciado en un momento dado union number int x; float y; indica que number es un tipo union con miembros int x y float. En un programa normalmente la definición de unión antecede a main, por lo que esta puede ser utilizada para declarar variables en todo el programa. Las operaciones que pueden ser ejecutadas en una unión son: asignar una unión a otra unión del mismo tipo, tomar la dirección (&) de una unión, y tener acceso a los miembros de una union utilizando el operador de miembro de estructura y el operador de apuntador de estructura. En una declaración, una union puede ser inicializada únicamente con un valor del mismo tipo que el primer miembro de la union. Por ejemplo, en la union anterior, la declaración union number value =10; Es una inicialización valida de la variable de union value, porque la union esta inicializada con un int, pero la siguiente declaración no sería valida: union number value = [1.43] Constantes de enumeración C proporciona un tipo final, definido por el usuario, conocido como una enumeración. Una enumeración, introducida por la palabra reservada enum, es un conjunto de constantes enteras representadas por identificadores. Estas constantes de enumeración son, en efecto, constantes simbólicas, cuyos valores pueden ser definidos automáticamente. Los valores de un enum se inician con 0, a menos de que se defina de otra manera, y se incrementan en 1. Por ejemplo enum months JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC;

Mete un string: Hello there string1 es: Hello string2 es : string literal string con espacios entre caracteres es : H e l l o

Page 24: Manua Estructura de Datos

Pag 24

Crea un nuevo tipo en enum months, en el cual los identificadores son definidos automáticamente a los enteros 0 a 11. Para numerar los meses 1 a 12, utilice la enumeración siguiente

enum monts JAN =1, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC;

Dado que el primer valor de la enumeración se define explícitamente en 1, los valores subsiguientes se incrementan en 1dando como resultado los valores 1hasta 12 Los identificadores en una enumeración deben de ser unicos. En una enumeración el valor de cada constante en la enumeración puede ser establecido explícitamente en la definición, mediante la asignación de un valor al identificador. Varios miembros pueden tener el mismo valor entero. #include <stdio.h> enum months JAN = 1, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC; main () enum months month;

char *monthName[] = “”, “January”,February”,”March”,”April”,”May”, “June”,”July”,”August”, “September”, “October”,”November”, “December”;

for (month = JAN; month <=DEC; month++) printf(“%2d%11s\n”,month, monthName[month]); return 0

Apuntadores Los apuntadores le permiten a los programas simular llamadas por referencia, crear y manipular estructuras de datos es decir, estructuras de datos que pueden crecer o encongerse como son listas enlazadas, colas de espera, pilas y árboles. Los apuntadores son variables que contienen direcciones de memoria como sus valores. Por lo regular una variable contiene directamente un valor especifico, un apuntador por otra parte, contiene la dirección de una variable que contiene un valor especifico. En este sentido un nombre de variable se refiere directamente a un valor y un apuntador se refiere indirectamente a un valor, el referirse a un valor a través de un apuntador se conoce como indirección. Los apuntadores como cualquier otra variable deben ser declarados antes de que puedan ser utilizados.

1 January 2 February 3 March 4 Apr 5 May 6 June 7 July 8 Aug 9 Sep 10 Oct 11 Nov 12 Dec

Page 25: Manua Estructura de Datos

Pag 25

int *contadorAptr, contador;

La declaración declará la variable contadorAptr siendo del tipo int*, se lee, “contadorAptr es un apuntador a int”, o bien contadorAptr apunta a un objeto de tipo entero”. También, la variable contador se declara como un entero, no un apuntador a un entero. El *solo se aplica a contadorAptr en la declaración. Los apuntadores deben ser inicializados cuando son declarados o en un enunciado de asignación. Un apuntador puede ser inicializado a 0, NULL, o una dirección. Un apuntador con el valor NULL apunta a nada. NULL es una constante simbólica, definida en el archivo de cabecera<stdio.h>. Cuando se inicializa 0 es equivalente a inicializar un apuntador a NULL. El valor 0 es el único valor entero que puede ser directamente asignado a una variable de apuntador Operadores de apuntador El &, u operador de dirección, es un operador unario que regresa la dirección de su operando. Por ejemplo, suponiendo las declaraciones int y= 5; int *yPtr; el enunciado yPtr = &y; asigna la dirección de la variable y a la variable de apuntador yPtr. La variable yPtr se dice que apunta a

Representación gráfica de un apuntador apuntando a una variable entera en memoria.

7

7 contadorAptr contador

contador

ContadorAptr se refiere en forma indirecta a la variable cuyo valor es 7

Contador se refiere directamente a la variable cuyo valor es 7.

y

yPtr

Page 26: Manua Estructura de Datos

Pag 26

Representación en memoria de y y yPtr. La dirección de a y el valor de aPtr son idénticos en la salida, confirmando asi que de hecho la dirección de a ha sido asignada a la variable de apuntador aPtr. Los operadores & * son complementos el uno del otro. En C se utilizan los apuntadores y el operador de indirección para simular llamadas por referencia. Cuando se pasan direcciones de los argumentos que deban ser modificados, se pasan las direcciones de los argumentos. Esto se lleva a cabo aplicando el operador de dirección (&), a la variable cuyo valor deberá ser modificado. Cuando se pasa a una función la dirección de una variable, el operador de indirección (*), puede ser utilizado en la función para modificar el valor de esa posición en la memoria de ese llamador #include <stdio.h> CuboPorReferencia(int *); main() int numero = 5; printf(“El valor original del número es %d\n”,number) cuboPorReferencia(&number); printf(“El nuevo valor de el número es %d\n”,number); return 0; void cuboPorReferencia(int *nPtr) *nPtr = *nPtr * *nPtr* *nPtr; Relación entre apuntadores y arreglos Los arreglos y los apuntadores en C están relacionados en forma intima y pueden ser utilizados casi en forma indistinta . Un nombre de arreglo puede ser considerado como un apuntador constante. Los apuntadores pueden ser utilizados para hacer cualquier operación que involucre subíndices de arreglos. Suponga que han sido declarados el arreglo entero b[5] y la variable de apuntador entera bPtr. Dado que el nombre del arreglo (sin subíndice) es un apuntador al primer elemento del arreglo, podemos definir bPtr igual a la dirección del primer elemento en el arreglo b mediante el enunciado. bPtr = b; Este enunciado es equivalente a tomar la dirección del primer elemento del arreglo, como sigue bPtr = &b[0] Alternativamente el elemento del arreglo b[3] puede ser referenciado con la expresión de apuntador

yPtr 600000 5 600000

y 500000

Page 27: Manua Estructura de Datos

Pag 27

*(bPtr + 3) El 3 en la expresión arriba citada es el desplazamiento del apuntador. Cuando el apuntador apunta al principio del arreglo, el desplazamiento indica que el elemento del arreglo debe ser referenciado, y el valor del desplazamiento es idéntico al subíndice del arreglo, la notación anterior se conoce como notación apuntador/desplazamiento.Al igual que el elemento del arreglo puede ser referenciado con una expresión de apuntador, la dirección &[3] puede ser escrita con la expresión de apuntador bPtr + 3 El arreglo mismo puede ser tratado como un apuntador, y utilizado en aritmética de un apuntador. Por ejemplo la expresión

*(b + 3) también se refiere al elemento del arreglo b[3]. En general, todas las expresiones de arreglos son subíndice pueden ser escritas mediante un apuntador y un desplazamiento. Arreglos de apuntadores Los arreglos pueden contener apuntadores. Un uso común para una estructura de datos como ésta, es formar un arreglo de cadenas. Cada entrada del arreglo es una cadena pero en C una cadena es esencial un apuntador a su primer carácter. Por lo que en un arreglo de cadenas cada entrada es de hecho un apuntador al primer carácter. char *suit[4] = “Corazones”,”Diamantes”, “Espadas”,” Treboles”); La porción suit[4] indica un arreglo de 4 elementos. La porción char * de la declaración índica que cada elemento del arreglo suit es del tipo apuntador a char. Los cuatro valores a colocarse en el arreglo son: Corazones, Diamantes, Treboles y Espadas. Cada una de estas está almacenada en memoria como una cadena de caracteres terminada por NULL, de una longitud de un carácter más largo que el número de caracteres entre las comillas. En el arreglo suit parece que estan colocadas estas cadenas pero en el arreglo solo están almacenados los apuntadores. Cada apuntador señala al primer carácter de su cadena correspondiente. Por lo tanto, aunque el arreglo suit es de tamaño fijo, permite el acceso a cadenas de caarácter de cualquier longitud.

Un ejemplo gráfico de un arreglo suit Asignación dinámica de memoria mediante new y delete Los operadores new y delete de C++ le permiten a los programas llevar a cabo la asignación dinámica de memoria. En ANSI C, la asignación dinámica de memoria por lo general se lleva a cabo con las funciones estandar de biblioteca malloc y free. Considere la declaración TypeName *ptr;

Suit[0] Suit[1] Suit[2] Suit[3]

C o r a z o n e s \0

D i a m a n t e s \0

T r e b o l e s \0

E s p a d a s \0

Page 28: Manua Estructura de Datos

Pag 28

Donde typeName es cualquier tipo (como int, float, char, etcétera). En ANSI C, el enunciado siguiente asigna en forma dinámica un objeto typeName, regresa un apuntador void al objeto, y asigna dicho apuntador a ptr Ptr = malloc (sizeof(typeName)) En C la asignación dinámica de memoria requiere una llamada de función a malloc y una referencia explicita al operador sizeof (o una mensión explicita al número necesario de bytes). La memoria se asigna sobre el montón (memoria adicional disponible para el programa en tiempo de ejecución). También, en puestas en práctica anteriores a ANSI C, el apuntador regresado por malloc debe ser explícitamente convertido (cast) al tipo apropiado de apuntador con el convertidor explicito (cast) (typeName*). En C++, el enunciado Ptr = new typeName Asigna memoria para un objeto del tipo typeName partiendo de la tienda libre del programa. El operador new crea automáticamente un objeto del tamaño apropiado y regresa un apuntador del tipo apropiado. Si mediante new no se puede asignar memoria, se regresa un apuntador nulo. Para liberar en C++ el espacio para este objeto se utiliza el siguiente enunciado:

delete ptr En C, se invoca la función free con el argumento ptr, a fin de desasignar memoria. El operador delete sólo puede ser utilizado para desasignar memoria ya asignada por new. Aplicar delete a un apuntador previamente desasignado puede llevar a errores, inesperados durante la ejecución del programa. Aplicar delete a un apuntador nulo no tiene efecto en la ejecución del programa. C++ permite un inicializador para un objeto recién asignado. Por ejemplo, float* cosaPtr = new float ( 3.14159); También mediante new los arreglos pueden ser creados dinámicamente. El siguiente enunciado asigna dinámicamente un arreglo de un subíndice de 100 enteros y asigna el apuntador regresado por new al apuntador entero arrayPtr int *arrayPtr; ArrayPtr = new int[100]; //creando arreglo dinámico Para desasignar la memoria asignada dinámicamente para arrayPtr por new, utilice el enunciado delete [] arrayPtr; Ordenamiento La ordenación o clasificación de datos (sort) es una operación consistente en disponer un conjunto –estructura- de datos, en algún determinado orden con respecto a uno de los campos elementos del conjunto. Los elementos numéricos se pueden ordenar en orden creciente o decreciente de acuerdo al valor numérico del elemento. En terminología de ordenación, el elemento por el cual esta ordenado un conjunto de datos se denomina clave. Una colección de datos puede ser almacenada en un archivo, un arreglo (vector o tabla), un arreglo de registros, una lista enlazada o un árbol. Cuando los datos están almacenados en un arreglo, una lista enlazada o un árbol, se denomina ordenación interna. Si los datos están almacenados en un archivo el proceso de ordenación se llama ordenación externa.

Page 29: Manua Estructura de Datos

Pag 29

Los métodos (algoritmos) de ordenación son numerosos, por ello se debe prestar especial atención en su elección ¿Cómo se sabe cual es el mejor algoritmo? La eficiencia es el factor que mide la calidad y rendimiento de un algoritmo.

1. tiempo menor de ejecución en computadora 2. menor número de instrucciones

Los métodos de ordenación se suelen dividir en dos grandes grupos:

directos burbuja, selección, inserción indirectos(avanzados) Shell, ordenación rápida, ordenación por mezcla

Ordenación por burbuja Este método es clásico y muy sencillo, aunque por desgracia poco eficiente. La ordenación por burbuja se basa en la comparación de elementos adyacentes de la lista (vector) e intercambiar sus valores si están desordenados. De este modo se dice que los valores más pequeños burbujean hacia la parte superior de la lista, mientras que los valores más grandes se hunden hacía el fondo de la lista. Supongamos un vector A[1], A[2],...,A[n]. Se comienza el seguimiento del vector de izquierda a derecha, comparando A[1] con A[2]; si están desordenados se intercambian entre sí. A continuación se compara A[2] con A[3], intercambiándolos si están desordenados, este proceso de comparaciones e intercambios continua a lo largo de toda la lista. Estas operaciones constituyen una pasada a través de la lista. Al terminar esta pasada el elemento mayor está en la parte inferior y algunos de los elementos han burbujeado hacia arriba de la lista. Se vuelve a explorar de nuevo la lista comparando elementos consecutivos e intercambiándolos cuando estén desordenados, pero esta vez el elemento mayor no se compara, ya que se encuentra en su posición correcta, al terminar esta pasada se habrá situado en su sitio el segundo elemento más grande se siguen las comparaciones hasta que toda la lista este ordenada cosa que sucederá cuando se hayan realizado (n-1) pasadas. Para su mejor comprensión, veamos gráficamente el proceso anterior con un vector (lista) con cinco elementos; A[1], A[2], A[3], A[4], A[5].

A[1] 23 15

A[2] 19 19

A[3] 45 23

A[4] 31 31

A[5] 15 44

Lista sin ordenar Lista ordenada En la lista A, i será el número de la pasada y j indica el orden del elemento de la lista. Se compara el elemento j-esimo y el (j+1)-ésimo.

Page 30: Manua Estructura de Datos

Pag 30

Pasada 1: i=1

j=1 j=2 j=3 j=4 Se han realizado cuatro comparaciones (5-1 o bien n-1 en el caso de n elementos) y tres intercambios. Pasada 2: i = 2

J=1 j=2 j=3 j=4 Pasada 3: i = 3

j=1 j=2 j=3 j=4

A[1] A[2] A[3] A[4] A[5]

23

19

45

31

15

19

23

45

31

15

19

23

45

31

15

19

23

31

45

15

19

23

31

15

45

elemento ordenado

A[1] A[2] A[3] A[4] A[5]

19

23

31

15

45

19

23

31

15

45

19

23

31

15

45

19

23

15

31

45

19

23

15

31

45

elemento ordenado

A[1] A[2] A[3] A[4] A[5]

19

23

15

31

45

19

23

15

31

45

19

15

23

31

45

19

15

23

31

45

19

15

23

31

45

elemento ordenado

Page 31: Manua Estructura de Datos

Pag 31

Pasada 4: i =4

j=1 j=2 j=3 j=4 j=5

Se observa que se necesitan cuatro pasadas para ordenar una lista de números de cinco elementos, por lo que una lista de n elementos necesitará n-1 pasadas. El número de pasadas se puede controlar con un bucle for y cada secuencia de comparaciones se puede controlar con un bucle for anidado al bucle de pasadas, en el que j varía desde 1 hasta 5 menos el valor especifico de i. Algoritmo (Pseudocódigo) Desde i = 1hasta n-1 hacer Desde j= 1 hasta n-i hacer Si A[j] > A[j+1] Entonces Intercambio (A[j],A[j+1]) Fin_si Fin _desde bucle j Fin_desde bucle i void burbuja(int Lista[],int N) int i, j,aux; for (j=1; j<N; j++) for (i=0; i < N-j;i++) if (Lista[i]> Lista[I+1]) aux = Lista[I]; Lista[i] =Lista[i+1]; Lista[i+1]= aux; Burbuja mejorado El algoritmo burbuja se puede mejorar si disponemos de algún tipo de indicador que registre si se han producido intercambios en la pasada. Cuando se exploré la lista y el indicador no refleje intercambios, la lista estará ya ocupada y se terminarán las comparaciones. El intercambio será una variable lógica NoIntercambio (o bien ordenado) que se inicializa a 1 (significa que la lista a priori está desordenada). Si dos elementos se intercambian en una pasada. No Intercambio se pone en 0. Al principio de cada pasada NoIntercambio se fija a 1 y a 0 si se produce a intercambios. El bucle externo for se sustituye por un bucle do while o bien while y un contenido i se necesitara para contar el número de pasadas.

A[1] A[2] A[3] A[4] A[5]

19

15

23

31

45

15

19

23

31

45

15

19

23

31

45

15

19

23

31

45

15

19

23

31

45

elemento ordenado

Page 32: Manua Estructura de Datos

Pag 32

i =1 Repetir NoIntercambio = true Desde j = i hasta n – i hacer Si A[j] > A[J+1] Entonces Intercambio (A[j], A[j+1])

NoIntercambio = false Fin si Fin_desde i = i+1 Hasta que NoIntercambio = true void burbuja_mejorada(int Lista[],int N) int i,j=1,aux,bandera=1; while (j<N && bandera==1) Bandera=0; for(I=0;I<N-j;I++) if (Lista[I]>Lista[I+1]) Bandera=1; Aux=Lista[I]; Lista[i]=Lista[i]; Lista[i+1]=aux; j++; Ordenación por inserción Este método está basado en la técnica utilizada por los jugadores de cartas para clasificar sus cartas. El jugador va colocando (insertando) cada carta en su posición correcta. Tres cartas 2 6 10

Cuatro cartas

2 6 //////// 9 ////////

10

Cinco cartas 2 6 //////// 7

//////// 9 10

El método se basa en considerar una parte de la lista ya ordenada y situar cada uno de los elementos restantes insertándolo en el lugar que le corresponde por su valor. A[1] A[2] A[3] A[4] A[5]

A[N]

1 4 10 15

6

Page 33: Manua Estructura de Datos

Pag 33

Algoritmo para cada elemento de la lista después del primero desde k = 2 hasta n hacer

Guardar el valor de este elemento A[k] en una variable Aux. Hacer espacio para Aux desplazando todos los valores mayores que dicho valor

A[k] una posición. Insertar el valor de Aux en el lugar del ultimo valor desplazado. Fin_desde

La operación de desplazamiento se realiza con un procedimiento Desplazar, que mueve todos los elementos de la lista mayores que Aux, comenzando con el elemento de la lista de posición Aux-1. Si Aux es el valor más pequeño hasta aquí, la operación de desplazamiento termina después que todos los elementos de la lista se han desplazado. Si Aux no es el valor más pequeño, la operación de desplazamiento termina cuando un valor menor o igual a Aux se alcanza. Aux se inserta en la posición que ocupaba el último valor que se desplazó. Algoritmo de desplazamiento Mientras el primer elemento no se desplaza y el valor del elemento > Aux hacer

o Desplazar elemento una posición. o Comprobar valor del siguiente elemento. o Definir NuevaPos como posición original del último elemento desplazado.

Fin_mientras Codificación del procedimiento OrdenarInserción void OrdenacionInsercion(int Lista[];int n) /*Lista (entrada/salida) , n (entrada) */ /*Lista = array de N elementos enteros */ int k, /*subindice del siguiente elemento al que se inserta*/ nueva_pos, /*subindice de este elemento después de la inserción*/ aux; for(k=2;k<=n;k++) aux = lista[k]; /*obtener siguiente elemento a insertar*/ Lista[nueva_pos(Lista,k,aux)]=aux; /*insertar aux en posición nueva*/ return 0; La función de nueva posición que desplaza todos los elementos de la lista int nueva_pos (int Lista[],int k,int aux ) int encontrado ; indicador /*desplazar valores > Aux . Comenzar con el elemento K – 1 */ encontrado= 0; while (k > 1 && !encontrado) if (Lista [k– 1] > aux) Lista[k] = Lista[k – 1];

1 4 6 10 15

Page 34: Manua Estructura de Datos

Pag 34

k--; else encontrado = 1; return k; Desplazar Ordenación por selección El algoritmo de ordenación por selección de una lista (vector) de n elementos tiene los siguientes pasos:

1. Encontrar el elemento mayor de la lista. 2. Intercambiar el elemento mayor con el elemento de subíndice n (o bien el elemento menor

en el subíndice 1). 3. A continuación se busca el elemento mayor en la sublista de subíndices 1...n – 1, y se

intercambiaba con el elemento de subíndice n – 1: por consiguiente, se sitúa el segundo elemento mayor en la posición n-1.

4. A continuación se busca el elemento mayor en la sublista 1...n – 2 y así sucesivamente.

El algoritmo de PosMayor debe guardar j como la posición del elemento mayor y luego poder intercambiar. int PosMayor (int Ultimo,int Lista[] ) /*encuentra el indice del elemento mayor en la tabla [1.. Ultimo]*/ int Indice_Max, Indice; Indice_Max = 1; for(Indice= 2; Indice<=Ultimo;Indice++) if (Lista [Indice] > Lista [Indice_Max] ) indice_Max = Indice; return Indice_Max; void Seleccion (int Limi,int Lista[]) int Aux, J, Mayor; for(J=Limi; >=2 ;J++)

A[1] A[2] A[3] A[4] A[5]

5

14

-2

10 2

5 2

-2

10

14

5 2

-2

10

14

-2 2 5

10

14

Lista desordenada

Page 35: Manua Estructura de Datos

Pag 35

/*encontrar el elemento mayor de 1..J*/ Mayor = PosMayor (J, Lista); /*Intercambio con el elemento Tabla [J]*/ Aux = Lista[Mayor]; Lista[Mayor] = Lista[J]; Lista [J] = Aux; Ordenamiento Shell La ordenación Shell debe el nombre a su inventor, D. L. Shell. Se suele denominar también ordenación por disminución de incremento (gap). La idea general del método (algoritmo) es la siguiente: Lista original 504 88 513 62 908 171 898 277 654 427 150 510 612 675 750 704

1. Se divide la lista original (16 elementos, en este ejemplo) en ocho grupos de dos (consideramos un incremento o intervalo de 16/2 = 8).

2. Se clasifica cada grupo por separado (se comparan las parejas de elementos y si no estan ordenados se intercambian entre si de posiciones).

3. Se divide ahora la lista en cuatro grupos de cuatro (intervalo o salto de 8/2 = 4) y nuevamente se clasifica cada grupo por separado.

4. Un tercer paso clasifica dos grupos de ocho registros y luego un cuarto paso completa el trabajo clasificando todos los 16 registros.

Primer paso (división/ordenación por 8) 504 88 513 62 908 171 898 277 654 427 150 510 612 675 750 704

Page 36: Manua Estructura de Datos

Pag 36

Segundo paso (división/ordenación por 4) 504 88 150 62 612 171 760 277 654 427 513 510 908 675 898 704

Tercer paso (división/ordenación por 2) 504 88 150 62 612 171 513 277 654 427 760 510 908 675 898 704

Cuarto paso (división/ordenación por 1) 154 62 504 88 513 171 612 277 654 427 760 510 898 675 908 704

62 88 154 171 277 427 504 510 513 612 654 675 704 760 898 908 El algoritmo de Shell tiene diferentes modelos más populares y citados en numerosas obras de programación Algoritmo Intervalo = n div 2 Mientras (intervalo > 0 ) hacer Desde i = (intervalo + 1) hasta n hacer

Page 37: Manua Estructura de Datos

Pag 37

J = i – intervalo Mientras (j >0 ) hacer K = j + intervalo Si ( a [j] <= a[k]) Entonces J = 0 Si no Intercambio (a[j], a[k] Fin si j = j – intervalo Fin mientras Fin desde Intervalo = intervalo div 2 Fin mientras Código void Intercambio (int X,int Y) int Aux ; Aux = X; X = Y; Y =Aux; void Shell (int Lista[],int N) int Intervalo =N/2, I, J, K ; while (Intervalo > 0) for( I = Intervalo + 1; I <=N;I++) J = I – Intervalo; while (J > 0) K = J + Intervalo; if (Lista[J] <= Lista[K]) J = 0; else Intercambio (*(Lista +J),*(Lista +K)); J = J – Intervalo; while Intervalo /= 2; return 0;

Page 38: Manua Estructura de Datos

Pag 38

Ordenamiento Mezcla (MergeSort) El proceso de mezcla, fusión o intercalación (merge) consiste en tomar dos vectores ordenados (a, b) y obtener un nuevo vector también ordenado. El algoritmo más sencillo para resolver el problema es:

1. Situar todos los elementos del vector a en el nuevo vector c. 2. Situar todos los elementos del vector b en el nuevo vector c. 3. Ordenar todo el vector c.

Esta solución no tiene en cuenta que los valores de de a y b ya están ordenados. El algoritmo que tiene en cuenta la ordenación es el siguiente:

1. Seleccionar el elemento de valor o clave mas pequeño con cualquiera de dos vectores y situarlo en el nuevo vector c.

2. Comparar a(i) y b(i) y poner el elemento de vector más pequeño en c(k). 3. Seguir esta secuencia de comparaciones hasta que los elementos de un vector se hayan

agotado en cuyo momento se copia el resto del otro vector en c. Ejemplo: Mezclar las dos listas de números a y b. 2 4 78 97 lista A -15 0 13 15 78 90 96 lista B

Lista C

2 4 78 97

-15 0 13 15 78 90 94 96

-15 0

i

j

k

<

Lista A

Lista B

Lista C

j se ha incrementado junto con k

Page 39: Manua Estructura de Datos

Pag 39

B < A[j], de modo que C[k] se obtiene de B[j] Procedimiento mezcla de los vectores A y B void Mezcla (int A[],int B[],int C[],int M,int N ) int I, J, K ; /*A Y B : entrada. Vectores ya ordenados*/ I=J=K=1; /*M Y N: número de elementos de A y B respectivamente*/ while ((I <= M) &&(J <= N)) /*C : salida. Vector mezcla ordenado*/ if (A[I] <= B[J]) /*El tipo Lista, tendrá una longitud minima de M + N elementos*/ C [K] = A[I]; I++; else C [K] = B[J]; J ++; K++; copiar el resto del vector no agotado if (I > M) for( P=J;P<=N;P++) C [K] = B [P]; K++; else for( P=I;P< M;P++) C [K] = A [P]; K++; return 0;

2 4 78 97

-15 0 13 15 78 90 94 96

-15

i

j

k

<

Lista A

Lista B

Comparar A[i] Y B[j]. Poner el Más pequeño en C[k]. Incrementar Los indices apropiados

Page 40: Manua Estructura de Datos

Pag 40

Búsquedas Búsqueda secuencial Un problema importante en el proceso de datos, como ya se ha comentado, es la búsqueda en un conjunto de datos de un elemento especifico y la recuperación de alguna información asociada al mismo. La búsqueda lineal o secuencial es la técnica más simple para buscar un elemento en un arreglo (vector). Consiste el método en el recorrido de todo el vector, desde el primer elemento hasta el último, y de uno en uno. Si el vector contiene el elemento, el proceso devolverá la posición del elemento buscado y en caso contrario un mensaje que indique la falta de éxito en la búsqueda. Supongamos una lista de números de la seguridad Social incluidos en un arreglo a y se desea buscar a ver si existe el número 453714. Números Seguridad Social

Mediante un bucle desde ir comparando el elemento t buscando con a[i], donde i varía, en el ejemplo anterior, de 1 a 100. En caso de encontrarlo, almacenar la posición (el indice arreglo) del mismo dentro de la lista y finalmente se devolverá al programa principal. Pseudocódigo 1 Posición = 0 lista = vector a[i] de n elementos desde i = 1 hasta n hacer si a[i] = t entonces Posición = i fin si fin desde Este algoritmo tiene un inconveniente, sea cual sea el resultado se recorre el vector completo. El algoritmo se puede mejorar con un bucle while o do_while, y utilizando unas banderas que detecten cuando se encuentre el elemento. El bucle se terminará por dos causas:

La bandera o indicador toma el valor esperado y la búsqueda ha tenido éxito. El valor del índice i es mayor que el número de términos de la lista, lo que significa que se

ha terminado de recorrer la misma y el elemento buscado no ha aparecido. Pseudocódigo 2

451871

120467 401321 25761

- - -

339412 81467 924116

Elemento a buscar: t 453714

A[1 A [2] A [3] A[4]

A[98] A[99]

A[100]

- - -

Page 41: Manua Estructura de Datos

Pag 41

Encontrado = falso Posición = 0 i = 1 mientras (i <= n) y (No Encontrado=verdadero) hacer si a[i] = t entonces Posición = i Encontrado = verdadero Fin si i = i + 1 fin_mientras Función búsqueda lineal int BusquedaLineal (int A[]; /*entrada, vector búsqueda*/ int N ; /* entrada, número de elementos */ int T /*elemento a buscar*/) int Encontrado=0,I=1,Posicion; Posicion= 0; /*posición del elemento caso de no éxitir*/ while (I <= N) && ! Encontrado) if (A[I] == T ) Posicion= I; Encontrado = 1; fin del if y del while I ++; return 0; posición del elemento en la lista Búsqueda Binaria La búsqueda lineal, por sus simplicidad es buena para listas de datos pequeñas para listas grandes es ineficiente, la búsqueda binaria es el método idóneo. Se basa en el conocido método divide y vencerás. Este método tiene una clara expresión en la búsqueda de una palabra en un diccionario. Cuando se busca una palabra no se comienza la búsqueda por la página 1 y se sigue secuencialmente sino que se abre el diccionario por una pagina donde aproximadamente se piensa puede estar la palabra, es decir se divide el diccionario en dos partes, al abrir la página se ve si se ha acertado o en que parte se encuentra la palabra buscada. Se repite este proceso hasta que por divisiones o aproximaciones sucesivas se encuentra la palabra. Supongamos que la lista donde se busca es

1331 1373 1555 1850 1892 1898 1989 2002 2400 2670 3200

Elemento central Elemento buscado

Page 42: Manua Estructura de Datos

Pag 42

Y que se busca el número 1989 Se examina en primer lugar el elemento central de la lista (las divisiones se toman iguales) 1898. Dado que 1989 es mayor que 1898, el elemento a buscar estará en la segunda mitad. Por consiguiente, se sigue la búsqueda en esta mitad:

El elemento central en esta sublista es 2400, y como1989 es menor, la nueva sublista donde buscar es

como ya no hay elemento central se toma el número inmediatamente anterior de la posición central, que en este caso es 1989. En este caso se ha encontrado el elemento deseado en tres comparaciones, mientras que en la búsqueda lineal hubiese necesitado al menos seis comparaciones. Este método es muy eficiente con el único inconveniente, que requiere la lista ordenada. Algoritmo

1. Establecer Primero = 1 y Ultimo = n (n, número de elementos). Estas variables representan la primera y última posición de la lista o sublista donde se está buscando y permite el calculo de la posición del elemento central.

2. Encontrado = falso . 3. mientras Primero <= Ultimo y Encontrado = falso hacer

Encontrar posición central Central = (Primero + Ultimo) div 2 Comparar elemento buscado t con A[Central] si t = a [Central] entonces Encontrado = verdadero sino si t > A [Central] entonces Primero = Central + 1 sino Ultimo = Central –1 fin mientras

4. si Encontrado = verdadero entonces Posición = Central existe elemento si no Posición = 0 no se ha encontrado fin_si

1989 2002 2400 2670 3200

Elemento central

Elemento a buscar

1989 2002

Elemento considerado central

Page 43: Manua Estructura de Datos

Pag 43

La búsqueda binaria requiere una ordenación previa del vector o lista en el que se va ha efectuar la búsqueda. Por consiguiente, las acciones típicas (módulos) en un algoritmo de búsqueda binaria son:

1. Lectura del vector 2. Ordenación del vector 3. Búsqueda binaria 4. Visualizar resultado

int Binaria (int T, int L[] ,int N ) int Primero, Ultimo, Central, Encontrado; Primero = 1; Ultimo = N; Encontrado = 0 ; while ((Primero <= Ultimo) && !Encontrado) Central = ( Primero + Ultimo) / 2; if (T == L [Central]) Encontrado = 1; else if ( T > L [Central]) Primero = Central + 1; else Ultimo = Central –1; if (!Encontrado) return 0; else return Central; Estructuras de datos lineales, representaciones secuenciales. Conceptos fundamentales: Tipo de Dato, Tipo de Dato Abstracto, Estructura de Datos, Registros. Concepto de estructuras de datos Aunque los terminos tipo de datos (o simplemente <<tipo>>), <<estructura de datos>> y <<tipo de dato abstracto>> parecen semejantes, su significado es diferente. Una estructura de datos es una colección de datos organizados de un modo particular, las estructuras de datos pueden ser de dos tipos: estructuras de datos estáticas y estructuras de datos dinámicas. Las estructuras de datos estáticas son aquellas en las que se asigna una cantidad fija de memoria cuando se declara la variable, en numerosas ocasiones se necesitan, colecciones de datos que crezcan y reduzcan su tamaño en memoria a medida que el programa progresa, a estas estructuras de datos cuya ocupación en memoria puede aumentar o disminuir en tiempo de ejecución se denominan estructuras dinámicas de datos. En un lenguaje de programación, el tipo de datos de una variable es el conjunto de valores que este puede tomar. Por ejemplo una variable de tipo booleano puede tomar los valores verdadero o falso, pero ningún otro, los tipos de datos básicos varian de un lenguaje a otro; en C son enteros

Page 44: Manua Estructura de Datos

Pag 44

(int), real (float), y carácter (char). Las reglas para construir tipos de datos compuestos a partir de los básicos también varían de un lenguaje a otro Un tipo de datos abstracto (TDA) es un modelo matemático, junto con varias operaciones definidas sobre ese modelo. Para representar el modelo matemático básico de un TDA se emplean estructuras de datos, que son conjuntos de variables quiza de tipos distintos, conectadas entre si de diversas formas. El componente básico de una estructura de datos es la celda. Se puede representar una celda como una caja capaz de almacenar un valor tomado de algún tipo de datos básico o compuesto, las estructuras de datos se crean dando nombres a agregados de celdas El mecanismo de agregación más sencillo en C y en la mayor parte de los lenguajes de programación es el arreglo (unidimensional), que es una sucesión de celdas de un tipo dado al cual se llamará casi siempre <<tipo_celda>> . Estructuras Las estructuras son colecciones de variables relacionadas –a veces denominados agregados bajo un nombre. Las estructuras pueden contener variables de muchos tipos diferentes de datos a diferencia de los arreglos, que contienen unicamente elemento de un mismo tipo de datos. Generalmente las estructuras se utilizan para definir registros a almacenar en archivos. Creación de estructuras Crear una estructura es definir un nuevo tipo de datos, denominado tipo estructura y declarar una variable de este tipo. En la definición del tipo estructura, y declarar una variable de este tipo. En la definición del tipo estructura, se especifican los elementos que la componen así como sus tipos. Cada elemento de la estructura recibe el nombre de miembro (campo del registro). La síntaxis es la siguiente: struct tipo_estructura declaraciones de los miembros ; tipo_estructura es un identificador que nombra el nuevo tipo definido. Después de definir un tipo estructura, podemos declarar una variable de ese tipo, de la forma: struct tipo_estructura [variable[, variable]…]; Para referirse a un determinado miembro de la estructura, se utiliza la notación: variable.miembro Ejemplo: struct ficha /* definición del tipo estructura ficha */ char nombre[40]; char dirección[40]; long telefono; ; La anterior definición no reserva ningún espacio en memoria, más bien genera un nuevo tipo de datos, que se utiliza para declarar variables.

Page 45: Manua Estructura de Datos

Pag 45

struct ficha var1, var2; Este ejemplo define las variables var1 y var2, de tipo ficha, por lo que cada una de las variables consta de los miembros: nombre, dirección y teléfono. Una variable que es un miembro de una estructura, puede utilizarse exactamente igual que cualquier otra variable. Ejemplo: var1. Telefono = 232323; gets(var2.nombre); La declaración de las variables var1 y var2, puede realizarse también directamente de la siguiente forma:

struct ficha

char nombre[40]; char direccion[40]; long telefono;

var1, var2; La declaración de un miembro de una estructura no puede contener calificadores de clase de almacenamiento extern, static, auto o register y no puede ser inicializado. Su tipo puede ser: fundamental, array, puntero, unión, estructura o función. Los miembros de la estructura pueden ser variables de los tipos de datos básicos, o agregados, como son los arreglos y otras estructuras. Una estructura no puede tener una instancia de si misma, sin embargo pudiera ser incluido un apuntador a la estructura ( estructura autoreferenciada). struct card char false[10]; char suit[10]; a, deck[52]; El nombre del rótulo es opcional. Si la definición de una estructura no contiene un nombre de rótulo e estructura, las variables de ese tipo de estructura pueden únicamente ser declaradas dentro de la definición de la estructura. La únicas operaciones válidas que pueden ejecutarse sobre estructuras son : asignar variables de estructura a variables de estructura del mismo tipo, tomando la dirección (&) de una variable de estructura obteniendo acceso a los miembros de una varible de estructura, y utilizando el operador sizeof, a fin de determinar el tamaño de la variable de estructura. Como inicializar estructuras Las estructuras pueden ser inicializadas mediante listas de inicialización como los arreglos. struct card a = (“Three”, “Hearts”);

Page 46: Manua Estructura de Datos

Pag 46

Como tener acceso a los miembros de estructuras Para tener acceso a miembros de estructuras se utilia el operador de miembro de estructura (.). printf (”%s”, a.suit); Cómo utilizar estructuras con funciones Las estructuras pueden ser pasadas a funciones pasando miembros de estructuras individuales, pasando toda la estructura o pasando un apuntador a una estructura. Cuando se pasan estructuras o miembros individuales de estructura a una función se pasan en llamada por valor. Para pasar una estructura en llamada por referencia, pase la dirección de la variable de estructura. Los arreglos de estructura como todos los demás arreglos son automáticamente pasados en llamada por referencia. typedef La palabra reservada typedef proporciona un mecanismo para la creación de sinónimos (o alias)para tipos de datos anteriormente definidos typedef struct card Card; define el nuevo nombre de tipo Card como un sinónimo para el tipo struct card. Lo anterior se puede expresar de la siguiente forma typedef struct char false[10]; char suit[10]; Card; Card puede ser utilizado para declarar variables de tipo struct card. La declaración Card deck[52]; Al crear un nuevo nombre utilizando typedef no se crea un nuevo tipo; typedef simplemente crea un nuevo nombre de tipo que puede ser utilizado como un seudónimo para un nombre de tipo existente. Ejemplo: El siguiente programa lee una lista de alumnos y sus correspondientes notas de final de curso, dando como resultado el tanto porciento de alumnos aprobados y suspendidos #include <stdio.h> #include <stdlib.h> #define NA 10 main() struct ficha char nombre[60]; float nota; ; struct ficha alumnos[NA]; /*arreglo de estructuras o registros */ int n =0,i; char *fin; /* Puntero al nombre leido*/

Page 47: Manua Estructura de Datos

Pag 47

float aprobados = 0, suspensos = 0; /*Entrada de datos*/ printf(“Finalizar la entrada con cont/Z\n\n”); printf(“Nombre”); fin = gets(alumnos[n].nombre); while (n <NA && fin ¡=NULL) printf(“Nota “); scanf(“%f”,&alumnos[n++].nota); printf(“Nombre “); fin =gets(alumnos[n].nombre); for (i =0;i<n>i++) if (alumnos[i].nota >=5) aprobados ++; else suspensos ++; printf(“Aprobados %.2g %%\n”,aprobados /n*100); printf(“Suspensos %.2g %% \n”,suspensos/n*100); T.D.A. Lista Modelo Matemático Las listas constituyen una estructura flexible en particular, por que pueden crecer y acortarse según se requiera, los elementos son accesibles y se pueden insertar y suprimir en cualquier posición de la lista, las listas también pueden concatenarse entre si o dividirse en sublistas; se representan de manera rutinaría en aplicaciones como manera de aplicación. Matemáticamente, una lista es una secuencia de cero o más elementos de un tipo determinado. A menudo se representa una lista como una sucesión de elementos separados por comas A1, a2, a3,.....an Donde n ≥ 0 y cada a1 es de tipo tipo_elemento. Al número n de elementos se le llama longitud de la lista. Al suponer que n ≥ 1, se dice que a1 es el primer elemento y an el último elemento. Si n = 0, se tiene una lista vacía, es decir, que no tiene elementos. Una propiedad importante de una lista es que sus elementos pueden estar ordenados en forma lineal de acuerdo con sus posiciones en la misma. Se dice que ai precede a ai+1 para i = 1, 2,...n-1 y que ai sucede a ai-1 para i = 2, 3,...n. Se dice que el elemento ai está en la posición i. Es conveniente postular también la existencia de una posición que sucede al último elemento de la lista. La función FIN(L) devolverá la posición que sigue a la posición que sigue a la posición n en una lista L de n elementos. Observese que la posición FIN(L), con respecto al principio de la lista, está en una distancia que varía conforme la lista crece o se reduce, mientras que las demas posiciones guardan una distancia fija con respecto al principio de la lista. Para formar un tipo de dato abstracto a partir de la noción matemática de la lista, se debe de definir un conjunto de operaciones con objetos de tipo lista.

Page 48: Manua Estructura de Datos

Pag 48

Operaciones Se representará ahora un conjunto representativo de operaciones con listas. Ahí, L es una lista de objetos de tipo tipo_elemento, x es un objeto de ese tipo y p es de tipo posición. Observese que <<posición>> es otro tipo de datos cuya implantación cambiará con aquella que se haya elegido para las listas. Aunque de manera informal se piensa en las posiciones como enteros, en la practica pueden tener otra representación.

1. INSERTA(x, p, L). Esta función inserta x en la posición p de la lista L, pasando los elementos de la posición p y siguientes a la posición inmediata posterior. Esto quiere decir que si L es a1,a2,...,an, se convierte en a1, a2,..., ap-1, x, ap,..., an. Si p es FIN(L) entonces L se convierte en a1, a2,...., an, x. Si la lista L no tiene posición p, el resultado es indefinido.

2. LOCALIZA(x, L). Esta función devuelve la posición de x en la lista L. Si x figura más de una vez en L, la posición de la primera aparición de x es la que se devuelve. Si x no figura en la lista entonces se devuelve FIN(L).

3. RECUPERA(p, L). Esta función devuelve el elemento que esta en la posición p de la lista L. El resultado no está definido si p = FIN(L) o si L no tiene la posición p. Obsérvese que si se utiliza RECUPERA, los elementos deben ser de un tipo que pueda ser devuelto por una función. No obstante en la practica siempre es posible modificar RECUPERA para devolver un apuntador a un objeto de tipo elemento.

4. SUPRIME(p, L). Esta función elimina el elemento en la posición p de la lista L. Si L es a1, a2,...,an, L se convierte en a1, a2,...ap-1, ap+1,..., an. El resultado no está definido si L no tiene posición p o si p = FIN(L).

5. SIGUIENTE(p, L) y ANTERIOR (p,L) devuelven las posiciones siguiente y anterior, respectivamente, a p en la lista L. Si p es la última posición de L, SIGUIENTE(p, L) =FIN(L). SIGUIENTE no esta definida si p es FIN(L). ANTERIOR no esta definida si p es 1, ambas funciones no estan definidas cuando L no tiene posición P.

6. ANULA(L). Esta función ocasiona que L se convierta en la lista vacía y devuelve la posición FIN(L).

7. PRIMERO(L). Esta función devuelve la primera posición de la lista L. Si L está vacía, la posición que se devuelve es FIN(L).

8. IMPRIME_LISTA(L). Imprime los elementos de L en su orden de aparición en la lista. Implementación de listas con arreglos En la realización de una lista mediante arreglos, los elementos de esta se almacenan en celdas contiguas de un arreglo. Esta representación permite recorrer con facilidad una lista y agregarle elementos nuevos al final, pero insertar un elemento en la mitad de la lista obliga a desplazarse una posición dentro del arreglo a todos los elementos que siguen al nuevo elemento para concederle espacio. De la misma forma la eliminación de un elemento, excepto el último, requiere desplazamientos de elementos para llenar de nuevo el vacio formado.

Page 49: Manua Estructura de Datos

Pag 49

En la realización con arreglos se define el tipo LISTA como un registro con dos campos, el primero es un arreglo de elementos que tiene la longitud adecuada para contener la lista de mayor tamaño que se puede representar. El segundo campo es un entero últ que indica la posición del último elemento de la lista en el arreglo. El i-ésimo elemento de la lista está en la i-ésima posición, mediante el entero i. La función FIN(L) sólo tiene que devolver últ +1. Las declaraciones importantes son: #include <stdio.h> #include <conio.h> # define TAM 10 # define TRUE 1 # define FALSE 0 typedef int dato; typedef int posicion; typedef char logico; struct LISTA dato elem[TAM]; posicion ult; ; typedef struct LISTA Lista; posicion Fin(Lista &L); void Anula(Lista &L); void Imprime(Lista L); posicion Ultimo(Lista &L); logico Vacia(Lista &L); logico Llena(Lista &L); posicion Primero(Lista &L); posicion Siguiente(posicion p, Lista &L); posicion Anterior(posicion p, Lista &L);

primer elemento segundo elemento

último elemento

últ

Long_máx

lista

vacío

1 2

Page 50: Manua Estructura de Datos

Pag 50

void Insertar(dato x, posicion p, Lista &L); void Suprimir(posicion p, Lista &L); posicion Localiza(dato x, Lista &L); dato Recupera(posicion p, Lista &L); posicion Fin(Lista &L) return (L.ult+1); void Anula(Lista &L) L.ult=-1; posicion Ultimo(Lista &L) if (L.ult >=0) return L.ult; else return -1; posicion Primero(Lista &L) if (L.ult >=0) return (0); else return -1; posicion Siguiente(posicion p, Lista &L) if (p>=L.ult || p< 0 ) return(-1); else return (++p); posicion Anterior(posicion p, Lista &L) if (p>L.ult || p== 0 ) return(-1); else return (--p); logico Vacia(Lista &L) if (L.ult==-1) return 1; else return 0; logico Llena(Lista &L) if (L.ult==TAM-1) return 1 ;

Page 51: Manua Estructura de Datos

Pag 51

else return 0; void Insertar(dato x, posicion p, Lista &L) posicion q; if ( Llena(L) ) printf("Lista Llena"); else if (p>Fin(L) || p< 0) printf( "Posicion invalida"); else for ( q= L.ult; q>=p; q--) L.elem[q+1]=L.elem[q]; L.elem[p]=x; L.ult++; void Suprimir(posicion p, Lista &L) posicion q; if (Vacia(L)) printf("Lista Vacia"); else if (p>L.ult || p< 0) printf( "Posicion invalida"); else for ( q= p; q<L.ult; q++) L.elem[q]=L.elem[q+1]; L.ult--; posicion Localiza(dato x, Lista &L) posicion q=0; while (q<Fin(L) && L.elem[q]!=x) q++; if (q==Fin(L)) return (-1); else return(q); dato Recupera(posicion p, Lista &L) if (p>L.ult || p< 0) printf( "Posicion invalida"); return -1;

Page 52: Manua Estructura de Datos

Pag 52

else return(L.elem[p]); void Imprime(Lista L) posicion q; for ( q=0; q<Fin(L); q++) printf("%d\n",L.elem[q]); void main( void ) Lista L; dato x; posicion p; int i; clrscr(); Anula(L); Insertar(1,0,L); for(i=0;i<9;i++) printf("Dame el numero"); scanf("%d",&x); Insertar(x,Primero(L),L); Imprime(L); printf("La posicion el numero"); scanf("%d",&p); Suprimir(p,L); clrscr(); Imprime(L); x=Recupera(1,L); printf("%d",x); x=Anterior(3,L); printf("%d",x); x=Siguiente(5,L); printf("%d",x); getch(); Ventajas y Desventajas de la implementación de Listas con arreglos. La implementación con arreglos requiere especificar el tamaño máximo de una lista en tiempo de compilación, esto quiere decir que el tamaño es fijo y no puede establecerse mientras se está ejecutando el programa, lo cual implica conocer con exactitud la longitud de la Lista para evitar desperdicio de memoria o bien si se estableció de un tamaño máximo y al utilizarla se rebasa, esto implicaría falla del programa, en otras palabras, un arreglo es una variable estática. Las operaciones como INSERTA y SUPRIME requieren un tiempo proporcional al número de elementos que haya que desplazar, y por lo general estás son las operaciones más importantes. Una ventaja es que como es bien sabido, los arreglos son estructuras de acceso directo, por lo que las demás operaciones son triviales, así que una representación con arreglos en un momento dado pudiera ser de utilidad.

Page 53: Manua Estructura de Datos

Pag 53

El tipo de Dato Abstracto PILA Una pila es un tipo especial de Lista en la que todas las inserciones y supresiones tienen lugar en un extremo denominado tope. Son estructuras LIFO (Last In First Out) o último en entrar, primero en salir en las que solo es posible quitar el elemento que se encuentra en la parte superior de la pila (tope), son muy útiles en muchos aspectos de la programación, tales como evaluación de expresiones, anidación de parentisis, asi como en la implementación de rutinas recursivas, entre muchas otras aplicaciones. Una estructura de este tipo generalmente incluye las Operaciones siguientes:

1. ANULA(P). Que convierte la pila en una Pila Vacía. 2. TOPE(P). Devuelve el elemento que está en el tope de la Pila. 3. POP(P). Suprime el elemento que está en el tope de la pila P. 4. PUSH(x, P). Inserta el elemento x en la parte superior de la Pila. 5. VACIA(P). Devuelve verdadero si la Pila está vacía y falso en caso contrario. 6. LLENA(P). Regresa verdadero si la Pila está llena y falso si no lo está.

Implementación de Pilas basadas en Arreglos Para representar una Pila con arreglos es posible hacerlo de manera similar como se hizo con las Listas con arreglos, establecemos la Pila como un registro don dos campos, el primero un arreglo para contener los datos de la Pila y un campo para almacenar la posición del elemento superior de la pila (que en lo sucesivo llamaremos tope). #define TAM 38 typedef char tipo_elem; typedef int logico; struct Pila Tipo_elem elemento[TAM]; Int tope; ;

1 2 3 4

tope . . .

TAM

Page 54: Manua Estructura de Datos

Pag 54

void ANULA(Pila &P) P.tope =TAM +1; Creamos una Pila Vacía tipo_elem TOPE(Pila &P) if (VACIA(P)) return TAM +1; else return L.elemento[P.tope]; void POP(Pila &P) if (VACIA(P)) printf (“Error, Pila Vacía\n”); else P.tope++; /*Eliminamos el elemento del tope*/ void PUSH(tipo_elemento x,Pila &P) if (LLENA(P)) printf(“Error, Pila Llena\n”) else P.tope--; P.elemento[P.tope]=x; int VACIA(Pila &P) return P.tope==TAM+1; int LLENA(Pila &P) return P.tope==1; Notación Polaca Las expresiones se componen de operandos, operadores y delimitadores. Los operandos son valores numéricos que se utilizan para calcular la expresión. Los operadores indican las operaciones matemáticas que van hacerse sobre los operandos respectivos. También determinan la cantidad de operandos necesarios para cada tipo de operación (binarios y unarios). Es evidente que el orden en que se calculan las operaciones puede ser muy importante, como en la expresión 6 + 4/2. Si la resolvemos como (6+4)/2, la respuesta es 5, si lo hacemos como 6 + (4/2), el resultado es 8. Los operadores tienen su precedencia

Page 55: Manua Estructura de Datos

Pag 55

Operador Valor 3 x,/ 2 +,- 1 Para cambiar el orden de cálculo de una expresión, pueden utilizarse paréntisis pero en su ausencia las operaciones de mayor precedencia se resuelven primero. Cuando una expresión incluye operaciones de igual precedencia, se calculan de izquierda a derecha. La forma más usual de representar una expresión es la forma infija, es decir, colocamos los operadores entre sus operandos. A + B (infija) A B + (posfija o polaca) La segunda forma con el operador después del operando se conoce como notación posfija (polaca). Al utilizar las reglas de precedencia de operadores, podemos convertir expresiones infijas a la notación polaca correspondiente, los pasos que debemos seguir son:

1. Encerrar entre parentisis toda la expresión infija. 2. Volver a colocar (mover) los operadores, uno por uno y en orden de precedencia, a su

posición final en notación postfija (a la derecha de sus operandos). 3. Quitar los parentisis.

Por ejemplo, vamos a convertir la expresión a + b X c a notación polaca. El primer paso es agregar los paréntesis: a + (bXc) Después se colocan los operadores en orden de precedencia, así el primero que debemos mover es el signo de multiplicación, X, para que la expresión resultante sea de este modo: a + (bcX) Los dos operandos del operador X son b y c, por lo que es fácil determinar la posición posfija de ese signo, pero cuales son los dos operandos del operador +?, la respuesta es a y el resultado de la subexpresión (bXc). Y ponemos el operador + después del parentisis de cierre: a(bcX)+ El paso final es quitar el paréntesis a b c X + Ahora, usando paréntesis, vamos a cambiar el orden del cálculo de los operandos y a convertir la expresión (a + b) X c en notación polaca: (a + b) X c expresión infija (a + b) X c se añaden paréntesis sin cambio (ab +)X c se convirtió el + (ab +) c X se convirtió el X ab + c X se eliminó el paréntesis

Page 56: Manua Estructura de Datos

Pag 56

Recursividad Un subprograma (procedimiento o función) recursivo es aquel que se llama así mismo. La recursividad es una alternativa a la iteración o repetición, y aunque en tiempo de computadora y en ocupación de memoria es la solución recursiva menos eficiente que la solución iterativa, existen numerosas situaciones en las que la recursividad es una solución simple y natural a un problema que en caso contrario sería difícil de resolver. Una aplicación importante de las pilas se da en la aplicación de procedimientos recursivos, en los lenguajes de programación. La organización a tiempo de ejecución de uno de tales lenguajes es el conjunto de estructuras de datos usadas para representar los valores de las variables de un programa durante su ejecución. Todo lenguaje que como Pascal, permita procedimientos recursivos, utiliza una pila de registros de activación, para representar los valores de todas las variables que pertenecen a cada procedimiento activo de un programa. Cuando se llama a un procedimiento P, se coloca en la pila un nuevo registro de activación para P, con independencia de si ya existe en ella otro registro de activación para ese mismo procedimiento. Cuando P vuelve, su registro de activación debe estar en el tope de la pila, pueso que P no puede volver si no lo han hecho todos los procedimientos a los que P ha llamado. Así, se puede sacar de la pila el registro de activación correspondiente a la llamada actual de P y hacer que el control regrese al punto en el que P fue llamado (este punto, conocido como dirección de retorno, se colocó en el registro de activación de P al llamar a este procedimiento). Ordenamiento Rápido (Quick Sort) Es uno de los métodos más rápidos y frecuentemente utilizados en ordenación (Quick Sort) Fue inventado por C.H. Hoare, y la cantidad de código necesario es sorprendentemente pequeño comparando con la excelente velocidad que proporciona. La idea básica de la ordenación rápida es:

Elegir un elemento de la lista denominado pivote. Dividir o partir la lista original en dos sublistas o mitades, de modo que en una de ellas

estén todos los elementos menores que el pivote; Las sublistas deben ser ordenadas, independientemente, del mismo modo, lo que conduce

a un algoritmo recursivo. La elección del pivote es arbitraria aunque por comodidad es usual utilizar el termino central de la lista original, o bien el primero o el último elemento de la misma.

9 23 31 17 21 19 13 15 26

1. Elijamos el elemento pivote; supongamos el término central, 21.

9 23 31 17 // 21//

19 13 15 26

pivote 2. A continuación se establecen dos punteros en la lista I o J. El primer puntero apunta al

primer elemento. Por consiguiente, I =1. El segundo puntero apunta al último elemento y, por lo tanto, J=9 (noveno elemento).

9 23 31 17 //

21// 19 13 15 26

I= 1 J=9

Page 57: Manua Estructura de Datos

Pag 57

3. Mientras I apunte a un elemento que sea menor que 20, se incrementa el valor de I en 1,

hasta que se encuentre un elemento mayor que el pivote. A continuación se realiza la misma tarea con el puntero J, buscando un elemento menor que 21, y mientras no lo encuentra se decrementa J en 1.

9 23 31 17 // 21//

19 13 15 26

I J 4. Se intercambian los elementos apuntados por I y J y a continucación se incrementan en

uno los contadores I, J.

9 15 31 17 // 21//

19 13 23 26

I J 5. El proceso se repite

9 15 13 17 // 21//

19 31 23 26

I J

9 15 13 17 // 21//

19 31 23 26

I J

9 15 13 17 // 19//

21 31 23 26

J I

6. En el momento en que J < I, se ha terminado la partición. Se han generado dos sublistas que tienen las propiedades citadas: la primera sublista, todos los elementos menores o iguales a 20, y en segunda todos los elementos mayores que 20.

Page 58: Manua Estructura de Datos

Pag 58

Sublista Izquierda Sublista Derecha 9 15 13 17 19 21 31 23 26 I J I J

9 15 13 17 19 21 31 23 26

I J I J 9 15 13 17 19 21 23 31 26

I J J I 9 13 15 17 19 31 26

J I I J 9 13 15 17 19 31 26

J I J I Sublista Izquierda II Sublista izquierda 12 9 13 15 17 19 Sublista izquierda 1 ordenada 9 13 15 17 19 Lista Ordenada

9 13 15 17 // 19//

21 23 26 31

Page 59: Manua Estructura de Datos

Pag 59

Algoritmo de Ordenación Rápida:

1. Inicializar I a Primero (primer indice de la lista) 2. Inicializar J a Ultimo (último indice en la lista) 3. Seleccionar el elemento pivote (termino Central)

Central = [(Primero + Ultimo) div 2] 4. Repetir

Mientras A[I] < Central hacer I=I + 1 Mientras A[J] > Central hacer J=J+1 Si I <= J entonces Intercambiar A[I],A[J] hasta que J < I Hasta _que I>J

5. Si J > Primero, llamar al procedimiento Partir, para dividir la sublista izquierda [Primero..J] 6. Si I < Ultimo, llamará al procedimiento Partir, para dividir la sublista derecha [J..Ultimo]

Código para Quick Sort typedef int enteros; enteros Lista[] void Intercambiar(int &m,int &n ) int Aux ; aux =m; /*intercambiar*/ m = n; n = aux; void Partir(int primero,int ultimo,enteros Lista[] ) int I,J, Central; partir I =primero; J =ultimo; //encontrar elemento pivote central Central = lista [(primero + ultimo)div 2]; do while (lista[I]< central) I++; while (lista[J]> central) J--; if (I <= J) Intercambiar(lista[I], lista[J]); I++; J--; // if while (I<=J); if (primero < J) Partir (primero, J,Lista); if (I<ultimo) Partir(I, ultimo,Lista) partir void rapido(enteros a[],int N) Partir (l, n,a)

Page 60: Manua Estructura de Datos

Pag 60

Torres de Hanoi Un caso típico de resolución de un problema con un método recursivo es el juego de niños conocidos como torres de hanoi. Se dispone de tres postes (1,2,3) con soportes de madera (varillas de alambre o similar) y un juego de discos de diferentes tamaños (el número de ellos se leerá en el programa principal) que se situan en el primer poste, el disco de mayor tamaño (diámetro) se sitúa en el fondo y el más pequeño en la parte superior. El juego consiste en mover los discos del poste 1 al poste 3 de acuerdo a las siguientes reglas:

1. Sólo un disco se puede mover a la vez. 2. Un disco nunca puede estar encima de otro disco con un diámetro más pequeño. 3. Un disco siempre debe estar en uno de los postes (Excepto cuando se este moviendo.

Análisis El problema a primera vista parece sencillo, pero su solución es francamente difícil y sólo la solución recursiva facilita la resolución. Tres, cuatro discos son imaginables, 64 (las leyendas citan esta cifra como la propuesta de un rey tibetano a sus subditos, al estilo del también famoso problema del tiempo necesario para llenar un tablero de ajedrez en progresión geométrica) es prácticamente inimaginable y casi imposible, sin solución recursiva. Algoritmo (3 discos)

Mover dos (3-1) discos desde el poste 1 hasta el poste 2. Mover el disco más grande desde el poste 1 hasta el poste 3. Mover los dos discos desde el poste 2 hasta el poste 3 utilizando el poste 1.

Algoritmo (n discos)

Mover n-1 discos desde 1 hasta el 2 utilizando el poste 3. Mover el disco restante desde 1 hasta el 3. Mover la torre de n-1 discos desde el poste 3 utilizando el poste 1.

Situación inicial

Page 61: Manua Estructura de Datos

Pag 61

Poste 1 Poste 2 Poste 3

Poste 1 Poste 2 Poste 3 Este modelo de solución es recursivo. #include typedef int Poste; typedef int Pos ; Pos NumDiscos; void Mover Disco (Poste Desde,Poste Hasta ) printf (“mover un disco desde el poste %d“, desde); printf(“hasta el poste %d\n”, hasta); void MoverTorre(Pos N,Poste Uno,Poste Dos,Poste Tres) if (N==1) MoverDisco (Uno, Tres); else MoverTorre (N – 1, Uno, Tres, Dos); MoverDisco (Uno, Tres); Mover Torre ( N – 1, Dos, Uno, Tres); main() printf (“Introduzca número de discos en juego\n”); scanf(“%d”,&NumDiscos); printf(“Para %d discos”, NumDiscos); printf (“Los movimientos sucesivos son :\n”); MoverTorre(NumDiscos, 1, 2, 3); El número de movimientos H(n) para una torre de n discos se calcula así:

Page 62: Manua Estructura de Datos

Pag 62

H(1) =1 H(n) = 1 + 2 H(n-1) (n>1) Con lo que se puede deducir H(n) = 2 E(n) – 1 para n > = 1 Para una torre de 4 discos los movimientos son 15 y para una torre de 64 discos los movimientos son inimaginables 2 E(64) – 1. Colas Una cola es otro tipo especial de lista en el cual los elementos se insertan en un extremo (el posterior ) y se suprimen en el otro (el anterior o frente). Las colas se conocen también como listas <<FIFO>> (first-in, first out) o listas <<primero en entrar, primero en salir>>. Las operaciones para una cola son análogas a las de las pilas, las diferencias sustanciales consisten en que las inserciones se hacen al final de la lista, y no al principio, y en que la terminología tradicional para colas y listas no es la misma. Se usarán las siguientes operaciones con colas. Operaciones

1. ANULA( C) convierte la cola C en una lista vacía. 2. FRENTE( C) es una función que devuelve el valor del primer elemento de la cola C.

FRENTE( C) se puede escribir en función de operaciones con listas, como RECUPERA (PRIMERO( C), C).

3. PONE_EN_COLA(x, C) inserta el elemento x al final de la cola C. En función de operaciones con listas, PONE_EN_COLA(x, C) es INSERTA(x, FIN( C), C).

4. QUITA_DE_COLA( C) suprime el primer elemento de C; es decir, QUITA_DE_COLA( C) es SUPRIME(PRIMERO( C), C).

5. VACIA( C) devuelve verdadero si, y sólo si, C es una cola vacía. Implementación de Colas basadas en Arreglos La representación de colas por medio de arreglos, se le conoce como Colas Circulares. La estructura de datos para representarlas se muestra a continuación: #define TAM 20 #define TRUE 1 #define FALSO 0 typedef int tipo_elem,logico; typedef struct Tipo_elem elemento [TAM]; Int final, frente; Cola;

Page 63: Manua Estructura de Datos

Pag 63

En la figura se representa una cola vacía, esto sucede si anterior está en la posición que sigue a la posición del posterior. void ANULA(COLA &C) C.Final =TAM-1; C.Frente =1; logico VACIA(COLA &C) if ((C.final+1==C.frente) || (C.final ==TAM-1 && C.frente==0)) return (TRUE); else return (FALSE); tipo_elemento FRENTE (COLA &C) if (VACIA ( C)) return -1; else return (C.elementos[C.Frente]); void PONE_EN_COLA(tipo_elem x;COLA &C) if (LLENA( C))) printf(“la cola está llena”) else if (C.final==TAM-1) C.final =0; else C.final++; c.elem[C.final]=x;

Final Frente

Page 64: Manua Estructura de Datos

Pag 64

void QUITA_DE_COLA(COLA &C) if (VACIA ( C)) printf(“la cola está vacía”) else if(C.frente==TAM-1) C.frente =0; else C-frente++; Concepto de apuntador En una computadora cada posición de memoría tiene una dirección y un valor especifico almacenado en esa posición. Se han utilizado nombres de variables en lugar de direcciones. Para almacenar un nuevo valor a la memoria se asigna a una variable, y la computadora envía una dirección a la memoria seguida por el valor a almacenar en esa posición. Con los apuntadores se puede hacer referencia a variables por sus direcciones. Considerese un programa que procese registros de empleados [es común que los registros de empleados sean muy largos; para nuestro ejemplo, supondremos que su tamaño es de 2048 bytes. Supongamos que ya hemos escrito una función para la nómina que procesa estos registros e imprime recibos. Una forma de suministrarle datos a nuestra función es pasarle cada uno de los registros de empleados como argumento. Estructuras autoreferenciadas Una estructura autoreferenciada contiene un miembro de apuntador que apunta a una estructura del mismo tipo de estructura. Por ejemplo, la definición struct nodo int dato; struct nodo *proxPtr; ; Define un tipo struct nodo. Una estructura del tipo struct nodo tiene dos miembros el miembro entero dato y el miembro de apuntador proxPtr. El miembro nextPtr apunga a una estructura de tipo struct nodo – Una estructura del mismo tipo que la que se está declarando aquí, de ahí el “termino estructura autorreferenciada”. El miembro proxPtr se conoce como un enlace o vínculo es decir proxPtr puede ser utilizada para vincular una estructura del tipo struct nodo con otra estructura del mismo tipo. Las estructuras autorreferenciadas pueden ser enlazadas juntas para formar útiles estructuras de datos como son las listas, las colas de esperas, las pilas y los árboles.

Dos estructuras autoreferenciadas enlazadas juntas Listas implementadas con cursores Algunos lenguajes, como FORTRAN y ALGOL, no tienen apuntadores. Si se trabaja con un lenguaje tal, se pueden simular los apuntadores mediante cursores; esto es, con enteros que indican posiciones en arreglos. Se crea un arreglo de registros para almacenar todas las listas de

15 10

Page 65: Manua Estructura de Datos

Pag 65

elementos cuyo tipo es tipo_elemento; cada registro contiene un elemento y un entero que se usa como curso Es decir, se define #define TAM 10 typedef int Tipo_elem; struct ESPACIO Tipo_elem Elemento; int sig; Lista[TAM]; int dis,Prim; Para la realización de este tipo de listas nos podemos auxiliar de una variable llamada Disponible, que nos da la posición del arreglo del primer disponible o vacía y en el caso de saber donde inicia la lista hacemos uso de una variable entera que llamaremos Primero.

Estructuras de Datos lineales, representaciones ligadas Teoría de lista ligadas La segunda forma de realización de listas, celdas enlazadas sencillas, utiliza apuntadores para enlazar elementos consecutivos. Esta implantación permite eludir el empleo de memoria contigua para almacenar una lista y, por tanto, también elude los desplazamientos de elementos para hacer inserciones o rellenar vacíos creados por la eliminación de elementos. No obstante, por esto hay que pagar el precio de un espacio adicional para los apuntadores.

1

ESPACIO

Prim

Disponible

D C A E B

8 4 1 6 9 7 0 5 0 10 2

2 3 4 5 6 7 8 9

10 11

Elemento sig

Page 66: Manua Estructura de Datos

Pag 66

Listas con encabezado Lista simplemente ligada En esta representación, una lista está formada por celdas; cada celda contiene un elemento de la lista y un apuntador a la siguiente celda. Si la lista a1, a2, a3,...,an, la celda que contiene ai tiene un apuntador a la celda que contiene a ai+1, para i=1,2,...,n – 1. La celda que contiene an posee un apuntador a NULL. Existe también una celda de encabezamiento que apunta a la celda que contiene a1; esta celda de encabezamiento no tiene ningún elemento. En este caso hablamos de una lista simplemente ligada con nodo de encabezamiento vacío, en la que el empleo de una celda completa para el encabezado simplifica la implementación de las operaciones para manipular la lista, aunque también se puede utilizar el encabezado para almacenar el primer elemento y a este tipo de representación se le conoce como lista simplemente ligada con nodo de encabezamiento no vacío, en la que las inserciones y supresiones al principio de la lista se manejan de manera especial. En el caso de una lista con encabezado vacio, el apuntador al siguiente nodo es NULL ya que no se tienen más celdas. La estructura de datos que emplearemos para representar una lista con apuntadores será un registro con dos campos, uno para guardar el elemento de la lista y otro para mantener la dirección del siguiente nodo. A continuación se muestra la figura de una lista simplemente ligada lineal con nodo de encabezamiento vacío y longitud n.

Las operaciones más comunes a las listas se muestran a continuación: typedef tipo_elemento: struct nodo tipo_elemento elemento; nodo *sig; typedef nodo *Nodo; void INICIALIZA ( Nodo Encabezado) Encabezado =new(nodo); //Se crea el nodo de encabezado Encabezado->sig =NULL; //Se le pone nil al campo siguiente, ya que la lista está vacía La siguiente figura muestra una lista vacía

encabezado

A1 A2 An

Lista

• • •

encabezado

Page 67: Manua Estructura de Datos

Pag 67

void INSERTA (tipo_elemento x, Nodo p ) //Coloca el elemento x delante de la celda que apuntada por p Nodo aux; aux= new(nodo); //Se reserva memoria para el nuevo nodo aux-> elemento=x; //Se almacena el elemento aux->sig =p->sig; //Se enlaza con el siguiente nodo de p p->sig=aux; //p se enlaza con el nuevo nodo

void SUPRIME(Nodo p) //Suprime el nodo siguiente al apuntado por p Nodo aux; Aux=p->sig; //Se almacena la dirección del nodo que se desea eliminar p->sig=aux->sig //Se enlaza con el nodo siguiente al que se quiere suprimir delete(aux); //Se libera el espacio de memoria ocupada por el nodo

Nodo LOCALIZA(tipo_elemento x,Nodo p) //Si encuentra el dato en la lista devuelve el apuntador al nodo anterior a donde lo encontró, //en caso contrario regresa nil while ((p->sig!=NULL)&&(p->sig->elemento!=x) p=p->sig; if (p->sig=NULL) return NULL; else return p; tipo_elemento RECUPERA (Nodo p) //regresa el elemento del nodo siguiente al apuntado por p if (!VACIA(p)) return p->sig->elemento; else

encabezado

A1 A2 An • • •

x p

aux

encabezado

A1 A2 An • • • x

p aux

Page 68: Manua Estructura de Datos

Pag 68

printf(“Error, no hay elemento”); int VACIA (Nodo p) VACIA=p->sig==NULL; void IMPRIME (Nodo p) if (VACIA(p)) printf(“Error, Lista Vacía”); else while (!VACIA(p)) printf(“%d”,RECUPERA(p)); p=p->sig; Lista simplemente ligada circular La implementación de una lista también puede hacerse mediante una lista simplemente ligada circular, que es muy similar a la anterior, con la diferencia de que el último nodo contiene en su campo siguiente un apuntador al encabezado en lugar de NULL. La estructura de datos que se emplea para representar este tipo de lista es la misma que para una lista ligada lineal, y al igual que estas también el nodo de encabezado puede contener información.

Una desventaja de las listas lineales es que dado un apuntador p a un nodo de la lista, no se puede tener acceso a cualquier otro nodo anterior a p. Si se recorre una lista, el apuntador al encabezado no debe ser modificado a fin de poder hacer referencia a esta lista. Si hacemos un pequeño cambio en la estructura de tal manera que el último nodo en lugar de tener en el campo siguiente un apuntador nulo (nil) tenga la dirección al inicio de la lista, entonces desde cualquier otro punto de la lista es posible llegar a cualquier otro punto. En este caso la lista se llama lista circular. typedef int tipo_elemento struct nodo Tipo_elemento Elemento; nodo *sig; typedef nodo *Nodo; void INICIALIZA(Nodo Encabezado) //Se crea el nodo de encabezado encabezado = new(nodo); //Se hace que el campo siguiente del

encabezado

A1 A2 An

Lista

• • •

Page 69: Manua Estructura de Datos

Pag 69

encabezado->sig = encabezado; encabezado se apunte asi mismo

void INSERTA (tipo_elemento x, Nodo p) /*Coloca el elemento x delante de la celda apuntada por p es exactamente igual que una lista lineal */ Nodo aux; aux =new(nodo); /*Se reserva memoria para el nuevo nodo*/ aux->elemento =x; /*Se almacena el elemento*/ aux->sig =p^->sig; /*Se enlaza con el siguiente nodo de p*/ p->sig = aux; /*p se enlaza con el nuevo nodo*/ void SUPRIME(Nodo p) //Suprime el nodo siguiente al apuntado por p Nodo aux; Aux =p->sig; //Se almacena la dirección del nodo que se desea eliminar p->sig =aux->sig; //Se enlaza con el nodo siguiente al que se quiere suprimir delete(aux); //Se libera el espacio de memoria ocupado por el nod Nodo LOCALIZA(tipo_elemento x,Nodo p) /*Si se encuentra el dato en la lista devuelve el apuntador al nodo anterior a donde lo encontró, en caso contrario regresa nil, en este caso hay que almacenar la dirección del nodo de encabezado para saber cuando recorrimos toda la lista*/ Nodo q; q=p; while (p->sig!=q) &&(p->sig->elemento!=x) p =p->.sig; if (p->sig==q) return NULL; else return p; tipo_elem RECUPERA(Nodo p) //Regresa el elemento del nodo siguiente al apuntado por p if (p->sig!=p) return p->sig->elemento; else return -1;

encabezado

Page 70: Manua Estructura de Datos

Pag 70

int VACIA(Nodo p) VACIA=p->sig==p //Si el que sigue del nodo de encabezado, es él mismo entonces no hay datos

void IMPRIME(Nodo p) Nodo q; if (VACIA(p )) printf(“Error, Lista Vacía\n”); else q = p; while (p!=q->sig) printf(“%d”,RECUPERA(p)); p = p->sig; Ventajas de la representación con apuntadores Operaciones como INSERTAR Y SUPRIMIR tienen un número constante de pasos para este tipo de listas, sin importar cuántos elementos contenga, a diferencia de la representación con arreglos que requiere un tiempo proporcional al número de elementos que siguen. En la implementación con arreglos se desperdicia espacio, independientemente de el número de elementos que en realidad tiene la lista en un momento dado, mientras que la representación con apuntadores utiliza sólo el espacio necesario para los elementos que contenga la lista en cada momento, pero requiere espacio para almacenar el apuntador a la siguiente celda. Asi que cualquiera de los dos métodos podría usar más espacio que el otro dependiendo de las circunstancias. Lista doblemente ligada lineal En algunas aplicaciones puede ser deseable poder recorrer eficientemente una lista, tanto hacía adelante como hacia atrás, o dado un elemento podría determinarse con rapidez el siguiente y el anterior. Para representar estas situaciones se emplean las listas Doblemente ligadas, en las que cada nodo o celda de la lista además de contener el elemento, contiene dos apuntadores, uno a la celda siguiente y otro a la celda anterior. De la misma manera que las listas simplemente ligadas, estás cuentan con un nodo de encabezamiento, que contiene la dirección del nodo que contiene al primer elemento de la lista o bien este nodo de encabezamiento puede contener al primer dato. Aun cuando una Lista Circular tiene ventajas sobre una Lista Lineal, ésta todavía tiene algunas deficiencias, uno no puede recorrer esta lista en dirección contraria, ni tampoco se puede eliminar un nodo de una lista simplemente ligada circular simplemente un apuntador a ese nodo. En el caso de que se requieran tener estas flexibilidades la estructura de datos para representar una lista con apuntadores es la lista doblemente ligada. Como se mencionó anteriormente, cada nodo en esta lista contiene dos apuntadores, uno es predecesor y el otro a su sucesor. Este tipo de lista puede ser tanto lineal como circular y puede contener o no un nodo de encabezado.

• • •

Page 71: Manua Estructura de Datos

Pag 71

La estructura de datos para representar este tipo de Lista sería un nodo com un registro con tres campos, uno para almacenar al elemento de la lista, y dos para almacenar las direcciones de los nodos siguiente y anterior a uno dado. Para insertar un dato hay que saber si se quiere insertar a la derecha o a la izquierda, además de que la inserción al principio y al final son diferentes, y sucede lo mismo para suprimir. typedef int tipo_elem struct nodo Tipo_elem elemento; nodo *sig, *ant ; ; typedef nodo *Nodo; Una lista Vacía se representaría de la siguiente manera:

void INICIALIZA (Nodo Encabezado) Encabezado =new(nodo); //Se crea el nodo de encabezado Encabezado->sig =NULL; Encabezado->ant =NULL; La siguiente figura muestra una lista vacía void INSERTA (tipo_elemento x, Nodo p ) //Coloca el elemento x delante de la celda que apuntada por p Nodo aux; aux = new(nodo); //Se reserva memoria para el nuevo nodo aux-> elemento =x; //Se almacena el elemento aux->sig =p->sig; //Se enlaza con el siguiente nodo de p aux->ant=p; if (p-<sig!=NULL) p->sig->ant =aux; REVISARp se enlaza con el nuevo nodo p->sig =aux; void SUPRIME(Nodo p) //Suprime el nodo siguiente al apuntado por p Nodo aux; aux =p->sig->sig;

nil

Encabezado

Page 72: Manua Estructura de Datos

Pag 72

delete(p->sig); p->sig=aux; if (p->sig!=NULL) aux->ant=p; Nodo LOCALIZA(tipo_elemento x,Nodo p) /*Si encuentra el dato en la lista devuelve el apuntador al nodo anterior a donde lo encontró, en caso contrario regresa nil*/ Nodo aux; aux=p; while (aux->sig!=NULL)&& (aux->sig->elemento!=x) aux =aux->sig; if (aux->sig==NULL) return NULL; else return aux; tipo_elemento RECUPERA(Nodo p) //regresa el elemento del nodo siguiente al apuntado por p if (! VACIA(p)) return p->sig->elemento; else return -1; int VACIA (Nodo p) VACIA=p->sig==NULL; Nodo ANTERIOR(Nodo p) if (p->ant->ant!=NULL) return p->ant; else return NULL; void IMPRIME (Nodo p) Nodo Aux; Aux =p; if (VACIA(p)) printf (“Error, Lista Vacía\n”); else while (!VACIA(p)) printf(“%d”,RECUPERA(p)); p=p->sig;

Page 73: Manua Estructura de Datos

Pag 73

Lista doblemente ligada circular Como se puede apreciar, este tipo de lista es similar a una lista Doblemente Ligada Lineal, pero en esta, el primer nodo (el encabezado) en su campo anterior contiene un apuntador al último nodo de la lista, mientras que el último nodo en su campo siguiente contiene la dirección del primer nodo es decir del encabezado.

typedef int tipo_elem struct nodo Tipo_elem elemento; Nodo *sig, *ant; typedef nodo *Nodo;

void INICIALIZA(Nodo Encabezado) Encabezado = new(nodo); //Se crea el nodo de encabezado Encabezado->ant =Encabezado; Encabezado->sig =Encabezado; //Se hace que el campo siguiente del //encabezado se apunte asi mismo void INSERTA (tipo_elemento x, Nodo p) /*Coloca el elemento x delante de la celda apuntada por p es exactamente igual que una lista lineal */ Nodo aux; aux =new(nodo); //Se reserva memoria para el nuevo nodo aux->elemento=x; //Se almacena el elemento aux->ant =p; aux->sig=p->sig; //Se enlaza con el siguiente nodo de p if (p->sig==p) p->ant = aux //p se enlaza con el nuevo nodo else p->sig->ant =aux; p->sig =aux; void SUPRIME(Nodo p)

• • •

Encabezado

Page 74: Manua Estructura de Datos

Pag 74

//Suprime el nodo siguiente al apuntado por p Nodo aux; aux=p->sig->sig; delete (p->sig); p->sig = aux; aux->ant=p; Nodo LOCALIZA(tipo_elemento x,Nodo p) /*Si se encuentra el dato en la lista devuelve el apuntador al nodo anterior a donde lo encontró, en caso contrario regresa nil, en este caso hay que almacenar la dirección del nodo de encabezado para saber cuando recorrimos toda la lista*/ Nodo aux; aux=p; while ((p->sig!=aux) && (p->sig->elemento!=x)) p=p->sig; if (p->sig==aux) then return NULL; else return p; tipo_elem RECUPERA(Nodo p) //Regresa el elemento del nodo siguiente al apuntado por p if (p->sig!=p) return =p->sig->elemento; else return -1; int VACIA(Nodo p) return p->sig==p; //Si el que sigue del nodo de encabezado, es él mismo // entonces no hay datos

void IMPRIME(Nodo p) Nodo q; if (VACIA(p )) printf (“Error, Lista Vacía”); else q=p; while(p!=q->sig) printf(“%d”,RECUPERA(p)); p=p->sig; Listas sin encabezado Lista simplemente ligada sin encabezado typedef int tipo_elemento; struct nodo tipo_elemento elemento; nodo *sig;

Page 75: Manua Estructura de Datos

Pag 75

typedef nodo *Nodo; void INICIALIZA (Nodo L) L =NULL; void INSERTA (tipo_elemento x,Nodo p,Nodo L) //Coloca el elemento x delante de la celda que apuntada por p Nodo aux,nuevo; if (p==NULL) nuevo = new(nodo); nuevo->elemento=x; //Se almacena el elemento nuevo->sig =L; //Se enlaza con el siguiente nodo de p L=nuevo; //p se enlaza con el nuevo nodo else aux =L->sig; nuevo = new(nodo); nuevo->tipo_elemento:=x; nuevo->sig = aux; L->sig =Nuevo; void SUPRIME(Nodo p,Nodo &L) Suprime el nodo siguiente al apuntado por p Nodo aux; if (p==NULL) aux:=L->sig; delete(L); L=aux; else aux=p->sig->sig; delete (p->sig); p->sig =aux; Nodo LOCALIZA(tipo_elemento x,Nodo L) /*Si encuentra el dato en la lista devuelve el apuntador al nodo anterior a donde lo encontró, en caso contrario regresa nil*/ if (L->info==x) then LOCALIZA:=nil else while (L->sig!NULL && L->sig->elemento!=x) L =L->sig; if (L->sig==NULL) return NULL; else return L; tipo_elemento RECUPERA (Nodo p)

Page 76: Manua Estructura de Datos

Pag 76

//regresa el elemento del nodo siguiente al apuntado por p if (!VACIA(p)) return p->sig->elemento; else printf(“Error, no hay elemento”); int VACIA (Nodo p) return L==NULL; void IMPRIME (Nodo p) while (L!=NULL) printf(“%d”,L->elemento); L=L->sig; void ANULA (Nodo L) Nodo aux; while (L!= NULL) aux=L->sig; delete (L); L=aux; Implementacion de Pilas basadas en apuntadores La representación de pilas basadas en apuntadores es similar a la de una lista simplemente ligada lineal, en donde el encabezado es el topo de la pila, y las inserciones y supresiones siempre se hacen en el tope (encabezado). typedef char tipo_elem; struct Nodo Tipo_elem elemento; Nodo *sig; ; typedef Nodo *posicion; typedef int logico; void INICIALIZA (posición P) P = new Nodo; P ->sig=NULL logico PILA_VACIA(posicion P) if (P=nil) return 1; else return 0; void METER(tipo elem x; posicion p) posicion aux;

Page 77: Manua Estructura de Datos

Pag 77

aux = new(Nodo); aux->elemento=x; aux->sig:=P->sig; P ->sig = aux; void SACAR(posición P) posicion Aux:; if (!PILA_VACIA(P)) Aux=P->sig; P->sig =Aux->sig; delete(Aux); else printf(“Pila vacía\n”); elemento TOPE(posicion P) if (PILA_VACIA (P)) return -1; else return P->sig->elemento; Implementacion de Colas basadas en apuntadores typedef char tipo_elem; struct nodo Tipo_elem elemento; nodo *sig; ; typedef nodo *Nodo; typedef int logico; struct Cola Nodo Fondo,Frente; void INICIALIZA (Cola C) C.Frente=NULL; C.Fondo=NULL; int VACIA(Cola C) if (C.Frente=NULL) return 1; else return 0; void METER (elemento x;Cola C); Nodo Aux; Aux =new(Nodo); Aux->elemento =x;

Page 78: Manua Estructura de Datos

Pag 78

Aux->sig =NULL; if (C.Fondo!=NULL) C.Fondo->sig =Aux; C.Fondo =Aux; else C.Frente =Aux; C.Fondo =Aux; void SACAR (Cola C) Nodo Aux:; if (! VACIA( C)) Aux:=C.Frente; C.Frente =Aux->.sig; delete(aux); else printf(“lista vacía”); elemento PRIMERO (Cola C) if (!VACIA( C)) return C.Frente->elemento; else return -1; elemento ULTIMO (Cola C) if (!VACIA( C)) return C.Fondo->elemento; else return -1; Estructura de datos no lineales, representaciones secuencial y ligada Teoría general de Árboles Un árbol impone una estructura jerárquica sobre una colección de objetos. Los árboles genealógicos y los organigramas son ejemplos comunes de árboles. Entre otras aplicaciones, los árboles se emplean para analizar circuitos eléctricos y para representar la estructura de fórmulas matemáticas, así como para organizar la información de bases de datos y para representar la estructura sintáctica de un programa fuente en compiladores. A continuación se representan las definiciones básicas y algunas de las operaciones más comunes con árboles y veremos como esta estructura de datos puede ser representada en Pascal. Árboles binarios Árbol Binario Un árbol binario es un conjunto finito de elementos que puede estar vacío o contener un elemento denominado la raiz del árbol, esta raíz contiene cuando mucho un subárbol izquierdo y ubárbol derecho; los cuales a su vez también son árboles binarios. A cada elemento de un árbol binario se le denomina Nodo del árbol.

Page 79: Manua Estructura de Datos

Pag 79

Formalmente, un árbol se puede definir de manera recursiva como sigue:

1. Un nodo es, por si mismo, un árbol. Ese nodo es también la raíz de dicho árbol. 2. Supongamos que n es un nodo y que A1, A2,...Ak son árboles con raíces n1,n2,...nk,

respectivamente. Se puede construir un nuevo árbol haciendo que n se convierta en el padre de los nodos n1,n2,..., nk. En dicho árbol, n es la raís y A1,A2,..,Ak son los subárboles de la raíz. Los nodos n1,n2,....nk reciben el nombre de hijos del nodo n.

Una forma de visualizar un árbol binario es considerar cada nodo conformado por tres campos fundamentales, uno para almacenar la información que contiene el nodo, y dos para almacenar la dirección del subárbol izquierdo y derecho. Si el subárbol izquierdo o derecho está vacío, contiene un apuntador nulo o nil. Arboles binarios de búsqueda Los árboles binarios pueden ser implementados como un arreglo o mediante el uso de apuntadores. Como caso particular representaremos la implementación con apuntadores de un Árbol Binario de búsqueda. Este tipo especial de árbol tiene como característica la siguiente: Para cada nodo, todos los elementos que están a su izquierda son menores que él y todos los elementos que están a su derecha son mayores, y esta definición es recursiva.

Subárbol izquierdo

Subárbol derecho

raíz

Page 80: Manua Estructura de Datos

Pag 80

Representación ligada typedef int tipo_elemento; struct nodo Tipo_elemento Info; Nodo *Izq, *Der typedef nodo *Nodo; void INICIALIZA( Nodo R) R = NULL; void INSERTA(tipo_elemento x,Nodo R) If (R==NULL) R= new(nodo); R->info =x; R->Izq =NULL; R->Der =NULL; else if (x>R->info) Si el dato a insertar es mayor INSERTA(x, R->Der) Se revisa el lado derecho else if (x<R->Info) INSERTA(x,R->Izq); Nodo BUSCAR (Info x,Nodo R) Nodo P; P=Raiz; if ((P==NULL) || (x=P->Info)) return P; else if (x<P->Info) return BUSCAR(x,P->Izq); else return BUSCAR(x,P->Der); Info MÁXIMO (Nodo P) while (P->Der !=NULL) P =P->Der; return P-> Info; printf(“%d”,P->info); Info MINIMO (Nodo P) while (P->Izq !=NULL) P=P->Izq; return P-> Info; printf(P->Info);

Page 81: Manua Estructura de Datos

Pag 81

Representaciones secuenciales Una forma conveniente de estructurar datos para representar un árbol binario consiste en dar a sus nodos los nombres 1, 2, ...,n y utilizar un arreglo de registros declarado como: Struct Nodo int Hijo_izq; int Hijo_der; Espacio_celdas[TAM_NODOS]; La idea es que espacio_celdas[i]. Hijo_izq sea el hijo izquierdo del nodo i, y que suceda lo mismo con hijo_der. Un valor 0 en cualquiera de esos campos indicará la ausencia de un hijo Recorridos En-Orden, Pre-Orden y Post-Orden Existen tres métodos de recorrer todos los nodos de un árbol, todos ellos serán definidos de manera recursiva, de tal manera que el recorrer un árbol binario comprende la visita a la raíz y el recorrido de los subárboles izquierdo y derecho. La única diferencia entre estos métodos es el orden en que se realizan estas operaciones. Recorrido en Preorden u Orden Previo

1. Visitar la raíz 2. Recorrer el subárbol izquierdo en Preorden 3. Recorrer el subárbol derecho en Preorden

Recorrido en Entreorden u Orden Simétrico

1. Recorrer el subárbol izquierdo en Entreorden 2. Visitar la raíz 3. Recorrer el subárbol derecho en Entreorden

Recorrido en Postorden u Orden Posterior

1. Recorrer el subárbol izquierdo en Postorden 2. Recorrer el subárbol derecho en Postorden 3. Visitar la raíz

typedef int tipo_elemento; struct nodo Tipo_elemento Info; Nodo *Izq, *Der; typedef nodo *Nodo; void PREORDEN(Nodo R) if (R!=NULL) printf(“%d”,R->info); //Visitamos la raíz PREORDEN(R->izq); //Recorremos el subárbol izquierdo PREORDEN(R->der); //Recorremos el subárbol derecho void ENTREORDEN (Nodo R) if (R!=NULL) ENTREORDEN(R->Izq); //Recorremos el subárbol izquierdo printf(“%d”,R->Info); //Visitamos la raíz ENTREORDEN(R->Der); //Recorremos el subárbol derecho

Page 82: Manua Estructura de Datos

Pag 82

void POSTORDEN(Nodo R) if (R =! NULL) POSTORDEN(R->Izq); POSTORDEN(R->Der); pirntf(“%d”,P->info); Grafos En los problemas originados en la computación, matemáticas, ingeniería y muchas otras disciplinas, a menudo es necesario representar relaciones arbitrarias entre objetos de datos. Los grafos los grafos dirigos y los no dirigos son modelos naturales de tales relaciones Grafo dirigido Un grafo dirigido G consiste en un conjunto de vértices V y un conjunto de arcos A. Los vértices se denominan también nodos o puntos; los arcos pueden llamarse arcos dirigidos o lineas dirigidas. Un arco es un par ordenado de vértces (v, w); y es la cola y w la cabeza del arco. El arco (v, w) se expresa a menudo como v w va de v a w, y que w es adyacente a v. Los vértices de un grafo dirido pueden usarse para representar objetos, y los arcos relacionados ente los objetos. Por ejemplo, los vértices pueden representar ciudades y los arcos, vuelos aéreos de de una ciudad a otra. Un grafo dirigo puede emplearse para representar el flujo de control en un programa de computador. Los vértces representan bloques básicos, y los arcos posibles tranasferencias del flujo de control. Un camino en un grafo dirigo es una secuencia de vértices v1, v2, ...vn, tal que v1 v2, v2 v3,.....vn-1 vn.son arcos. Este camino va del vértices v1 al vértice vn, pasa por los vértices v2, v3,...vn-1 y termina en el vértice vn. La longitud de un camino es el número de arcos en ese camino, en este caso n-1. Como caso especial, un vértice sencillo, v, por si mismo denota un camino de longitud cero de v a v. En la figura la secuencia 1, 2, 4, es un camino de longitud 2 que va del vértice 1 al vértice 4.

Grafo dirigido

1 2

3 4

Page 83: Manua Estructura de Datos

Pag 83

Un camino es simple si todos los vértices, excepto tal vez el primero y el último, son distintos. Un ciclo simple es un camino simple de longitud por lo menos uno, que empieza y termina en el mismo vértice. En la figura, el camino 3, 2, 4, 3 es un ciclo de longitud 3. En muchas aplicaciones es útil asociar información a los vértices y arcos de un grafo dirigido. Para este propósito es posible usar un grafo dirigido etiquetado, en el cual cada arco, cada vértice o ambos pueden tener una etiqueta asociada. Una etiqueta puede ser un nombre, un costo o un valor de cualquier tipo de datos dado. La siguiente figura muestra un grafo dirigido etiquetado en el que cada arco esta etiquetado con una letra que causa una transición de un vértice a otro. Este grafo dirigido etiquetado tiene la interesante propiedad de que las etiquetas de los arcos de cualquier ciclo que sale del vértice 1 y vuelve e él producen una cadena de caminos a y b en el cual los números de a y de b son pares. Un grafo dirido etiquetado, un vértice puede tener a la vez un nombre y una etiqueta. A menudo se empleará la etiqueta del vértice como si fuera el nombre. Así, los números de la figura pueden interpretarse como nombres o como etiquetas de vértices.

Representación de grafos dirigidos Para representar un grafo dirigido se pueden emplear varias estructuras de datos; la selección apropiada depende de las operaciones que se aplicarán a los vértices y a los arcos del grafo. Una representación común para un grafo dirido es G= (V,A) es la matriz de adyacencia. Supóngase que V= 1,2,...,n.La matriz de adyacencia para G es una matriz una matriz A de dimensión n X n, de elementos boléanos, donde A[i, j] es verdadero si, y sólo si, existe un arco que vaya del vértice i al j. Con frecuencia se exhibirán matrices de adyacencias con 1 para verdadero y 0 para falso.; las matrices de adyacencia pueden incluso obtenerse de esa forma. En la representación con una matriz de adyacencia, el tiempo de acceso requerido a un elemento es independiente del tamaño de V y A. Así la representación con matriz de adyacencia es útil en algoritmos para grafos, en los cuales suele ser necesario saber si un arco dado está presente. Algo muy relacionado con esto es la representación con matriz de adyacencia etiquetada de un grafo dirido, donde a[i,j] es la etiqueta del arco que va del vértice i al vértice j. Si no existe un arco de i a j, debe emplearse como entrada para A[i,j] un valor que no pueda ser una etiqueta válida.

1 2

3 4

b b b b

a a

a a

Page 84: Manua Estructura de Datos

Pag 84

Matriz de adyacencia etiquetada para el grafo dirigido de la anterior figura. La principal desventaja de usar una matriz de adyacencia para representar un grafo dirido es que requiere un espacio Ω(n²) aun si el grafo dirido tiene menos de n² arcos. Sólo leer o examinar la matriz puede llevar un tiempo O(n²), lo cual invalidaría los algoritmos O(n) para la manipulación de grafos dirigidos con O(n) arcos. Para evitar esta desventaja se puede utilizar otra representación común para un grafo dirigido G=(V,A) llamada representación con lista de adyacencia. La lista de adyacencia para un vértice i es una lista, en algún orden, de todos los vértices adyacentes a i. Se puede representar G por medio de un arreglo CABEZA, donde CABEZA[i] es un apuntador a la lista de adyacencia del vértice i. La representación con lista de adyacencia de un grafo dirigido requiere un espacio proporcional a la suma del número de vértices más el número de arcos; se usa bastante cuando el número de arcos es mucho menor que n² . Sin embargo, una desventaja potencial de la representación con lista de adyacencia es que puede llaeva un tiempo O(n) determinar si existe un arco del vértice i al vértice j, ya que puede haber O(n) vértices en la lista de adyacencia para el vértice i. La figura muestra una representación con lista de adyacencia para el grafo dirigido de la primera figura, donde se usan listas enlazadas sencillas. Si los arcos tienen etiquetas, éstas podrían incluirse en las celdas e la lista ligada.

Si hubo inserciones y supresiones en las listas de adyacencias, sería preferible tener el arreglo CABEZA apuntando a celdas de encabezamiento que no contienen vértices adyacentes. Por otra parte, si se espera que el grafo permanezca fijo, sin cambios (o con muy pocos) en las listas de adyacencia, sería preferible que CABEZA[i] fuera un cursor a un arreglo ADY, donde ADY[CABEZAA[i]], ADY[CABEZA[i]+1], ..., y así sucesivamente, contuvieran los vértices adyacentes al vértice i, hasta el punto en ADY donde se encuentra por primera vez un cero, el cual marca el fin de la lista de vértices adyacentes a i.

1 2 3 4

1 a B

2 a b

3 b A

4 b a

2

4

2

3

3 1 2 3 4

Page 85: Manua Estructura de Datos

Pag 85

Grafos no dirigidos Un grafo no dirigido G =(V,A) consta de un conjunto finito de vértices V y de un conjunto de aristas A. Se diferencia de un grafo dirigido en que cada arista en A es un par no ordenado de vértices. Si (v,w) es una arista no dirigida, entonces (v,w) =(w,v). Los grafos se emplean en distintas disciplinas para modelar relaciones simétricas entre objetos. Los objetos se representan por los vértices del grafo, y dos objetos están conectados por una arista si están relacionados entre sí. El siguiente programa pone de manifiesto el uso de operadores de miembro de estructura y de apuntador de estructura. #include <stdio.h> struct card char *face; char *suit; ; main() struct card a; struct card *aPtr; a.face = “Ace”; a.suit = “Spades”; aPtr = &a; printf(“%s%s%s\n%s%s%s\n%s%s%s\n”,a.face, “ of “, a.suit, aPtr->face, “of”, aPtr->suit, (*aPtr).face, “of”, (*aPtr).suit); return 0;

Ace of Spades Ace of Spades Ace of Spades