Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Manua Estructura de Datos
Manua Estructura de Datos
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
Pag 1
Pilas
Colas
6. Grafos.
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
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)
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;
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
Cadena_de_control y lista_de_argumentos
printf(“ABC”);
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 :
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.
return;
return (377);
return (a * b);
return (++x);
return ++x;
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.
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)
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.
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.
Palabras reservadas.
Las palabras reservadas en C tienen un significado especial y no se pueden utilizar para otros
propositos
Operadores aritméticos
Los operadores aritméticos (+,-,*) pueden ser utilizados con tipos enteros o reales, si ambos son
enteros el resultado es entero si uno es real el resultado es real.
2+3 =5
2 +3.0 = 5.0
2.0 + 3 = 5.0
2.0 + 3.0 = 5.0
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.
%
*
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.
Operadores de relación
Se utilizan para expresar condiciones y describen una relación entre dos valores.
Los operadores de relación se utilizan en condiciones cuyo formato tiene una de las siguientes
formas:
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.
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
* - -
||
&&
Pag 10
Operador &&
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;
Pag 13
printf(“D: %d\n”, dcontador);
printf(“F: %d\n”, fcontador);
return 0;
}
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.
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.
#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
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.
float x[8];
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
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
/* 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.
main()
{
int a [SIZE] = {1, 3, 5, 4, 7, 2, 99, 16, 45, 67, 89, 45}
i, total =0;
for (i=0; i <=SIZE – 1;i++)
total += a[i]
printf(“El total del valor de los elementos del arreglo es %d\n”,total);
return 0;
}
El siguiente ejemplo utiliza arreglos para resumir los resultados de datos recopilados en una
investigación. Deseamos resumir el número de respuestas de cada tipo (es decir del 1 hasta el
10). El arreglo respuestas es un arreglo de 40 elementos correspondiente a las respuestas de los
alumnos. Utilizamos un arreglo de 11 elementos, frecuencia para contar el número de ocurrencias
de cada respuesta. Ignoramos el primer elemento frecuencia[0], por que es más lógico tener un
incremento de respuesta 1 frecuencia[1] que frecuencia[0]. Esto nos permite utilizar cada
respuesta directa como subíndice en el arreglo frecuencia.
El primer ciclo for toma las respuestas del arreglo respuestas una por una e incrementa uno de
los diez contadores (frecuencia[1] hasta frecuencia [10] ) en el arreglo frecuencia. El enunciado
clave es
++frecuencia[respuestas[pregunta]
Este enunciado incrementa el contador frecuencia apropiado, dependiendo del valor de
respuestas[pregunta]. Por ejemplo cuando la variable del contador pregunta es 0,
respuestas[pregunta] es 1 y por lo tanto, ++frecuencia [respuesta[pregunta]]; se interpreta en
realidad como
++frecuencia[1];
#include <stdio.h>
#define RESPUESTAS_SIZE 40
#define FRECUENCIA_SIZE 11
main()
{
int answer, rating;
int respuestas[RESPUESTAS_SIZE] = {1, 2, 6, 4, 8, 5, 9, 7, 8, 10, 1, 6, 3, 8, 6, 10, 3, 8,
2, 7, 6, 5, 7, 6, 8, 6, 7, 5, 6, 6, 5, 6, 7, 5, 6, 4, 8, 6, 8, 10);
int frecuencia[FRECUENCIA_SIZE] = {0};
for (pregunta = 0; pregunta <= RESPUESTA_SIZE – 1; answer++)
++frecuencia[respuestas[pregunta]];
printf (“%s%17s\n”,”Rating”, “Frecuencia”);
Pag 19
Rating Frecuencia
1 2
2 2
3 2
4 2
5 5
6 11
7 5
8 7
9 1
10 3
int Temperaturas[24];
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:
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.
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
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’.
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
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
#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;
}
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
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:
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”};
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
Pag 25
yPtr y
500000 600000 600000 5
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;
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.
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
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
En C++, el enunciado
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.
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
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.
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
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
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)
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
Tres cartas 2 6 10
Cuatro 2 6 //////// 9 10
cartas ////////
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.
1 4 10 15
A[N]
Pag 32
1 4 6 10 15
Algoritmo
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
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;
}
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.
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.
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
504 88 150 62 612 171 513 277 654 427 760 510 908 675 898 704
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
Aux = X;
X = Y;
Y =Aux;
}
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.
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
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.
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:
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
int Encontrado=0,I=1,Posicion;
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:
El elemento central en esta sublista es 2400, y como1989 es menor, la nueva sublista donde
buscar es
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:
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.
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
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
};
Después de definir un tipo estructura, podemos declarar una variable de ese tipo, de la forma:
variable.miembro
Ejemplo:
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:
struct ficha
{
char nombre[40];
char direccion[40];
long telefono;
} var1, var2;
struct card {
char false[10];
char suit[10];
}a, deck[52];
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.
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 (.).
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{
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;
};
/*Entrada de datos*/
printf(“Finalizar la entrada con cont/Z\n\n”);
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
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.
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
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
struct LISTA
{
dato elem[TAM];
posicion ult;
};
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();
}
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:
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}
}
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.
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).
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.
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
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
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
9 13 15 17 19
9 13 15 17 19
Lista Ordenada
9 13 15 17 // 21 23 26 31
19//
Pag 58
Algoritmo de Ordenación Rápida:
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:
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)
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
Pos NumDiscos;
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);
}
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
#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.
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
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
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
typedef tipo_elemento:
struct nodo{
tipo_elemento elemento;
nodo *sig;
}
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
A1 x A2 • • • An
encabezado p aux
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.
encabezado
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
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.
Encabezado
nil
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.
•••
Encabezado
struct Cola{
Nodo Fondo,Frente;
}
Á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.
Pag 79
Representación ligada
typedef int tipo_elemento;
struct nodo{
Tipo_elemento Info;
Nodo *Izq, *Der
}
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
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
}
}
Grafos
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
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
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í.
#include <stdio.h>
struct card {
char *face;
char *suit;
};
main()
{
struct card a;
struct card *aPtr;
a.face = “Ace”;
a.suit = “Spades”;
aPtr = &a;
Ace of Spades
Ace of Spades
Ace of Spades
Pag 85