1. INSTITUTO TECNOLÓGICO
INSTITUTO TECNOLÓGICO 1
DE TUXTEPEC
DE TUXTEPEC
Ingeniería en informática
Portafolio de evidencias
Actiiviidad:
Act v dad:
Temas de la unidad
Grupo “A”
Turno: vespertino
Presentado por:
Margarita Antonio Gómez
Profesor(a):
Lic. Tomas Torres Ramírez
5 de diciembre 20011
2. 2
Índice
Introducción…………..………………………
………………….3
Introducción a las estructuras de
datos………………………………………4
Recursividad………………………………………………………
…………………………….7
Estructuras
lineales……………………………………………………………
…………13
Estructuras no
lineales……………………………………………………………
……43
Métodos de
ordenamiento………………………………………………………
…..63
3. Métodos de 3
búsqueda……………………………………………………………
…….68
Análisis de los algoritmos
Conclusión…………………………………………………………
……………………………
INTRODUCCION
En este trabajo hablaremos de todos los
temas del semestre, relacionados con
la carrera de informática, en la materia
estructura de datos.
4. También algunos ejemplos de 4
programas en java que complementa lo
aprendido en este semestre.
INTRODUCCIÓN A LAS ESTRUCTURAS
DE DATOS.
TIPOS DE DATOS ABSTRACTOS (TDA)
Un TDA es un tipo de dato definido por el programador que se puede
manipular de un modo similar a los tipos de datos definidos por el sistema.
5. Está formado por un conjunto válido de elementos y un número de
5
operaciones primitivas que se pueden realizar sobre ellos.
Ejemplo:
- Definición del tipo
Numero racional: Conjunto de pares de elementos (a,b) de tipo entero, con
b<>0.
MAPA CONCEPTUAL TAD
PROGRAMA RECURSIONES ARCHIVO DE TEXTO
/*
* Programa que utiliza metodos recursivos
* Fibonaci, Factorial, Potencia y Division por estas sucesivas
*/
package packRecursiones;
import javax.swing.*;
public class claseRecursivas {
public static int a, b, n;
6. claseRecursivas(){
6
a=0; b=0; n=0;
};
public static int potencia(int b, int n) {
if(n == 0) { //caso base
return 1;
} else {
return b * potencia(b, n -1); // llamada recursiva
}
}
public static int factorial(int n){
if(n==0) return 1; //Caso base
else return n*factorial(n-1); //Formula recursiva
}
public static int fibonaci( int n){
if(n==1 || n==2) return 1;
else return fibonaci(n-1)+fibonaci(n-2);
}
public static int division( int a, int b)
{
if(b > a) return 0;
else
return division(a-b, b) + 1;
}
public static void menu(){
String k="";
int e=5, r=0;
k+="Menú de operacionesnn1) Fibonacin2) Factorialn3) potencian";
k+="4) Division por restas sucesivasn5) Salir";
k+="nnElige una opción";
do{
e=Integer.parseInt(JOptionPane.showInputDialog(k));
switch(e){
case 1:
n=Integer.parseInt(JOptionPane.showInputDialog("Valor maximo para fibonaci"));
r=fibonaci(n);
JOptionPane.showMessageDialog(null,"Los numero fibonaci son "+r);
break;
case 2:
n=Integer.parseInt(JOptionPane.showInputDialog("Valor para obtener el
factorial"));
r=factorial(n);
JOptionPane.showMessageDialog(null,"El factorial es. "+r);
break;
case 3:
b=Integer.parseInt(JOptionPane.showInputDialog("Valor para la base"));
n=Integer.parseInt(JOptionPane.showInputDialog("Valor para potencia"));
7. r=potencia(b,n);
7
JOptionPane.showMessageDialog(null,"La potencia es: "+r);
break;
case 4:
a=Integer.parseInt(JOptionPane.showInputDialog("Valor para dividendo"));
b=Integer.parseInt(JOptionPane.showInputDialog("Valor para la divisor"));
r=division(b,n);
JOptionPane.showMessageDialog(null,"El resultado de la division es: "+r);
break;
case 5:
JOptionPane.showMessageDialog(null,"Fin del programa");
}
}while(e!=5);
};
public static void main(String[] args) {
// claseRecursivas obj1=new claseRecursivas();
claseRecursivas.menu();
}
}
RECURSIVIDAD
RESUMEN
La recursividad es una técnica de programación importante. Se utiliza para realizar
una llamada a una función desde la misma función.
La recursividad consiste en realizar una definición de un concepto en términos del
propio concepto que se está definiendo.
8. La recursividad forma parte del repertorio para resolver problemas en
8
Computación y es de los métodos más poderosos y usados.
Los algoritmos recursivos ofrecen soluciones estructuradas, modulares y
elegantemente simples.
La recursividad es un concepto fundamental en matemáticas y en computación.
Una definición recursiva dice cómo obtener conceptos nuevos empleando el
mismo concepto que intenta describir.
En toda situación en la cual la respuesta pueda ser expresada como una
secuencia de movimientos, pasos o transformaciones gobernadas por un conjunto
de reglas no ambiguas, la fórmula recursiva es un buen candidado para resolver el
problema.
Ejemplos:
Los números naturales se pueden definir de la siguiente forma: 0 es un
Número natural y el sucesor de un número natural es también un número
natural.
El factorial de un número natural n, es 1 si dicho número es 0, o n
multiplicado por el factorial del número n-1, en caso contrario.
La n-ésima potencia de un número x, es 1 si n es igual a 0, o el producto de
x por la potencia (n-1)-ésima de x, cuando n es mayor que 0.
Diseño de módulos recursivos.
Módulo M con una llamada a sí mismo: módulo recursivo directo.
Módulo M con una llamada a otro F, que hace una llamada a
M: Módulo recursivo indirecto.
Ejemplo: Implementación del factorial de un número.
public long factorial (long n)
{
if (n == 0) return 1;
else return n * factorial(n-1);
}
Búsqueda de soluciones recursivas: cuatro preguntas básicas.
¿Cómo se puede definir el problema en términos de uno o más problemas más
pequeños del mismo tipo que el original?
9. ¿Qué instancias del problema harán de caso base?
9
Conforme el problema se reduce de tamaño ¿se alcanzará el caso base?
¿Cómo se usa la solución del caso base para construir una solución correcta al
problema original?
Ejemplo:
La pila del ordenador y la recursividad.
La memoria de un ordenador a la hora de ejecutar un programa queda dividida en
dos partes:
·la zona donde se almacena el código del programa y
·la zona donde se guardan los datos: pila (utilizada para llamadas recursivas).
Programa principal llama a una rutina M, se crea en la pila de un registro de
activación o entorno, E almacena constantes, variables locales y parámetros
formales. Estos registros se van apilando conforme se llama sucesivamente desde
una función a otras.
Cuando finaliza la ejecución, se va liberando el espacio
Profundidad de la recursión: número de registros de activación en la pila en un
momento dado.
10. 10
Problema:
Si profundidad es muy grande => desbordamiento pila.
Representación gráfica del registro de activación:
Ejemplo de evolución de la pila: impresión de palabras en sentido contrario al
leído.
Función recursiva: imp_OrdenInverso.
Parámetros: número de palabras que quedan por leer (n).
Respuestas:
1. Se lee la primera palabra, y recursivamente se llama a la función para que lea e
imprima el resto de palabras, para que finalmente se imprima la primera leída.
2. El caso base: cuando sólo quede una palabra (n== 1), en cuyo caso se
imprimirá directamente.
3. Como en cada llamada recursiva hay que leer una palabra menos del total, en
n-1 llamadas se habrá alcanzado el caso base.
4. Una vez se haya alcanzado el caso base al devolver continuamente el control a
las funciones invocantes se irán imprimiendo de atrás a delante las palabras
obtenidas desde la entrada estándar.
public void imp_OrdenInverso(int n)
{
String palabra;
if (n == 1)
{
palabra =Console.in.readln();
System.out.println(palabra);
}
else
{
palabra =Console.in.readln();
imp_OrdenInverso(n-1);
Las definiciones recursivas de funciones en matemáticas que tienen como
argumentos números enteros, se llaman relaciones de recurrencia.
Forma de una ecuación de recurrencia: coar +c1ar-1 + c2ar-2 + ....+ ckar-k = f(r) función
matemática discreta donde ci son constantes, es llamada una ecuación de
recurrencia de coeficientes constantes de orden k, condicionada a que c 0 y ck = 0.
Una definición recursiva dice cómo obtener conceptos nuevos empleando el
mismo concepto que intenta definir.
11. El poder de la recursividad es que los procedimientos o conceptos complejos
11
pueden expresarse de una forma simple.
Un razonamiento recursivo tiene dos partes: la base y la regla recursiva de
construcción. La base no es recursiva y es el punto tanto de partida como de
terminación de la definición.
Ejemplo:
Base: La secuenciación, iteración condicional y selección son estructuras válidas
de control que pueden ser consideradas como enunciados.
Regla recursiva: Las estructuras de control que se pueden formar combinando de
manera válida la secuenciación iteración condicional y selección también son
válidos.
Un conjunto de objetos está definido recursivamente siempre que:
(B) algunos elementos del conjunto se especifican explícitamente
1. El procedimiento se llama a si mismo
2. El problema se resuelve, resolviendo el mismo problema pero de tamaño menor
3. La manera en la cual el tamaño del problema disminuye asegura que el caso
base eventualmente se alcanzará
La recursividad es un método poderoso usado en inteligencia artificial, su poder es
que algunos conceptos complejos pueden expresarse en una forma simple. Una
definición recursiva difiere de una definición circular en que tiene una forma de
escapar de su expansión infinita. Este escape se encuentra en la definición o
porción no recursiva o terminal de la definición.
Las fórmulas recursivas pueden aplicarse a situaciones tales como prueba de
teoremas, solución de problemas combinatorios, algunos acertijos, etc.
Cuando una llamada recursiva es la última posición ejecutada del procedimiento
se llama recursividad de cola, recursividad de extremo final o recursión e extremo
de cola.
Cuando un procedimiento incluye una llamada a si mismo se conoce como
recursión directa.
Cuando un procedimiento llama a otro procedimiento y este causa que el
procedimiento original sea invocado, se conoce como recursión indirecta.
Al principio algunas personas se sienten un poco incómodas con la recursividad,
tal vez porque da la impresión de ser un ciclo infinito, pero en realidad es menos
peligrosa una recursión infinita que un ciclo infinito, ya que una recursividad infinita
pronto se queda sin espacio y termina el programa, mientras que la iteración
infinita puede continuar mientras no se termine en forma manual.
12. Cuando un procedimiento recursivo se llama recursivamente a si mismo varias
12
veces, para cada llamada se crean copias independientes de las variables
declaradas en el procedimiento.
PROGRAMA CON MENÚ Y MÉTODOS RECURSIVOS
Programa que llama a los métodos recursivos
package packRecursiones;
import javax.swing.*;
public class claseRecursivas {
public static int a, b, n;
claseRecursivas(){
a=0; b=0; n=0;
};
public static int potencia(int b, int n) {
if(n == 0) { //caso base
return 1;
} else {
return b * potencia(b, n -1); // llamada recursiva
}
}
public static int factorial(int n){
if(n==0) return 1; //Caso base
else return n*factorial(n-1); //Formula recursiva
}
13. public static int fibonaci( int n){
13
if(n==1 || n==2) return 1;
else return fibonaci(n-1)+fibonaci(n-2);
}
public static int division( int a, int b)
{
if(b > a) {
return 0;
}
else {
return division(a-b, b) + 1;
}
}
public static void menu(){
String k="";
int e=5, r=0;
k+=" MENU DE OPERACIONESnn1) Fibonacin2) Factorialn3) potencian4)
Division por restas sucesivasn5) SalirnnElige una opción";
do{
e=Integer.parseInt(JOptionPane.showInputDialog(k));
switch(e){
case 1:
n=Integer.parseInt(JOptionPane.showInputDialog("Valor maximo para fibonaci"));
r=fibonaci(n);
JOptionPane.showMessageDialog(null,"Los numero fibonaci son: "+r);
break;
14. case 2:
14
n=Integer.parseInt(JOptionPane.showInputDialog("Valor para obtener el
factorial"));
r=factorial(n);
JOptionPane.showMessageDialog(null,"El factorial de "+n+"!="+r);
break;
case 3:
b=Integer.parseInt(JOptionPane.showInputDialog("Valor para la base"));
n=Integer.parseInt(JOptionPane.showInputDialog("Valor para potencia"));
r=potencia(b,n);
JOptionPane.showMessageDialog(null,"La potencia es: "+r);
break;
case 4:
b=Integer.parseInt(JOptionPane.showInputDialog("Valor para dividendo"));
n=Integer.parseInt(JOptionPane.showInputDialog("Valor para la divisor"));
r=division(b,n);
JOptionPane.showMessageDialog(null,"El resultado de la division es: "+r);
break;
case 5:
JOptionPane.showMessageDialog(null,"Fin del programa... :)");
}
}while(e!=5);
};
public static void main(String[] args) {
// claseRecursivas obj1=new claseRecursivas();
claseRecursivas.menu();
17. Las estructuras lineales son importantes porque aparecen con mucha frecuencia
17
en situaciones de la vida: Una cola de clientes de un banco, las instrucciones de
un programa, los caracteres de una cadena o las páginas de un libro
Características: existe un único elemento, llamado primero, existe un único
elemento, llamado último, cada elemento, excepto el primero, tiene un único
predecesor y cada elemento, excepto el último, tiene un único sucesor.
Operaciones: crear la estructura vacía, insertar un elemento, borrar y obtener un
elemento. Para definir claramente el comportamiento de la estructura es necesario
determinar en qué posición se inserta un elemento nuevo y qué elemento se borra
o se obtiene.
Principales estructuras lineales: pilas, colas y listas.
ESTRUCTURAS LINEALES
Listas (estructura de datos)
Una lista enlazada es una de las estructura de datos fundamentales, y puede ser
18. usada para implementar otras estructuras de datos. Consiste en una secuencia de
18
nodos, en los que se guardan campos de datos arbitrarios y una o dos referencias
(punteros) al nodo anterior y/o posterior. El principal beneficio de las listas
enlazadas respecto a los array convencionales es que el orden de los elementos
enlazados puede ser diferente al orden de almacenamiento en la memoria o el
disco, permitiendo que el orden de recorrido de la lista sea diferente al de
almacenamiento.
Una lista enlazada es un tipo de dato auto-referenciado porque contienen un
puntero o link a otro dato del mismo tipo. Las listas enlazadas permiten
inserciones y eliminación de nodos en cualquier punto de la lista en tiempo
constante (suponiendo que dicho punto está previamente identificado o
localizado), pero no permiten un acceso aleatorio. Existen diferentes tipos de listas
enlazadas: Lista Enlazadas Simples, Listas Doblemente Enlazadas, Listas
Enlazadas Circulares y Listas Enlazadas Doblemente Circulares.
Lenguajes imperativos u orientados a objetos tales como C o C++ y Java,
respectivamente, disponen de referencias para crear listas enlazadas.
Operaciones básicas con listas
El alterador de lista es un objeto que permite recorrer los elementos de la lista y
operar con ella
Dispone de un cursor que se sitúa entre dos elementos: previo ypróximo
Las operaciones básicas de las listas son:
• Constructor: Crea la lista vacía
19. • hazNula: Elimina todos los elementos de la lista, dejándolo vacía
19
• estaVacia: Si la lista está vacía retorna true. En otro caso, retorna false
• Tamaño: Retorna un entero que dice cuántos elementos hay en la lista
• iteradorDeLista: Retorna un iterador de lista asociado a la lista, para poder
recorrer sus elementos
Operaciones básicas de los iteradores de lista
Inicialmente el iterador de lista tiene el cursor situado antes del primer elemento
Las operaciones básicas que se pueden hacer con un iterador de lista son:
añade: Añade elElemento a la lista entre los elementos previo y próximo, si
existen; si no existen, lo añade como el único elemento de la lista. El cursor
queda situado justo después del nuevo elemento.
Borra: borra el último elemento obtenido de la lista con previo() o proximo().
Si no hay tal elemento, lanza estadoIncorrecto.
cambiaElemento: modifica el último elemento obtenido de la lista con
previo() o proximo() sustituyéndolo por el nuevoElemento. Si no hay tal
elemento, o si se ha llamado a borra () o añade() después de previo() o
proximo(), lanza estadoIncorrecto.
hayPrevio: Retorna true si existe un elemento previo al cursor y false en
caso contrario.
hayProximo: Retorna true si existe un elemento proximo al cursor y false en
caso contrario.
20. Previo: Retorna el elemento previo al cursor, y hace que éste retroceda un
20
elemento en la lista. Si no existe elemento previo, lanza noExiste.
Próximo: Retorna el elemento próximo al cursor, y hace que éste avance un
elemento en la lista. Si no existe elemento próximo, lanza noExiste.
Observar que si se llama a previo () después de llamar a proximo() se
obtiene el mismo elemento.
La interfaz List de Java
public interface List<E> extends Collection<E>
{
// Positional access
E get(int index);
E set(int index, E element); //optional
boolean add(E element); //optional
void add(int index, E element); //optional
E remove(int index); //optional
boolean addAll(int index,
Collection<? extends E> c); //optional
// Search
int indexOf(Object o);
int lastIndexOf(Object o);
// Iteration
ListIterator<E> listIterator();
ListIterator<E> listIterator(int index);
// Range-view
List<E> subList(int from, int to);
}
Tipos de listas
Listas simples enlazadas
21. 21
La lista enlazada básica es la lista enlazada simple la cual tiene un enlace por
nodo. Este enlace apunta al siguiente nodo en la lista, o al valor NULL o a la lista
vacía, si es el último nodo.
Una lista enlazada simple contiene dos valores: el valor actual del nodo y un
enlace al siguiente nodo
Lista Doblemente Enlazada
Un tipo de lista enlazada más sofisticado es la lista doblemente enlazada o lista
enlazadas de dos vías. Cada nodo tiene dos enlaces: uno apunta al nodo anterior,
o apunta al valor NULL o a la lista vacía si es el primer nodo; y otro que apunta al
siguiente nodo siguiente, o apunta al valor NULL o a la lista vacía si es el último
nodo.
Una lista doblemente enlazada contiene tres valores: el valor, el link al nodo
siguiente, y el link al anterior
En algún lenguaje de muy bajo nivel, XOR-Linking ofrece una vía para
implementar listas doblemente enlazadas, usando una sola palabra para ambos
enlaces, aunque el uso de esta técnica no se suele utilizar.
Listas enlazadas circulares
22. 22
En una lista enlazada circular, el primer y el último nodo están unidos juntos. Esto
se puede hacer tanto para listas enlazadas simples como para las doblemente
enlazadas. Para recorrer un lista enlazada circular podemos empezar por
cualquier nodo y seguir la lista en cualquier dirección hasta que se regrese hasta
el nodo original. Desde otro punto de vista, las listas enlazadas circulares pueden
ser vistas como listas sin comienzo ni fin. Este tipo de listas es el más usado para
dirigir buffers para “ingerir” datos, y para visitar todos los nodos de una lista a partir
de uno dado.
Una lista enlazada circular que contiene tres valores enteros
Listas enlazadas circulares simples
Cada nodo tiene un enlace, similar al de las listas enlazadas simples, excepto que
el siguiente nodo del último apunta al primero. Como en una lista enlazada simple,
los nuevos nodos pueden ser solo eficientemente insertados después de uno que
ya tengamos referenciado. Por esta razón, es usual quedarse con una referencia
solamente al último elemento en una lista enlazada circular simple, esto nos
permite rápidas inserciones al principio, y también permite accesos al primer nodo
desde el puntero del último nodo.
Lista Enlazada Doblemente Circular
En una lista enlazada doblemente circular, cada nodo tiene dos enlaces, similares
23. a los de la lista doblemente enlazada, excepto que el enlace anterior del primer
23
nodo apunta al último y el enlace siguiente del último nodo, apunta al primero.
Como en una lista doblemente enlazada, las inserciones y eliminaciones pueden
ser hechas desde cualquier punto con acceso a algún nodo cercano. Aunque
estructuralmente una lista circular doblemente enlazada no tiene ni principio ni fin,
un puntero de acceso externo puede establecer el nodo apuntado que está en la
cabeza o al nodo cola, y así mantener el orden tan bien como en una lista
doblemente enlazada.
Aplicaciones de las listas
Las listas enlazadas son usadas como módulos para otras muchas estructuras de
datos, tales como pilas, colas y sus variaciones.
El campo de datos de un nodo puede ser otra lista enlazada. Mediante este
mecanismo, podemos construir muchas estructuras de datos enlazadas con listas;
esta practica tiene su origen en el lenguaje de programación Lisp, donde las listas
enlazadas son una estructura de datos primaria (aunque no la única), y ahora es
una característica común en el estilo de programación funcional.
A veces, las listas enlazadas son usadas para implementar arrays asociativos, y
estas en el contexto de las llamadas listas asociativas. Hay pocas ventajas en este
uso de las listas enlazadas; hay mejores formas de implementar éstas estructuras,
por ejemplo con árboles binarios de búsqueda equilibrados. Sin embargo, a veces
una lista enlazada es dinámicamente creada fuera de un subconjunto propio de
nodos semejante a un árbol, y son usadas más eficientemente para recorrer ésta
serie de datos.
BIBLIOGRAFIA
Cómo programar en Java - Página 978
24. Harvey M. Deitel, Paul J. Deitel, Guillermo Trujano Mendoza – 2004
24
http://estdatosgrupo8a.blogspot.com/2009/03/estructuras-lineales.html
PROGRAMA DE LISTAS
25. Subir el programa de listas armado con los métodos que están en 25
la presentación.
Program
a::
import javax.swing.JOptionPane;
public class ListEnlaza{
static ListEnlaza accion=new ListEnlaza();
int n=Integer.parseInt(JOptionPane.showInputDialog("TAMAÑO"));
public static void main(String[] args) {
int opc=0;
while(true){
opc=Integer.parseInt(JOptionPane.showInputDialog(null,"__¿ACCION A
REALIZAR?__nn"+"1. INCERTAR AL INICIOn"+"2. INCERTAR AL FINALn"+"3. BORRAR
AL FINAL n"+"4.BORRAR AL INICIO n"+"5. TAMAÑO DE LA LISTA n"+"6. DATOS DE LA
LISTA FINALn"+"7. DATOS DE LA LISTA INICIO n"+"8. SALIRn"));
switch(opc){
case 1: accion.Insercion();
break;
case 2: accion.addLast(100);
break;
case 3: accion.deleteLast();
26. break;
26
case 4: accion.Eliminar();
break;
case 5: accion.tamano();
break;
case 6: accion.imprimir();
break;
case 7: accion.Recorrido();
break;
case 8: System.exit(0);
break;
default: JOptionPane.showMessageDialog(null,"OPCION INVALIDA","ERROR",
JOptionPane.ERROR_MESSAGE);;
break;}} }
public class Nodo {
Nodo nodoDer;
int dato;
public Nodo(int dato) {
this.dato = dato;
this.nodoDer = null;
}}
private Nodo primero;
private Nodo ultimo;
private int tamano;
27. public ListEnlaza() {
27
this.primero = null;
this.ultimo = null;
this.tamano = 0;
}
//Metodo utilizado para denotar que la lista se encuentra vacia.
public boolean siVacio() {
return (this.primero == null);
}
//Metodo para agregar al inicio
String LIS[]=new String [n];
int Final=-1;
int Frente=0;
int Max=LIS.length-1;
public void Insercion()throws ArrayIndexOutOfBoundsException{
if(Final==Max){
JOptionPane.showMessageDialog(null,"¡DESBORDAMIENTO, LLENA!","ERROR",
JOptionPane.ERROR_MESSAGE);;
}
else {
Frente=-1;
Final++;
28. LIS[Final]=JOptionPane.showInputDialog(null,"INSERTA UN DATO:");
28
JOptionPane.showMessageDialog(null,"SE INSERTO EL DATO (
"+LIS[Final]+" )");
}
}
public void Eliminar()throws ArrayIndexOutOfBoundsException{
if(Final==-1)
JOptionPane.showMessageDialog(null,"¡VACIA","ERROR!",
JOptionPane.ERROR_MESSAGE);
else{
JOptionPane.showMessageDialog(null, "¡DATO ("+LIS[ 0 ]+")
ELIMINADO!");
for (int i=0; i<Final-1; i++)
LIS[ i ] = LIS[ i+1 ];
Final--;}}
public void Recorrido()throws ArrayIndexOutOfBoundsException{
if(Final==-1){
JOptionPane.showMessageDialog(null,"COLA VACIA","ERROR",
JOptionPane.ERROR_MESSAGE);
}
else{
String Recorrido= "";
Recorrido+= "n ELEMENTOS EN COLA:";
for(int i=0; i<=Final; i++)
Recorrido+= "nLISTA["+i+"] = "+LIS[i];
29. JOptionPane.showMessageDialog(null, Recorrido);
29
} }
//Metodo para agregar al final de la lista.
public ListEnlaza addLast(int dato) {
dato=Integer.parseInt(JOptionPane.showInputDialog("Ingresa un dato"));
if(siVacio()) {
Nodo nuevo = new Nodo(dato);
primero = nuevo;
ultimo = nuevo;
nuevo.nodoDer = nuevo;
}
else {
Nodo nuevo = new Nodo(dato);
nuevo.nodoDer = null;
ultimo.nodoDer = nuevo;
ultimo = nuevo;
}
this.tamano++;
return this;
}
//Metodo para borrar al final de la lista.
public Nodo deleteLast() {
Nodo eliminar = null;
30. if(siVacio()) {
30
JOptionPane.showMessageDialog(null,"LA LISTA SE ENCUENTRA
VACIA","ERROR",
JOptionPane.ERROR_MESSAGE);
return null;
}
if(primero == ultimo) {
primero = null;
ultimo = null;
}
else {
Nodo actual = primero;
while(actual.nodoDer != ultimo) {
actual = actual.nodoDer;
}
eliminar = actual.nodoDer;
actual.nodoDer = null;
ultimo = actual;
}
JOptionPane.showMessageDialog(null,"SE ELIMINO EL DATO DE LA POSICION (
"+tamano+" )");
this.tamano--;
return eliminar;
}
31. //Metodo que imprime el tamaño de la lista.
31
public void tamano() {
JOptionPane.showMessageDialog(null, "El tamaño es:n " + this.tamano);
}
//Metodo que imprime la lista y los valores ingresados.
public Nodo imprimir() {
Nodo imprimir = null;
if(siVacio()) {
JOptionPane.showMessageDialog(null,"LA LISTA SE ENCUENTRA
VACIA","ERROR",
JOptionPane.ERROR_MESSAGE);
return null;
}
if(tamano != 0) {
Nodo temp = primero;
String str = "";
for(int i = 0; i < this.tamano; i++) {
str = str + temp.dato + "n";
temp = temp.nodoDer;
}
JOptionPane.showMessageDialog(null, "Lista:n" + str);
}
return imprimir;
}
32. }
32
Pantall
as:
Hacer un programa que utilice las
33. operaciones sobre las pilas o colas (push, pop y recorrido). 33
import javax.swing.*;
public class Colas {
static Cola accion=new Cola();
public static void main(String[] args) {
int opc=0;
while(true){
opc=Integer.parseInt(JOptionPane.showInputDialog(null,"__¿ACCION A
REALIZAR?__nn" +"1. INSERCIÓNn"+"2. RECORRIDOn"+"3. BUSCARn"+"4.
ELIMINAR n"+"5. SALIRn"));
switch(opc){
case 1: accion.Insercion();
break;
case 2: accion.Recorrido();
break;
case 3: accion.Buscar();
break;
case 4: accion.Eliminar();
break;
case 5: System.exit(0);
break;
default: JOptionPane.showMessageDialog(null,"OPCION
INVALIDA","ERROR",
JOptionPane.ERROR_MESSAGE);;
break;
34. }
34
}
}
}
class Cola{
int n=Integer.parseInt(JOptionPane.showInputDialog("INTRODUCE EL
TAMAÑO DE LA COLA"));
String cola[]=new String [n];
int Final=-1;
int Frente=0;
int Max=cola.length-1;
public void Insercion()throws ArrayIndexOutOfBoundsException{
if(Final==Max){
JOptionPane.showMessageDialog(null,"¡DESBORDAMIENTO, COLA
LLENA!","ERROR",
JOptionPane.ERROR_MESSAGE);;
}
else {
Frente=-1;
Final++;
cola[Final]=JOptionPane.showInputDialog(null,"INSERTA UN DATO:");
JOptionPane.showMessageDialog(null,"SE INSERTO EL
DATO ( "+cola[Final]+" )");
}
}
public void Recorrido()throws ArrayIndexOutOfBoundsException{
35. if(Final==-1){
35
JOptionPane.showMessageDialog(null,"COLA VACIA","ERROR",
JOptionPane.ERROR_MESSAGE);
}
else{
String Recorrido= "";
Recorrido+= "n ELEMENTOS EN COLA:";
for(int i=0; i<=Final; i++)
Recorrido+= "ncola["+i+"] = "+cola[i];
JOptionPane.showMessageDialog(null,
Recorrido);
}
}
public void Buscar()throws ArrayIndexOutOfBoundsException{
int ban=0;
String encontrado="";
String elemento=JOptionPane.showInputDialog(null,"¿QUE DATO
BUSCAS?");
for(int i=0;i<=Final;i++){
if(elemento.equals(cola[i])){
encontrado=encontrado+cola[i];
ban=ban+1;
}
}
if(ban==0){
36. JOptionPane.showMessageDialog(null,"DATO ("+elemento+")
36
NO ENCONTRADO ","ERROR",
JOptionPane.ERROR_MESSAGE);
}
else{
JOptionPane.showMessageDialog(null, ""+ban+" DATO
ENCONTRADO= ( "+elemento+" )");
}
}
public void Eliminar()throws ArrayIndexOutOfBoundsException{
if(Final==-1)
JOptionPane.showMessageDialog(null,"¡COLA VACIA","ERROR!",
JOptionPane.ERROR_MESSAGE);
else{
JOptionPane.showMessageDialog(null, "¡DATO ("+cola[ 0 ]+")
ELIMINADO!");
for (int i=0; i<Final-1; i++)
cola[ i ] = cola[ i+1 ];
Final--;
}
}
}
Programa de pila personalizado
37. 37
mport javax.swing.*;
public class Pila2{
class Nodo {
int info;
Nodo sig;
}
private Nodo raiz;
public Pila2 (){
raiz=null;
}
public void insertar(int x){
Nodo nuevo;
nuevo=new Nodo ();
nuevo.info=x;
if(raiz==null)
{
nuevo.sig=null;
raiz=nuevo;
}
else
{
nuevo. sig=raiz;
38. raiz=nuevo;
38
}
}
public int extraer ()
{
if(raiz!=null)
{
int informacion=raiz. info;
raiz = raiz. sig;
return informacion;
}
else
{
return Integer.MAX_VALUE;
}
}
public void imprimir (){
Nodo reco=raiz;
System.out.println("listado de todos los elementos de la pila.");
while(reco!=null){
System.out.print(reco.info+"-");
reco=reco.sig;
}
39. System.out.println();
39
}
public static void main (String[] ar) {
Pila2 pila1=new Pila2();
int op=4,dato;
do
{
String menu ="nn menu de opciones
nn1)Insertarn2)Eliminarn3)Listan4)SalirnnElije una opcion:";
op=Integer.parseInt(JOptionPane.showInputDialog(menu));
switch(op){
case 1:
dato=Integer.parseInt(JOptionPane.showInputDialog("Dato a insertar"));
pila1.insertar(dato);
break;
case 2:dato=pila1.extraer();
JOptionPane.showMessageDialog(null,"El dato
eliminado es:"+dato);
break;
case 3:pila1.imprimir();
break;
case 4:JOptionPane.showMessageDialog(null,"Fin del
programa");
}
}
40. while(op!=4)
40
}
}
Hacer un programa que manipule una cola. Debe tener los métodos para
insertar, eliminar y recorrer la cola en una lista enlazada.
Colas
Las colas son una subclase de las listas lineales que siguen el
orden FIFO (First Input First Output - Primero en entrar Primero en
salir). Este orden es que siguen las filas que hacemos en la vida
cotidiana al ir al banco, las tortillas, etc. En las colas la inserción
se hace al final y la eliminación se hace al frente, por lo que se
hace necesario el uso de una variable Primero y otra variable
Último por medio de las cuales se llevaran a cabo las operaciones.
Import javax.swing.*;
public class colas1 {
static cola accion=new cola();
public static void main(string[] args) {
int opc=0;
while(true){
opc=integer.parseint(joptionpane.showinputdialog(null,"__¿accion a
realizar?__nn" +"1. Insertarn"+"2. Recorrern"+"3. Eliminar n"+"4. Salirn"));
switch(opc){
41. case 1: accion.insercion();
41
break;
case 2: accion.recorrido();
break;
case 4: accion.eliminar();
break;
case 5: system.exit(0);
break;
default: joptionpane.showmessagedialog(null,"opcion
invalida","error",
joptionpane.error_message);;
break;
}
}
}
}
Class cola{
int n=integer.parseint(joptionpane.showinputdialog("introduce el tamaño de
la cola"));
string cola[]=new string [n];
int final=-1;
int frente=0;
int max=cola.length-1;
public void insercion()throws arrayindexoutofboundsexception{
42. if(final==max){
42
joptionpane.showmessagedialog(null,"¡desbordamiento, cola llena!","error",
joptionpane.error_message);;
}
else {
frente=-1;
final++;
cola[final]=joptionpane.showinputdialog(null,"inserta un dato:");
joptionpane.showmessagedialog(null,"se inserto el dato (
"+cola[final]+" )");
}
}
public void recorrido()throws arrayindexoutofboundsexception{
if(final==-1){
joptionpane.showmessagedialog(null,"cola vacia","error",
joptionpane.error_message);
}
else{
string recorrido= "";
recorrido+= "n elementos en cola:";
for(int i=0; i<=final; i++)
recorrido+= "ncola["+i+"] = "+cola[i];
joptionpane.showmessagedialog(null, recorrido);
}
}
44. ESTRUCTURAS NO LINEALES 44
Arboles
Concepto de arboles
Un árbol se define como una colección de nodos organizados en forma recursiva.
Cuando hay 0 nodos se dice que el árbol está vacío, en caso contrario el árbol
consiste en un nodo denominado raíz, el cual tiene 0 o más referencias a otros
árboles, conocidos como subárboles. Las raíces de los subárboles se denominan
hijos de la raíz, y consecuentemente la raíz se denomina padre de las raíces de
sus subárboles. Una visión gráfica de esta definición recursiva se muestra en la
siguiente figura:
Los nodos que no poseen hijos se denominan hojas. Dos nodos que tienen el
padre en común se denominan hermanos.
Un camino entre un nodo n1 y un nodo nk está definido como la secuencia de
nodos n1, n2,..., nk tal que ni es padre de ni+1, 1 <= i < k. El largo del camino es el
número de referencias que componen el camino, que para el ejemplo son k-1.
Existe un camino desde cada nodo del árbol a sí mismo y es de largo 0. Nótese
que en un árbol existe un único camino desde la raíz hasta cualquier otro nodo del
árbol. A partir del concepto de camino se definen los conceptos de ancestro y
descendiente: un nodo n es ancestro de un nodo m si existe un camino desde n a
m; un nodo n es descendiente de un nodo m si existe un camino desde m a n.
Se define la profundidad del nodo nk como el largo del camino entre la raíz del
arbol y el nodo nk. Esto implica que la profundidad de la raíz es siempre 0. La
altura de un nodo nk es el máximo largo de camino desde nk hasta alguna hoja.
Esto implica que la altura de toda hoja es 0. La altura de un árbol es igual a la
altura de la raíz, y tiene el mismo valor que la profundidad de la hoja más
profunda. La altura de un árbol vacío se define como -1.
La siguiente figura muestra un ejemplo de los conceptos previamente descritos:
45. 45
A es la raíz del árbol.
A es padre de B, C y D.
E y F son hermanos, puesto que ambos son hijos de B.
E, J, K, L, C, P, Q, H, N y O son las hojas del árbol.
El camino desde A a J es único, lo conforman los nodos A-B-F-J y es de
largo 3.
D es ancestro de P, y por lo tanto P es descendiente de D.
L no es descendiente de C, puesto que no existe un camino desde C a L.
La profundidad de C es 1, de F es 2 y de Q es 4.
La altura de C es 0, de F es 1 y de D es 3.
La altura del árbol es 4 (largo del camino entre la raíz A y la hoja más
profunda, P o Q).
Clasificación de árboles binarios:
1.-arbol binario distinto: Se dice que un árbol es distinto cuando su estructura
grafica es diferente.
2.-arbol binario similar.- Se dice que un árbol es similar cuando su estructura
grafica es idéntica pero la información que contiene entre sus nodos es diferente.
3.-arbol binario equivalente.-Son aquellos que su estructura grafica es idéntica
pero además la información entre sus nodos.
4.-arbol binario completo.-son aquellos que todos nus nodos excepto el último
nivel tienen sus dos hijos.
5.-arbol binario lleno: Es aquel que tiene su número máximo de posibles nodos.
Operaciones básicas sobre arboles binarios
Árboles binarios
46. Un árbol binario es un árbol en donde cada nodo posee 2 referencias a subárboles
46
(ni más, ni menos). En general, dichas referencias se denominan izquierda y
derecha, y consecuentemente se define el subárbol izquierdo y subárbol derecho
del arbol.
Como en toda estructura de datos hay dos operaciones básicas, inserción y
eliminación.
Inserción
El procedimiento de inserción en un árbol binario de búsqueda es muy sencillo,
únicamente hay que tener cuidado de no romper la estructura ni el orden del árbol.
Cuando se inserta un nuevo nodo en el árbol hay que tener en cuenta que cada
nodo no puede tener más de dos hijos, por esta razón si un nodo ya tiene 2 hijos,
el nuevo nodo nunca se podrá insertar como su hijo. Con esta restricción nos
aseguramos mantener la estructura del árbol, pero aún nos falta mantener el
orden.
Para localizar el lugar adecuado del árbol donde insertar el nuevo nodo se realizan
comparaciones entre los nodos del árbol y el elemento a insertar. El primer nodo
que se compara es la raíz, si el nuevo nodo es menor que la raíz, la búsqueda
prosigue por el nodo izquierdo de éste. Si el nuevo nodo fuese mayor, la búsqueda
seguiría por el hijo derecho de la raíz.
Este procedimiento es recursivo, y su condición de parada es llegar a un nodo que
no tenga hijo en la rama por la que la búsqueda debería seguir. En este caso el
nuevo nodo se inserta en ese hueco, como su nuevo hijo.
Vamos a verlo con un ejemplo sobre el siguiente árbol:
47. 47
Se quiere insertar el elemento 6.
Lo primero es comparar el nuevo elemento con la raíz. Como 6 > 4, entonces la
búsqueda prosigue por el lado derecho. Ahora el nuevo nodo se compara con el
elemento 8. En este caso 6 < 8, por lo que hay que continuar la búsqueda por la
rama izquierda. Como la rama izquierda de 8 no tiene ningún nodo, se cumple la
condición de parada de la recursividad y se inserta en ese lugar el nuevo nodo.
Borrar
El borrado en árboles binarios de búsqueda es otra operación bastante sencilla
excepto en un caso. Vamos a ir estudiando los distintos casos.
Tras realizar la búsqueda del nodo a eliminar observamos que el nodo no tiene
hijos. Este es el caso más sencillo, únicamente habrá que borrar el elemento y ya
habremos concuído la operación.
Si tras realizar la búsqueda nos encontramos con que tiene un sólo hijo. Este caso
también es sencillo, para borrar el nodo deseado, hacemos una especie de
puente, el padre del nodo a borrar pasa a apuntar al hijo del nodo borrado.
48. 48
Por último, el caso más complejo, si el nodo a borrar tiene dos hijos. En este caso
se debe sustitui el nodo a borrar por mayor de los nomdos menores del nodo
borrado, o por el menor de los nodos mayores de dicho nodo. Una vez realizada
esta sustitución se borrra el nodo que sustituyó al nodo eliminado (operación
sencilla ya que este nodo tendrá un hijo a lo sumo).
Sobre el siguiente árbol queremos eliminar el elemento 6. Tenemos dos opciones
para sustituirlo:
El menor de sus mayores: 7.
El mayor de sus menores: 4.
Vamos a sustituirlo por el 7 (por ejemplo). El árbol resultante sería el siguiente,
tras eliminar también el elemento 7 de su ubicación original.
49. 49
Otras operaciones
En los árboles de búsqueda la operación buscar es muy eficiente. El algoritmo
compara el elemento a buscar con la raíz, si es menor continua la búsqueda por la
rama izquierda, si es mayor continua por la izquierda. Este procedimiento se
realiza recursivamente hasta que se encuentra el nodo o hasta que se llega al final
del árbol.
Otra operación importante en el árbol es el recorridod el mismo. El recorrido se
puede realizar de tres formas diferentes:
Preorden: Primero el nodo raíz, luego el subárbol izquierdo y a continuación
el subárbol derecho.
Inorden: Primero el subárbol izquierdo, luego la raíz y a continuación el
subárbol derecho.
Postorden: Primero el subárbol izquierdo, luego el subárbol derecho y a
continuación la raíz.
Aplicaciones:
Aplicaciones de árboles binarios
Un árbol binario es una estructura de datos útil cuando se trata de hacer modelos
de procesos en donde se requiere tomar decisiones en uno de dos sentidos en
cada parte del proceso. Por ejemplo, supongamos que tenemos un arreglo en
donde queremos encontrar todos los duplicados. Esta situación es bastante útil en
el manejo de las bases de datos, para evitar un problema que se llama
redundancia.
50. Una manera de encontrar los elementos duplicados en un arreglo es recorrer todo
50
el arreglo y comparar con cada uno de los elementos del arreglo. Esto implica que
si el arreglo tiene elementos, se deben hacer comparaciones, claro, no es
mucho problema si es un número pequeño, pero el problema se va
complicando más a medida que aumenta.
Si usamos un árbol binario, el número de comparaciones se reduce bastante,
veamos cómo.
El primer número del arreglo se coloca en la raíz del árbol (como en este ejemplo
siempre vamos a trabajar con árboles binarios, simplemente diremos árbol, para
referirnos a un árbol binario) con sus subárboles izquierdo y derecho vacíos.
Luego, cada elemento del arreglo se compara son la información del nodo raíz y
se crean los nuevos hijos con el siguiente criterio:
Si el elemento del arreglo es igual que la información del nodo raíz,
entonces notificar duplicidad.
Si el elemento del arreglo es menor que la información del nodo raíz,
entonces se crea un hijo izquierdo.
Si el elemento del arreglo es mayor que la información del nodo raíz,
entonces se crea un hijo derecho.
Una vez que ya está creado el árbol, se pueden buscar los elementos repetidos.
Si x el elemento buscado, se debe recorrer el árbol del siguiente modo:
Sea k la información del nodo actual p. Si entonces cambiar el nodo actual
a right(p), en caso contrario, en caso de que informar una ocurrencia
duplicada y en caso de que cambiar el nodo actual a left(p).
El siguiente algoritmo
leer numero buscado >> n
tree=makeTree(n)
while(hay numeros en el arreglo){
leeSiguienteNumero >> k
p=q=tree;
while(k!=info(p)&&q!=NULL){
p=q
if(k<info(p))
q=left(p)
else
q=right(p)
}
if(k==info(p))
51. despliega<<" el numero es duplicado";
51
else
if (k<info(p))
setLeft(p,k)
else
setRight(p,k)
}
Figura 28: Árbol binario para encontrar números duplicados
Para saber el contenido de todos los nodos en un árbol es necesario recorrer el
árbol. Esto es debido a que solo tenemos conocimiento del contenido de la
dirección de un nodo a la vez. Al recorrer el árbol es necesario tener la dirección
de cada nodo, no necesariamente todos al mismo tiempo, de hecho normalmente
se tiene la dirección de uno o dos nodos a la vez; de manera que cuando se tiene
la dirección de un nodo, se dice que se visita ese nodo.
Aunque hay un orden preestablecido (la enumeración de los nodos) no siempre es
bueno recorrer el árbol en ese orden, porque el manejo de los apuntadores se
vuelve más complejo. En su lugar se han adoptado tres criterios principales para
recorrer un árbol binario, sin que de omita cualquier otro criterio diferente.
Los tres criterios principales para recorrer un árbol binario y visitar todos sus
nodos son, recorrer el árbol en:
preorden:
Se ejecutan las operaciones:
1. Visitar la raíz
2. recorrer el subárbol izquierdo en preorden
3. recorrer el subárbol derecho en preorden
entreorden:
Se ejecutan las operaciones:
1. recorrer el subárbol izquierdo en entreorden
2. Visitar la raíz
52. 3. recorrer el subárbol derecho en entreorden
52
postorden:
Se ejecutan las operaciones:
1. recorrer el subárbol izquierdo en postorden
2. recorrer el subárbol derecho en postorden
3. Visitar la raíz
Al considerar el árbol binario que se muestra en la figura 28 usando cada uno de
los tres criterios para recorrer el árbol se tienen las siguientes secuencias de
nodos:
En preorden:
En entreorden:
En postorden:
Esto nos lleva a pensar en otra aplicación, el ordenamiento de los elementos de
un arreglo.
Para ordenar los elementos de un arreglo en sentido ascendente, se debe
construir un árbol similar al árbol binario de búsqueda, pero sin omitir las
coincidencias.
El arreglo usado para crear el árbol binario de búsqueda fue
<14,15,4,9,7,18,3,5,16,4,20,17,9,14,5>
El árbol de ordenamiento es el que se muestra en la figura 29
53. Figura 29: Árbol binario para ordenar una secuencia de números 53
Arboles balanceados (avl)
Un árbol AVL (llamado así por las iniciales de sus inventores: Adelson-Velskii y
Landis) es un árbol binario de búsqueda en el que para cada nodo, las alturas de
sus subárboles izquierdo y derecho no difieren en más de 1.
No se trata de árboles perfectamente equilibrados, pero sí son lo suficientemente
equilibrados como para que su comportamiento sea lo bastante bueno como para
usarlos donde los ABB no garantizan tiempos de búsqueda óptimos.
El algoritmo para mantener un árbol AVL equilibrado se basa en reequilibrados
locales, de modo que no es necesario explorar todo el árbol después de cada
inserción o borrado.
Operaciones en AVL
Los AVL son también ABB, de modo que mantienen todas las operaciones que
poseen éstos. Las nuevas operaciones son las de equilibrar el árbol, pero eso se
hace como parte de las operaciones de insertado y borrado.
Factor de equilibrio
Cada nodo, además de la información que se pretende almacenar, debe tener los
dos punteros a los árboles derecho e izquierdo, igual que los ABB, y además un
miembro nuevo: el factor de equilibrio.
El factor de equilibrio es la diferencia entre las alturas del árbol derecho y el
izquierdo:
FE = altura subárbol derecho - altura subárbol izquierdo;
Por definición, para un árbol AVL, este valor debe ser -1, 0 ó 1.
Rotación simple a la derecha (SD)
Esta rotación se usará cuando el subárbol izquierdo de un nodo sea 2 unidades
más alto que el derecho, es decir, cuando su FE sea de -2. Y además, la raíz del
subárbol izquierdo tenga una FE de -1 ó 0, es decir, que esté cargado a la
izquierda o equilibrado.
54. Grafos:
54
Grafos
Desafortunadamente no existe una terminología estandarizada en la teoría de los
grafos, por lo tanto es oportuno aclarar que las presentes definiciones pueden
variar ligeramente entre diferentes publicaciones de estructura de datos y de teoría
de grafos, pero en general se puede decir que un grafo como indica su nombre lo
indica es la representación (para nuestro caso) gráfica de los datos de una
situación particular, ejemplo:
Los datos contienen, en algunos casos, relaciones entre ellos que no es
necesariamente jerárquica. Por ejemplo, supongamos que unas líneas aéreas
realizan vuelos entre las ciudades conectadas por líneas como se ve en la figura
anterior (más adelante se presentaran grafos con estructuras de datos); la
estructura de datos que refleja esta relación recibe el nombre de grafo.
Se suelen usar muchos nombres al referirnos a los elementos de una estructura
de datos. Algunos de ellos son "elemento", "ítem", "asociación de ítems",
"registro", "nodo" y "objeto". El nombre que se utiliza depende del tipo de
estructura, el contexto en que usamos esa estructura y quien la utiliza.
En la mayoría de los textos de estructura de datos se utiliza el termino "registro" al
hacer referencia a archivos y "nodo" cuando se usan listas enlazadas, árboles y
grafos.
También un grafo es una terna G = (V,A,j ), en donde V y A son conjuntos finitos, y
j es una aplicación que hace corresponder a cada elemento de A un par de
elementos de V. Los elementos de V y de A se llaman, respectivamente, "vértices"
y "aristas" de G, y j asocia entonces a cada arista con sus dos vértices.
Esta definición da lugar a una representación gráfica, en donde cada vértice es un
punto del plano, y cada arista es una línea que une a sus dos vértices.
Aristas
Son las líneas con las que se unen las aristas de un grafo y con la que se
construyen también caminos.
Si la arista carece de dirección se denota indistintamente {a, b} o {b, a}, siendo a y
b los vértices que une.
Si {a ,b} es una arista, a los vértices a y b se les llama sus extremos.
Aristas Adyacentes: Se dice que dos aristas son adyacentes si convergen
en el mismo vértice.
55. 55
Aristas Paralelas: Se dice que dos aristas son paralelas si vértice inicial y el
final son el mismo.
Aristas Cíclicas: Arista que parte de un vértice para entrar en el mismo.
Cruce: Son dos aristas que cruzan en un punto.
Vértices
Son los puntos o nodos con los que esta conformado un grafo. Llamaremos grado
de un vértice al número de aristas de las que es extremo. Se dice que un vértice
es `par' o `impar' según lo sea su grado.
Vértices Adyacentes: si tenemos un par de vértices de un grafo (U, V) y si
tenemos un arista que los une, entonces U y V son vértices adyacentes y
se dice que U es el vértice inicial y V el vértice adyacente.
Vértice Aislado: Es un vértice de grado cero.
Vértice Terminal: Es un vértice de grado 1.
Caminos
Sean x, y " V, se dice que hay un camino en G de x a y si existe una sucesión
finita no vacía de aristas {x,v1}, {v1,v2},..., {vn,y}. En este caso
x e y se llaman los extremos del camino
El número de aristas del camino se llama la longitud del camino.
Si los vértices no se repiten el camino se dice propio o simple.
Si hay un camino no simple entre 2 vértices, también habrá un camino
simple entre ellos.
Cuando los dos extremos de un camino son iguales, el camino se llama
circuito o camino cerrado.
56. Llamaremos ciclo a un circuito simple
56
Un vértice a se dice accesible desde el vértice b si existe un camino entre
ellos. Todo vértice es accesible respecto a si mismo
Terminología delos grafos
Un grafo está formado por un conjunto de nodos(o vértices) y un conjunto de
arcos. Cada arco en un grafo se especifica por un par de nodos.
El conjunto de nodos es {A, B, C, D, F, G, H} y el conjunto de arcos {(A, B), (A, D),
(A,C), (C, D), (C, F), (E, G), (A, A)} para el siguiente grafo
Operaciones básicas sobre grafos
Suponga que un grafo G se mantiene en memoria mediante la representación
enlazada
GRAFO (NODO, SIG, ADY, PRINCIPIO, NDISP, ENL, ADISP)
Discutida en la sección anterior. Esta sección discute las operaciones, de
búsqueda inserción y eliminación de nodos y aristas en el grafo G. La operación
de recorrido se trata en la siguiente sección.
Procedimiento
Busca(INFO, ENL, PRINCIPIO, ITEM, POS) [Algoritmo 5.2]
57. Busca la posición POS del primer nodo que contiene a ITEM, o hace
57
POS : = NULO.
1.-Hacer PTR : =PRINCIPIO.
2.-Repetir mientras PTR sea diferente de NULO:
Si ITEM = INFO[PTR], entonces; Hacer POS := PTR y
Volver.
Si no hacer PTR : = ENL [PTR].
[Fin del Bucle].
3.-Hacer POS : = NULO y volver.
Diferente que en el capítulo 5. Por supuesto, si se usan listas enlazadas circulares
a árboles binarios de búsqueda en vez de listas enlazadas, se deben de usar los
procedimientos análogos.
BÚSQUEDA EN UN GRAFO
Suponga que queremos encontrar la posición POS de un nodo N de un grafo C .
Esto se
puede llevar a cabo mediante el Procedimiento 8.3, tal como sigue:
Llamar BUSCA (NODO, SIG, PRINCIPIO, N, POS)
O sea, que esta sentencia de llamada busca en la lista NODO el nodo N.
Procedimiento 8.4 ELIMINAR (INFO, ENL, PRINCIPIO, DISP, ITEM, INDIC)
[Algoritmo 5.10]
Elimina el primer nodo de la lista que contenga a ITEM, o hace
INDIC:=FALSO si ITEM no aparece en la lista.
1.-[¿Lista vacia?] Si PRINCIPIO = NULO, entonces: Hacer
INDIC : = FALSO y volver .
2.-[¿ITEM en primer nodo?] Si INFO [PRINCIPIO] = ITEM,
Entonces: Hacer PTR : = PRINCIPIO,
PRINCIPIO: =ENL[PRINCIPIO],
ENL[PTR]: = DISP, DISP : = PTR,
INDIC : = VERDADERO y volver.
[Fin del condicional].
58. 3.-Hacer PTR : = ENL[PRINCIPIO] y SALVA : = PRINCIPIO.
58
[Inicializar punteros].
4.-Repetir pasos 5 y 6 mientras PTR sea diferente a NULO:
5.- Si INFO [PTR] = ITEM entonces:
Hacer ENL [SALVA]: = ENL[PTR],
ENL[PTR]: = DISP, DISP: = PTR,
INDIC: =VERDADERO y volver.
[Fin de la condicional].
6.- Hacer SALVA := PTR y PTR := ENL [PTR].
[Actualizar punteros].
[Fin Bucle del paso 4].
7.- Hacer INDIC : = FALSO y Volver.
INSERCIÓN EN UN GRAFO
Suponga que se va a insertar un nodo N en el grafo G. Observe que N será
asignado a
NODO [NDISP], el primer nodo disponible. Mas aún, como N será un nodo
aislado, se debe hacer ADY[NDISP]: = NULO. El procedimiento 8.6 hace esto
mediante una variable lógica INDIC que indica si hay desbordamiento.
Por supuesto, el Procedimiento 8.6 debe ser modificado si la lista NODO se
mantiene como una lista ordenada o como un árbol binario de búsqueda.
Procedimiento
INSNODO(NODO, SIG, ADY, PRINCIPIO, NDISP, N, INDIC)
Este procedimiento inserta el nodo N en le grafo G.
1.-[¿DESBORDAMIENTO?] Si NDISP= NULO, entonces: Hacer
INDIC := FALSO y volver.
2.- Hacer ADY[NDISP] : = NULO.
3.-[Quitar el nodo de la lista NDISP].
Hacer NUEVO: =NDISP y NDISP :=SIG[NDISP].
4.-[Insertar nodo N en la lista NODO].
Hacer NODO[NUEVO] : = N, SIG[NUEVO] :=PRINCIPIO y
59. PRINCIPIO : = NUEVO.
59
5.-Hacer INDIC := VERDADERO y Volver.
ELIMINACIÓN DE UN ARCO EN UN GRAFO
Suponga que se va a eliminar una arista (A, B) del grafo G. (Nuestro
procedimiento asumirá que A y B son nodos de G). De nuevo debemos encontrar
primero la posición
POSA de A y la posición POSB de B en la lista de nodos. Entonces simplemente
eliminamos POSB de la lista de sucesores de A, que tiene el puntero de la lista
ADY[POSA].Se usa una variable INDIC lógica para indicar si no existe tal arista en
el grafo G. El procedimiento es el siguiente:
Procedimiento 8.8 ELIMARISTA(NODO, SIG, ADY, PRINCIPIO, DEST, ENL,
ADISP, A, B, INDIC).
Este procedimiento elimina la arista (A, B) del grafo G .
1.-Llamar BUSCA(NODO, SIG, PRINCIPIO, A, POSA)[Localizar
Nodo A].
2.- Llamar BUSCA(NODO, SIG, PRINCIPIO, B, POSB) [Localizar
Nodo B].
3.-Llamar ELIMINAR (DEST, ENL, ADY[POSA], ADISP, POSB,
INDIC).[Usar el procedimiento 8.4].
4.-Volver.
60. BIBLIOGRAFIA
60
Técnicas de Diseño de Algoritmos en Java-Escrito por Sonia Jaramillo
Valbuena,Sonia Jaramillo Valbuena, Sergio Augusto Cardona Torres, Maria Lili
Villegas Ramirez Página 162
Estructuras de datos y algoritmos con Java-Escrito por Adam Drozdek Página
261
Introducción a las Estructuras de datos en Java- Escrito por Sonia Jaramillo
Valbuena,Sonia Jaramillo Valbuena, Sergio Augusto Cardona Torres, Dumar
Antonio Villa Zapata Página 209
61. Hacer un programa que manipule una lista o una pila o una cola 61
pero que se vea la animación de la operación realizada
package Clases;
import java.awt.*;
import javax.swing.JPanel;
public class PanelDibujo extends JPanel {
public int x=20;
public int y=100;
public pila Pila;
public boolean swborrar=false;
public PanelDibujo(pila p){
Pila=p;
}
public void paintComponent(Graphics g){
super.paintComponents(g);
Graphics2D g2d=(Graphics2D)g;
g2d.setColor(Color.WHITE);
g2d.fillRect(0,0,400,200);
64. MÉTODOS DE ORDENAMIENTO
64
Ordenamiento por burbuja
package BurbujaPaq;
public class Burbujamilenteros {
public static void main(String[] args) {
int x[]= new int [100];
int i,j, aux;
System.out.println("---------NUMEROS GENERADOS-------");
for (i=0; i<x.length; i++) {
x[i]=(int)(Math.random()*1000);
System.out.println(x[i] + " | ");
}
System.out.println("");
System.out.println("---------NUMEROS ORDENADOS-------");
for (i=0; i<x.length; i++) {
for (j=0; j<x.length-1; j++) {
if (x[j] > x[j+1]){
aux=x[j];
x[j]=x[j+1];
x[j+1]=aux;
}
}
}
for (i=0; i<x.length; i++) {
System.out.println(x[i] + " | ");
}
}
}
Hacer un programa en Java que muestre el siguiente menú:
1. Burbuja
2. Shell
3. Quicksort
4. Radix
65. Para cada opción se ordenaran primero 500 elementos y se le 65
toma el tiempo, luego 1000 y se le toma el tiempo y por último
2000 elementos y se le toma el tiempo. Al final imprimir los
arreglos originales y su tiempo de ordenamiento.
/ Crea un archivo de acceso aleatorio, escribiendo 100 registros vacíos en el disco.
import java.io.*;
import javax.swing.*;
public class CrearArchivoAleatorio {
private static final int NUMERO_REGISTROS = 100;
// permitir al usuario seleccionar el archivo a abrir
private void crearArchivo()
{
// mostrar cuadro de diálogo para que el usuario pueda seleccionar el archivo
JFileChooser selectorArchivo = new JFileChooser();
selectorArchivo.setFileSelectionMode( JFileChooser.FILES_ONLY );
int resultado = selectorArchivo.showSaveDialog( null );
// si el usuario hizo clic en el botón Cancelar del cuadro de diálogo, regresar
if ( resultado == JFileChooser.CANCEL_OPTION )
return;
66. // obtener el archivo seleccionado
66
File nombreArchivo = selectorArchivo.getSelectedFile();
// mostrar error si el nombre del archivo es inválido
if ( nombreArchivo == null || nombreArchivo.getName().equals( "" ) )
JOptionPane.showMessageDialog( null, "Nombre de archivo incorrecto",
"Nombre de archivo incorrecto", JOptionPane.ERROR_MESSAGE );
else {
// abrir el archivo
try {
RandomAccessFile archivo =
new RandomAccessFile( nombreArchivo, "rw" );
RegistroCuentasAccesoAleatorio registroEnBlanco =
new RegistroCuentasAccesoAleatorio();
// escribir 100 registros en blanco
for ( int cuenta = 0; cuenta < NUMERO_REGISTROS; cuenta++ )
registroEnBlanco.escribir( archivo );
archivo.close(); // cerrar el archivo
// mostrar mensaje indicando que el archivo se creó
67. JOptionPane.showMessageDialog( null, "Se creó el archivo " +
67
nombreArchivo, "Estado", JOptionPane.INFORMATION_MESSAGE );
System.exit( 0 ); // terminar el programa
} // fin del bloque try
// procesar excepciones durante operaciones de apertura, escritura o cierre
del archivo
catch ( IOException excepcionES ) {
JOptionPane.showMessageDialog( null, "Error al procesar el archivo",
"Error al procesar el archivo", JOptionPane.ERROR_MESSAGE );
System.exit( 1 );
}
} // fin de instrucción else
} // fin del método crearArchivo
public static void main( String args[] )
{
CrearArchivoAleatorio aplicacion = new CrearArchivoAleatorio();
aplicacion.crearArchivo();
}
69. 69
MÉTODOS DE BÚSQUEDA
Programa de los hombres búsqueda binaria
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import javax.swing.*;
public class PruebaFile extends JFrame
implements ActionListener {
private JTextField campoEntrada;
private JTextArea areaSalida;
public PruebaFile()
{
super( "Prueba de la clase File" );
70. 70
campoEntrada = new JTextField( "Escriba aquí el nombre del archivo o directorio" );
campoEntrada.addActionListener( this );
areaSalida = new JTextArea();
ScrollPane panelDesplazable = new ScrollPane();
panelDesplazable.add( areaSalida );
Container contenedor = getContentPane();
contenedor.add( campoEntrada, BorderLayout.NORTH );
contenedor.add( panelDesplazable, BorderLayout.CENTER );
setSize( 400, 400 );
setVisible( true );
}
public void actionPerformed( ActionEvent eventoAccion )
{
File nombre = new File( eventoAccion.getActionCommand() );
71. 71
if ( nombre.exists() ) {
areaSalida.setText( nombre.getName() + " existen" +
( nombre.isFile() ? "es un archivon" : "no es un archivon" ) +
( nombre.isDirectory() ? "es un directorion" :
"no es un directorion" ) +
( nombre.isAbsolute() ? "es una ruta absolutan" :
"no es una ruta absolutan" ) + "Última modificación: " +
nombre.lastModified() + "nLongitud: " + nombre.length() +
"nRuta: " + nombre.getPath() + "nRuta absoluta: " +
nombre.getAbsolutePath() + "nPadre: " + nombre.getParent() );
if ( nombre.isFile() ) {
try {
BufferedReader entrada = new BufferedReader(
new FileReader( nombre ) );
StringBuffer bufer = new StringBuffer();
String texto;
areaSalida.append( "nn" );
72. 72
while ( ( texto = entrada.readLine() ) != null )
bufer.append( texto + "n" );
areaSalida.append( bufer.toString() );
}
catch( IOException excepcionES ) {
JOptionPane.showMessageDialog( this, "ERROR EN ARCHIVO",
"ERROR EN ARCHIVO", JOptionPane.ERROR_MESSAGE );
}
}
else if ( nombre.isDirectory() ) {
String directorio[] = nombre.list();
areaSalida.append( "nnContenido del directorio:n");
for ( int i = 0; i < directorio.length; i++ )
areaSalida.append( directorio[ i ] + "n" );
74. Programa de las mujeres búsqueda secuencial
74
import java.util.*;
class programa8
{
public static void main(String[]args)
{
int a[],n,n1,indice;
Scanner sc=new Scanner(System.in);
System.out.print("Ingresa tamaño del vector: ");
n=sc.nextInt();
a=new int[n];
a=inicializa(n);
muestra(a);
System.out.print("Ingresa numero a buscar: ");
n1=sc.nextInt();
75. indice=busquedaLineal(a,n1);
75
if(indice==-1)
{
System.out.println("tu número no esta en la lista");
}
else
{
System.out.println("tu número esta en el indice: "+indice);
}
}
static int[] inicializa(int n)
{
int i,j,a[]=new int[n];
for(i=0;i<n;i++)
{
a[i]=randomxy(1,50);
}
return a;
}
static int busquedaLineal(int a[],int n)
76. {
76
int i;
for (i=0;i<a.length;i++)
{
if (a[i] == n)
{
return i+1;
}
}
return -1;
}
static void muestra(int a[])
{
int n=a.length;
for(int i=0;i<n;i++)
{
System.out.print(a[i]+" ");
}
System.out.print("nn");
}
77. static int randomxy(int x,int y)
77
{
int ran=(int) (Math.floor(Math.random()*(y-x+1))+x);
return ran;
}
}
Programa De Examen
import java.util.*;
class busqueda
{
public static void main(String[]args)
{
int a[],n,n1,indice;
Scanner sc=new Scanner(System.in);
System.out.print("Ingresa tamaño del arreglo: ");
n=sc.nextInt();
a=new int[n];
78. 78
a=inicializa(n);
muestra(a);
System.out.print("Ingresa numero a buscar: ");
n1=sc.nextInt();
indice=busqueda1(a,n1);
if(indice==-1)
{
System.out.println("tu número no esta en la
lista");
}
else
{
System.out.println("tu número esta en el
indice: "+indice);
}
}
static int[] inicializa(int n)
{
int i,j,a[]=new int[n];
for(i=0;i<n;i++)
{
a[i]=randomxy(1,50);
79. }
79
return a;
}
static int busqueda1(int a[],int n)
{
int i;
for (i=0;i<a.length;i++)
{
if (a[i] == n)
{
return i+1;
}
}
return -1;
}
static void muestra(int a[])
{
int n=a.length;
for(int i=0;i<n;i++)
{
System.out.print(a[i]+" ");
}
80. System.out.print("nn");
80
}
static int randomxy(int x,int y)
{
int ran=(int) (Math.floor(Math.random()*(y-x+1))+x);
return ran;
}
}
81. ANÁLISIS DE LOS ALGORITMOS 81
INTRODUCCIÓN
Los algoritmos se pueden clasificarse según la complejidades de tiempo y espacio, y a
este respecto pueden distinguirse varias clases de algoritmos, como lo ilustran la
siguiente figura. Su crecimiento también se muestra, por ejemplo, los algoritmo se llama
constante si su tiempo de ejecución sigue siendo el mismo para cualquier número de
elementos; se llama cuadrático si su tiempo de ejecución es O (n2).para cada una de
estas clases de algoritmos y sus tiempo de ejecución en una computadora que realiza
un millón de operaciones por segundo (1s=106µs=103ms).
Clase complejidad 10 102 103
n
constante O(1) 1 1µs 1 1µs 1 1µs
Logarítmica O(1gn) 3.32 3µs 6.64 7µs 9.97 10µs
Lineal O(n) 10 10µs 102 100µs 103 1ms
O(n1gn) O(n1gn) 33.2 33µs 664 664µs 9970 10ms
Cuadrática O(n2) 102 100µs 104 10ms 106 1s
82. 82
ANÁLISIS DE LOS ALGORITMOS.
Es una disciplina de las ciencias de la computación y, en la mayoría de los casos, su
estudio es completamente abstracto sin usar ningún tipo de lenguaje de
programación ni cualquier otra implementación. Así, el análisis de los algoritmos se
centra en los principios básicos del algoritmo, no en los de la implementación particular.
Una forma de plasmar (o algunas veces "codificar") un algoritmo es escribirlo
en pseudocódigo o utilizar un lenguaje muy simple tal como Léxico, cuyos códigos
pueden estar en el idioma del programador.
Por ejemplo:
Un algoritmo que verifica que hay más ceros que unos en una secuencia binaria infinita
debe ejecutarse siempre para que pueda devolver un valor útil. Si se implementa
correctamente, el valor devuelto por el algoritmo será válido, hasta que evalúe el
siguiente dígito binario. De esta forma, mientras evalúa la siguiente secuencia podrán
leerse dos tipos de señales: una señal positiva (en el caso de que el número de ceros
sea mayor que el de unos) y una negativa en caso contrario. Finalmente, la salida de
este algoritmo se define como la devolución de valores exclusivamente positivos si hay
más ceros que unos en la secuencia y, en cualquier otro caso, devolverá una mezcla de
señales positivas y negativas.
83. 83
COMPLEJIDAD EN EL TIEMPO Y ESPACIO
Complejidad en el tiempo:Cantidad de tiempo necesario para la ejecución
Órdenes de Complejidad
Se dice que O (f(n)) define un "orden de complejidad". Escogeremos como
representante de este orden a la función f(n) más sencilla del mismo. Así tendremos
O(1) orden constante
O(log n)
orden logarítmico
O(n) orden lineal
O(n log n)
O(n2) orden cuadrático
O(na) orden polinomiol (a > 2)
O(an) orden exponencial (a > 2)
O (n!) orden factorial
Es más, se puede identificar una jerarquía de órdenes de complejidad que coincide con
el orden de la tabla anterior; jerarquía en el sentido de que cada orden de complejidad
superior tiene a los inferiores como subconjuntos. Si un algoritmo A se puede demostrar
de un cierto orden O1, es cierto que también pertenece a todos los órdenes superiores
(la relación de orden cota superior de' es transitiva); pero en la práctica lo útil es
encontrar la "menor cota superior", es decir el menor orden de complejidad que lo
cubra. Los algoritmos de complejidad O(n) y O(n log n) son los que muestran un
comportamiento más "natural": prácticamente a doble de tiempo, doble de datos procesables.
Los algoritmos de complejidad logarítmica son un descubrimiento fenomenal, pues en el doble
de tiempo permiten atacar problemas notablemente mayores, y para resolver un problema el
doble de grande sólo hace falta un poco más de tiempo (ni mucho menos el doble).
84. Los algoritmos de tipo polinómico no son una maravilla, y se enfrentan con dificultad a
84
problemas de tamaño creciente. La práctica viene a decirnos que son el límite de lo
"tratable".
Sobre la tratabilidad de los algoritmos de complejidad polinómicahabria mucho que
hablar, y a veces semejante calificativo es puro eufemismo. Mientras complejidades del
orden O (n2) y O (n3) suelen ser efectivamente abordables, prácticamente nadie acepta
algoritmos de orden O(n100), por muy polinómicos que sean. La frontera es imprecisa.
Cualquier algoritmo por encima de una complejidad polinómico se dice "intratable" y
sólo será aplicable a problemas ridículamente pequeños.
Por ejemplo, si disponemos de dos algoritmos para el mismo problema, con tiempos de
ejecución respectivos:
algoritmo tiempo complejidad
f 100 n O(n)
2
g n O(n2)
Asintóticamente, "f" es mejor algoritmo que "g"; pero esto es cierto a partir de N >
100.Si nuestro problema no va a tratar jamás problemas de tamaño mayor que 100,
es mejor solución usar el algoritmo "g". El ejemplo anterior muestra que las
constantes que aparecen en las fórmulas para T(n), y que desaparecen al calcularlas
funciones de complejidad, pueden ser decisivas desde el punto de vista de ingeniería.
Pueden darse incluso ejemplo:
algoritmo tiempo complejidad
f 100 n O(n)
g n2 O(n2)
85. 85
Las siguientes propiedades se pueden utilizar como reglas para el cálculo de órdenes
de complejidad. Toda la maquinaria matemática para el cálculo de límites se puede
aplicar directamente:
C. Lim (n->inf) f (n)/g (n) = 0 => f IN O (g
) =>g NOT_IN O(f)=>O(f) es subconjunto de O(g)
D. Lim (n->inf) f (n)/g (n) = k => f IN O(g)
=>g IN O(f)
=> O(f) = O(g)
E. Lim(n->inf)f(n)/g(n)= INF => f NOT_IN O(g)
=>g IN O(f)
=>O(f) es superconjunto de O(g)
Las que siguen son reglas habituales en el cálculo de límites:
F. Si f, g IN O(h) =>f+g IN O(h)
G. Sea k una constante, f(n) IN O(g) => k*f(n) IN O(g)
H. Si f IN O(h1) y g IN O(h2) =>f+g IN O(h1+h2)
I. Si f IN O(h1) y g IN O(h2) => f*g IN O(h1*h2)
J. Sean los reales 0 < a < b =>O(na) es subconjunto de O(nb)
K. Sea P(n) un polinomio de grado k => P(n) IN O(nk)
L. Sean los reales a, b > 1 =>O(loga) = O(logb)
La regla [L] nos permite olvidar la base en la que se calculan los logaritmos en
expresiones de complejidad. La combinación de las reglas [K, G] es probablemente la
más usada, permitiendo de un plumazo olvidar todos los componentes de un polinomio,
menos su grado.
Por último, la regla [H] es la basica para analizar el concepto de secuencia en un
programa: la composición secuencial de dos trozos de programa es de orden de
complejidad el de la suma de sus partes.
86. Costes en tiempo y en espacio
86
La característica básica que debe tener un algoritmo es que sea correcto, es decir, que
produzca el resultado deseado en tiempo finito. Adicionalmente puede interesarnos que
sea claro, que este bien estructurado, que sea fácil de usar, que sea fácil de
implementar y que sea eficiente.
Entendemos por eficiencia de un algoritmo la cantidad de recursos de computo que
requiere; es decir, cual es su tiempo de ejecución que cantidad de memoria utiliza.
A la cantidad de tiempo que requiere la ejecución de un cierto algoritmo se le suele
llamar coste en tiempo mientras que a la cantidad de memoria que requiere se le suele
llamar coste en espacio.
Es evidente que conviene buscar algoritmos correctos que mantengan tan bajo como
sea posible el consumo de recursos que hacen del sistema, es decir, que sean lo mas
eficientes posible. Cabe hacer notar que el concepto de eficiencia de un algoritmo es un
concepto relativo, esto quiere decir que ante dos algoritmos correctos que resuelven el
mismo problema, uno es mas eficiente que otro si consume menos recursos. Por tanto,
podemos observar que el concepto de eficiencia y en consecuencia el concepto de
coste nos permitirá comparar distintos algoritmos entre ellos.
Si consideramos los algoritmos elementales de ordenación por selección y por Inserción
¿Cómo podríamos elegir cual de ellos utilizar en una aplicación dada?}Veremos más
adelante que para efectos prácticos ambos algoritmos son similares y que son
eficientes solo para ordenar secuencias con un número pequeño de elementos. Sin
embargo hay varias comparaciones que se pueden hacer entre ellos.
Tamaño del vector Núm. Comparaciones Núm. Intercambios
Selección n(n − 1)/2 0 (min.)
n− 1 (max.)
n− 1 (min.) 0 (min.)
Insertion prop.n2/4 (prom.) prop. N2/4 (prom.)
Prop. N2/2 (max.) prop. N2/2
De la tabla anterior podemos inferir que para ordenar secuencias cuyos elementos
Sean difíciles de comparar (las comparaciones son caras) es más conveniente Utilizar
el método de ordenación por inserción, y que este método es más eficiente mientras
87. más ordenada este la secuencia de entrada. Si por el contrario queremos ordenar
87
secuencias de elementos que son fáciles de comparar (o que tienen claves asociadas
fáciles de comparar) y en cambio los intercambios son carosseria más conveniente
utilizar el algoritmo de ordenación por selección.
En general, ¿cómo podemos elegir entre un algoritmo y otro si ambos resuelven el
mismo problema?
Pero este método tiene varios inconvenientes ya que los resultados dependen: del
subconjunto de pruebas escogidas del ordenador en el que se realicen las pruebas del
lenguaje de programación utilizado o del compilador, entre otros factores.
Ejemplo. Análisis de la funciona os min, que encuentra la posición del elemento mínimo
en un vector de enteros.
El coste en tiempo de un algoritmo depende del tipo de operaciones que realiza y del
coste especifico de estas operaciones. Este coste suele calcularse únicamente en
función del tamaño de las entradas para que sea independiente del tipo de maquina,
lenguaje de programación, compilador, etc. en que se implementa el algoritmo.
Definición 1.1 Dado un algoritmo A cuyo conjunto de entradas es A, su eficiencia o
coste (en tiempo, en espacio, en número de operaciones de entrada/ salida, etc.) es
una función tal que:
T: A! R+
∞ 7! T (∞)
COMPARACION DE COMPLEJIDAD Y TIEMPO EN ALGORITMOS.
Se analizara el tiempo de ejecución y el tiempo empleado por los algoritmos de
inserción directa, selección directa, el método de la burbuja y el método de la burbuja
mejorado, ordenando una arreglo de enteros en 3 casos: en orden ascendente (mejor
caso), un orden descendente (peor caso) y un orden al azar (caso promedio).
ALGORITMOS:
- INSERCION DIRECTA:
Para x=2 hasta n (+)
88. Y x-1
88
Sw 0
Mientras (y<>0) y (sw=0)
Si A[y-1]>A[y]
AuxA[y-1]
A[y-1]A[y]
A[y]Aux
De lo contrario
Sw1
Yy-1
SELECCIÓN DIRECTA:
Para x=1 hasta n-1 (+)
Menorx
Para y=x+1 hasta n (+)
Si A[y] <A [menor]
Menory
Si menor<>x
AuxA[x]
A[x] A[menor]
89. A[menor] Aux
89
- METODO DE LA BURBUJA:
Para i=1 hasta n-1
Para j=i+1 hasta n
Si A[i]>A[j]
AuxA[i]
A[i]A[j]
A[j]Aux
METODO DE LA BURBUJA MEJORADO:
Sw1
Mientras sw=1
Sw 0
Para i=1 hasta n-1 (+)
Si A[i] >A[i+1]
AuxA[i]
A[i]A[i+1]
A[i+1]Aux
90. COMPARACION DE COMPLEJIDADES:
90
ORDEN DE COMPLEJIDAD
INSERCION SELECCION BURBUJA BURBUJA
DIRECTA DIRECTA (MEJORADO)
MEJOR O(N) O(N2) O(N2) O(N)
CASO
CASO O(N2) O(N2) O(N2) O(N2)
PROMEDIO
PEOR CASO O(N2) O(N2) O(N2) O(N log N)
TIEMPO EN COMPARACIONES Y ASIGNACIONES:
Probar que son equivalentes las condiciones siguientes:
1. G es un grafo bipartito.
2. G es un grafo 2{coloreable.
3. G es un grafo que carece de ciclos de longitud impar.
1. COMPLEJIDAD ALGORÍTMICA
Un algoritmo será mas eficiente comparado con otro, siempre que consuma menos
recursos, como el tiempo y espacio de memoria necesarios para ejecutarlo.
La eficiencia de un algoritmo puede ser cuantificada con las siguientes medidas de
complejidad:
1. Complejidad Temporal o Tiempo de ejecución: Tiempo de cómputo necesario
para ejecutar algún programa.
2. Complejidad Espacial: Memoria que utiliza un programa para su ejecución, La
eficiencia en memoria de un algoritmo indica la cantidad de espacio requerido para
ejecutar el algoritmo; es decir, el espacio en memoria que ocupan todas las variables
91. propias al algoritmo. Para calcular la memoria estática de un algoritmo se suma la
91
memoria que ocupan las variables declaradas en el algoritmo. Para el caso de la
memoria dinámica, el cálculo no es tan simple ya que, este depende de cada ejecución
del algoritmo. Este análisis se basa en las Complejidades Temporales, con este fin,
para cada problema determinaremos una medida N, que llamaremos tamaño de la
entrada o número de datos a procesar por el programa, intentaremos hallar respuestas
en función de dicha N.El concepto exacto que cuantifica N dependerá de la naturaleza
del problema, si hablamos de un array se puede ver a N como el rango del array, para
una matriz, el número de elementos que la componen; para un grafo, podría ser el
número de nodos o arcos que lo arman, no se puede establecer una regla para N, pues
cada problema acarrea su propia lógica y complejidad.
Tiempo de Ejecución: El tiempo de Ejecución de un programa se mide en función de
N, lo que designaremos como (N).Esta función se puede calcular físicamente
ejecutando el programa acompañados de un reloj, o calcularse directamente sobre el
código, contando las instrucciones a ser ejecutadas y multiplicando por el tiempo
requerido por cada instrucción. Así, un trozo sencillo de código como:
S1;
for(x = 0; x < N; x++)
S2;
Demanda: T(N) = t1 + t2 * N
Donde t1 es el tiempo que lleva ejecutar la serie S1 de sentencias, y t2 es el que lleva
la serie S2.Habitualmente todos los algoritmos contienen alguna sentencia condicional
o selectiva, haciendo que las sentencias ejecutadas dependan de la condición lógica,
esto hace que aparezca más de un valor para T(N), es por ello que debemos hablar de
un rango de valores:
Tmin(N) ≤ T(N) ≤ Tmax(N)
92. Estos extremos son llamados "el peor caso" y "el mejor caso" y entre ambos se puede
92
hallar "el caso promedio" o el más frecuente, siendo este el más difícil de estudiar; nos
centraremos en el "el peor caso" por ser de fácil cálculo y se acerca a "el caso
promedio", brindándonos una medida pesimista pero fiable.Toda función T(N) encierra
referencias al parámetro N, y a una serie de constantes Ti dependientes de factores
externos al algoritmo. Se tratará de analizar los algoritmos dándoles autonomía frente a
estos factores externos, buscando estimaciones generales ampliamente válidas, a
pesar de ser demostraciones teóricas.
COMPLEJIDAD EN ESPACIO
Cantidad de memoria necesaria para la ejecución
La misma idea que se utiliza para medir la complejidad en tiempo de un algoritmo se
utiliza para medir su complejidad en espacio. Decir que un programa es O( N ) en
espacio significa que sus requerimientos de memoria aumentan proporcionalmente con
el tamaño del problema. Esto es, si el problema se duplica, se necesita el doble de
memoria. Del mismo modo, para un programa de complejidad O( N2 ) en espacio, la
cantidad de memoria que se necesita para almacenar los datos crece con el cuadrado
del tamaño del problema: si el problema se duplica, se requiere cuatro veces más
memoria. En general, el cálculo de la complejidad en espacio de un algoritmo es un
proceso sencillo que se realiza mediante el estudio de las estructuras de datos y su
relación con el tamaño del problema.
El problema de eficiencia de un programa se puede plantear como un compromiso
entre el tiempo y el espacio utilizados. En general, al aumentar el espacio utilizado para
almacenar la información, se puede conseguir un mejor desempeño, y, entre más
compactas sean las estructuras de datos, menos veloces resultan los algoritmos. Lo
mismo sucede con el tipo de estructura de datos que utilice un programa, puesto que
cada una de ellas lleva implícitas unas limitaciones de eficiencia para sus operaciones
básicas de administración. Por eso, la etapa de diseño es tan importante dentro del
proceso de construcción de software, ya que va a determinar en muchos aspectos la
calidad del producto obtenido.
93. SELECCIÓN DE UN ALGORITMO
93
La escogencia de un algoritmo para resolver un problema es un proceso en el que se
deben tener en cuenta muchos factores, entre los cuales se pueden nombrar los
siguientes:
La complejidad en tiempo del algoritmo. Es una primera medida de la calidad de una
rutina, y establece su comportamiento cuando el número de datos que debe procesar
es muy grande. Es importante tenerla en cuenta, pero no es el único factor que se debe
considerar.
La complejidad en espacio del algoritmo. Es una medida de la cantidad de espacio que
necesita la rutina para representar la información. Sólo cuando esta complejidad resulta
razonable es posible utilizar este algoritmo con seguridad. Si las necesidades de
memoria crecen desmesuradamente con respecto al tamaño del problema, el rango de
utilidad del algoritmo es bajo y se debe descartar.
La dificultad de implementar el algoritmo. En algunos casos el algoritmo óptimo puede
resultar tan difícil de implementar, que no se justifique desarrollarlo para la aplicación
que se le va a dar a la rutina. Si su uso es bajo o no es una operación crítica del
programa que se está escribiendo, puede resultar mejor adoptar un algoritmo sencillo y
fácil de implementar, aunque no sea el mejor de todos.
94. 94
EFICIENCIA DE LOS ALGORITMOS
La eficiencia de un algoritmo, se suelen estudiar los recursos (memoria y tiempo) que
consume el algoritmo. El análisis de algoritmos se ha desarrollado para obtener valores
que de alguna forma indiquen (o especifiquen) la evolución del gasto de tiempo y
memoria en función del tamaño de los valores de entrada.