Está en la página 1de 85

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

Pag 1
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.

Pag 2
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”);
Pag 3
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ó Cómo se describe el argumento correspondiente
n
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ó Los caracteres de la entrada se convierten en
n
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 */
{

Pag 4
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.

Pag 5
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

Pag 6
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

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

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
Pag 7
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.

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.

El orden de las operaciones es:


x -(A + B % 2) + y * z

%
*

Pag 8
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. ||

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

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

* - -

> > <

||

&&

Pag 10
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

Pag 11
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

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

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.

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

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);

Pag 13
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>

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

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.

while (expresión lógica)


{
Sentencia 1
-
-
Sentencia 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.

Pag 14
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.

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

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;
}

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

for ( expresion1; expresion2; expresion3 )


{
Sentencia 1;
Sentencia 2;
-
-
-
Sentencia n
}

#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;
}

Sum es 2550

Pag 16
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.2 12.0 3.45 4.32 0.31 513. 2.65 13.0
1 4 6 4

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;
Pag 17
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

Pag 18
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 total del valor de los elementos del arreglo es 383

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;
}

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

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;
}

array = FFF0
&array = FFF0

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];

Pag 20
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 */

Pag 21
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);
Pag 22
for (i = 0; string1[i] != ‘\0’; I++)
printf(%c ”, string1[i]);
printf(“\n”);
return 0;
}

Mete un string: Hello there


string1 es: Hello
string2 es : string literal
string con espacios entre caracteres es :
He l l o

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};

Pag 23
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
}
1 January
2 February
3 March
4 Apr
5 May
6 June
7 July
8 Aug
9 Sep
10 Oct
11 Nov
12 Dec

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.

Pag 24
int *contadorAptr, contador;
contador Contador se refiere
7 directamente a la
variable cuyo valor es
7.
contadorAptr contador
7 ContadorAptr se
refiere en forma
indirecta a la
variable cuyo
valor es 7

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

yPtr

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

Pag 25
yPtr y
500000 600000 600000 5

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

Pag 26
*(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.

Suit[0] C o r a z o n e s \0

Suit[1]
D i a m a n t e s \0
Suit[2]
T r e b o l e s \0
Suit[3]
E s p a d a s \0
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;

Pag 27
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.

Pag 28
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.

Pag 29
Pasada 1: i=1

A[1] 23 19 19 19 19

A[2] 19 23 23 23 23

A[3] 45 45 45 31 31

A[4] 31 31 31 45 15 elemento
ordenado
A[5] 15 15 15 15 45
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

A[1] 19 19 19 19 19

A[2] 23 23 23 23 23

A[3] 31 31 31 15 15
elemento
A[4] 15 15 15 31 31 ordenado

A[5] 45 45 45 45 45
J=1 j=2 j=3 j=4

Pasada 3: i = 3
A[1] 19 19 19 19 19

A[2] 23 23 15 15 15

A[3] 15 15 23 23 23
elemento
A[4] 31 31 31 31 31 ordenado

A[5] 45 45 45 45 45

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

Pag 30
Pasada 4: i =4
A[1] 19 15 15 15 15

A[2] 15 19 19 19 19

A[3] 23 23 23 23 23
elemento
A[4] 31 31 31 31 31 ordenado

A[5] 45 45 45 45 45
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.
Pag 31
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 2 6 //////// 9 10
cartas ////////

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]

1 4 10 15
A[N]

Pag 32
1 4 6 10 15
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];
Pag 33
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.

Lista A[1] 5 5 5 -2
desordenada
A[2] 14 2 2 2

A[3] -2 -2 -2 5

A[4] 10 10 10 10

A[5] 2 14 14 14

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++)


{

Pag 34
/*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

Pag 35
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
Pag 36
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;
}

Pag 37
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

2 4 78 97 Lista A
j se ha incrementado
i junto con k
<

Lista C
Lista B
-15 0 13 15 78 90 94 96
k

-15 0 Lista C

Pag 38
2 4 78 97 Lista A
i
<

Lista B
-15 0 13 15 78 90 94 96
k

-15 Comparar A[i]


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

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;
}

Pag 39
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


A[1} 451871
A [2] 120467
A [3] 401321
A[4] 25761
- -
Elemento a buscar: t
- -
453714
- -
A[98] 339412
A[99] 81467
A[100] 924116

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

Pag 40
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 Elemento central
1989
Elemento buscado
2002
2400
2670
3200

Pag 41
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:

Elemento a buscar 1989


2002
2400 Elemento central
2670
3200

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

1989 Elemento considerado central


2002

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

Pag 42
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

Pag 43
(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.

Pag 44
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”);

Pag 45
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*/
Pag 46
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.
Pag 47
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.

Pag 48
1 primer elemento
2 segundo elemento

lista

últ último elemento

vacío

Long_máx

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);
Pag 49
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 ;
Pag 50
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;
}
Pag 51
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.
Pag 52
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
Pag 53
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

Pag 54
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

abcX+
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

Pag 55
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 // 19 13 15 26
21//

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 // 19 13 15 26
21//

I= 1 J=9
Pag 56
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 // 19 13 15 26
21//

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 // 19 13 23 26
21//

I J
5. El proceso se repite

9 15 13 17 // 19 31 23 26
21//

I J

9 15 13 17 // 19 31 23 26
21//

I J

9 15 13 17 // 21 31 23 26
19//

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.

Pag 57
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 // 21 23 26 31
19//

Pag 58
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)
}
Pag 59
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

Pag 60
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í:


Pag 61
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;

Pag 62
Final Frente

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;
}
}
Pag 63
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.

15 10

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

Pag 64
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.

ESPACIO
1 D 8
2 4
Prim
3 C 1
4 6
5 A 9
6 7
7 0
8 E 5
9 B 0
Disponible
10 10
11 2

Elemento sig

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.

Pag 65
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.

A1 A2 • • • An

encabezado Lista

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
Pag 66
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
}

A1 A2 • • • An

encabezado p x

aux

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
}

A1 x A2 • • • An

encabezado p aux

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
Pag 67
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.

A1 A2 • • • An

encabezado Lista

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
Pag 68
encabezado->sig = encabezado;
} {encabezado se apunte asi mismo}

encabezado

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;
}

Pag 69
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.

•••

Pag 70
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:

Encabezado

nil

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;
Pag 71
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;
}
}

Pag 72
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;

Encabezado

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){


Pag 73
//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;
Pag 74
}
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){


Pag 75
//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;
Pag 76
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;
Pag 77
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.
Pag 78
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.

raíz

Subárbol
izquierdo

Subárbol
derecho

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.

Pag 79
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);
}
Pag 80
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
Pag 81
}
}

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.

1 2

3 4

Grafo dirigido

Pag 82
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.

a
1 2
a
b b b b

a
3 4
a

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.

Pag 83
1 2 3 4
1 a B
2 a b
3 b A

4 b a

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.

1 2 3

2 4

3 2

4 3

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.

Pag 84
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

Pag 85

También podría gustarte