PPT GESTIÓN ESCOLAR 2024 Comités y Compromisos.pptx
ESTRUCTURA DE DATOS
1. INSTITUTO TECNOLOGICO SUPERIOR DE FELIPE CARRILLO
PUERTO
UNIDAD ACADEMICA TULUM
INGENIERIA EN SISTEMAS COMPUTACIONALES
ESTRUCTURA DE DATOS
DOCENTE: YOLANDA
ALUMNO: RUBEN ISAI VAZQUEZ DE LEON
3er. SEMESTRE, GRUPO “A”
2. INTRODUCCION A LA ESTRUCTURA DE DATOS:
Tipos de datos abstractos:
Un tipo de dato abstracto (TDA) o Tipo abstracto de datos (TAD) es un modelo
matemático compuesto por una colección de operaciones definidas sobre un conjunto de
datos para el modelo, con mucha frecuencia se utilizan los términos TDA y Abstracción de
Datos de manera equivalente, y esto es debido a la similitud e interdependencia de ambos. Sin
embargo, es importante definir por separado los dos conceptos.
Como ya se mencionó, los Lenguajes de Programación Orientados a Objetos son lenguajes
formados por diferentes métodos o funciones y que son llamados en el orden en que el programa
lo requiere, o el usuario lo desea. La abstracción de datos consiste en ocultar las características de
un objeto y obviarlas, de manera que solamente utilizamos el nombre del objeto en nuestro
programa. Esto es similar a una situación de la vida cotidiana. Cuando yo digo la palabra “perro”,
usted no necesita que yo le diga lo que hace el perro. Usted ya sabe la forma que tiene un perro y
también sabe que los perros ladran. De manera que yo abstraigo todas las características de todos
los perros en un solo término, al cual llamo “perro”. A esto se le llama ‘Abstracción’ y es un
concepto muy útil en la programación, ya que un usuario no necesita mencionar todas las
características y funciones de un objeto cada vez que éste se utiliza, sino que son declaradas por
separado en el programa y simplemente se utiliza el término abstracto (“perro”) para mencionarlo.
En el ejemplo anterior, “perro” es un Tipo de Dato Abstracto y todo el proceso de definirlo,
implementarlo y mencionarlo es a lo que llamamos Abstracción de Datos.
Vamos a poner un ejemplo real de la programación. Supongamos que en algún Lenguaje de
Programación Orientado a Objetos un pequeño programa saca el área de un rectángulo de las
dimensiones que un usuario decida. Pensemos también que el usuario probablemente quiera
saber el área de varios rectángulos. Sería muy tedioso para el programador definir la multiplicación
de ‘base’ por ‘altura’ varias veces en el programa, además que limitaría al usuario a sacar un
número determinado de áreas. Por ello, el programador puede crear una función denominada
‘Área’, la cual va a ser llamada el número de veces que sean necesitadas por el usuario y así el
programador se evita mucho trabajo, el programa resulta más rápido, más eficiente y de menor
longitud. Para lograr esto, se crea el método Área de una manera separada de la interfaz gráfica
presentada al usuario y se estipula ahí la operación a realizar, devolviendo el valor de la
multiplicación. En el método principal solamente se llama a la función Área y el programa hace el
resto.
Al hecho de guardar todas las características y habilidades de un objeto por separado se le llama
Encapsulamiento y es también un concepto importante para entender la estructuración de datos.
Es frecuente que el Encapsulamiento sea usado como un sinónimo del Ocultación de información,
aunque algunos creen que no es así.
3. Veamos los diferentes tipos de abstracción que podemos encontrar en un programa:
1. Abstracción funcional: crear procedimientos y funciones e invocarlos mediante un nombre
donde se destaca qué hace la función y se ignora cómo lo hace. El usuario sólo necesita conocer
la especificación de la abstracción (el qué) y puede ignorar el resto de los detalles (el cómo).
2. Abstracción de datos:
Tipo de datos: proporcionado por los leguajes de alto nivel. La representación usada es
invisible al programador, al cual solo se le permite ver las operaciones predefinidas para cada
tipo.
Tipos definidos: por el programador que posibilitan la definición de valores de datos más
cercanos al problema que se pretende resolver.
TDA: para la definición y representación de tipos de datos (valores + operaciones), junto con
sus propiedades.
Objetos: Son TDA a los que se añade propiedades de reutilización y de compartición de
código.
MANEJO DE MEMORIA ESTÁTICA Y DINÁMICA:
Manejo de Memoria:
El problema con la memoria estática de memoria es que siempre se reserva antes de conocer los
datos concretos del problema y esto origina reservar siempre un máximo de memoria que en la
mayoría de las veces no se va a necesitar.
4. Memoria dinámica:
La reserva de memoria dinámica se hace en tiempo de ejecución después de leer los datos y de
conocer el tamaño exacto del problema. Como consecuencia se adapta mucho mejor a las
necesidades en cada caso.
El sitio donde se almacenan los objetos se denominan en ingles heap o free store traducido como
montículo o memoria libre, y el sitio preciso donde se encuentre depende del compilador y el tipo
de puntero utilizado. La creación y destrucción de los objetos está en manos del programador a
través de los operadores new y delate.
En C# las variables que se declaran son punteros y se pasan eficientemente con referencia,
tampoco es necesario considerar la liberación de la memoria puesto que framework se encarga de
liberar todas las referencias que no se estén ut ilizando y compactar la memoria para mejorar el
rendimiento.
Memoria Estática
La forma más fácil de almacenar el contenido de una variable en memoria en tiempo de ejecución
es en memoria estática o permanente a lo largo de toda la ejecución del programa.
Ventajas de utilizar memoria dinámica vs memoria estática
La memoria dinámica sirve para que los programadores se adapten siempre al tamaño del
problema que tienen que resolver sin desperdiciar recursos de memoria y esto se traduce en una
mayor eficiencia en la ejecución de los programas, las ventajas de utilizar memoria dinámica se
valoran mejor en comparación con la utilización de la reserva de la memoria estática, como se
muestra en el siguiente cuadro.
Ejemplo de uso de la memoria estática:
using System;
using System.Collections.Generic;
using System. Text;
namespace ConsoleApplication1
5. {
class Csimple
{
static void Main(string[] args)
{
int[] numeros = new int[] { 1, 2, 3, 4, 5 };
for (int i = 0; i <>
Console.WriteLine("Numero:{0}={1}", i + 1, numeros[i]);
Console.ReadLine();
}
}
}
Ejemplo de uso de memoria dinámica:
using System;
namespace Circunferencia1
{
class CircunferenciaApp
{
public static void Main()
{
const double PI=3.1415926; // Esto es una constante
double Radio=4; // Esto es una variable
Console.WriteLine("El perímetro de una circunferencia de radio {0} es {1}", Radio,
2*PI*Radio);
Console.WriteLine("El área de un círculo de radio {0} es {1}", Radio,
PI*Math.Pow(Radio,2));
6. }
}
}
La salida en la consola de este programa sería la siguiente:
El perímetro de una circunferencia de radio 4 es 25,1327408
El área de un círculo de radio 4 es 50,2654816
}
RECURSIVIDAD:
Concepto de recursividad:
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. Como ejemplo útil se puede presentar el cálculo de números
factoriales. Él factorial de 0 es, por definición, 1. Los factoriales de números mayores se calculan
mediante la multiplicación de 1 * 2 * …, incrementando el número de 1 en 1 hasta llegar al número
para el que se está calculando el factorial.
El siguiente párrafo muestra una función, expresada con palabras, que calcula una factorial.
“Si el número es menor que cero, se rechaza. Si no es un entero, se redondea al siguiente entero.
Si el número es cero, su factorial es uno. Si el número es mayor que cero, se multiplica por él
factorial del número menor inmediato.”
Para calcular el factorial de cualquier número mayor que cero hay que calcular como mínimo el
factorial de otro número. La función que se utiliza es la función en la que se encuentra en estos
momentos, esta función debe llamarse a sí misma para el número menor inmediato, para poder
ejecutarse en el número actual. Esto es un ejemplo de recursividad.
La recursividad es un concepto importante en informática. Muchos algoritmos se pueden describir
mejor en términos de recursividad.
Supongamos que P es un procedimiento que contiene una sentencia de Llamada a sí mismo o una
sentencia de Llamada a un segundo procedimiento que puede eventualmente llamar de vuelta al
procedimiento original P. Entonces P se dice que es u procedimiento recursivo. Como el programa
no ha de continuar ejecutándose indefinidamente, un procedimiento recursivo ha de tener las dos
siguientes propiedades:
(1) Debe existir un cierto criterio, llamado criterio base, por el que el procedimiento no se llama así
mismo.
(2) Cada vez que el procedimiento se llame a si mismo(directa o indirectamente), debe estar más
cerca del criterio base.
Un procedimiento recursivo con estas dos propiedades se dice que está bien definido.
Similarmente, una función se dice que está definido recursivamente si la definición de la función se
refiere a sí misma. De nuevo, para que la definición no sea circular, debe tener las dos siguientes
propiedades:
(1) Debe haber ciertos argumentos, llamados valores base, para los que la función no se refiera a
sí misma.
(2) Cada vez que la función se refiera a sí misma, el argumento de la función debe acercarse más
al valor base.
Una función recursiva con estas dos propiedades se dice también que está bien definida.
Tipos.
Podemos distinguir dos tipos de recursividad:
Directa: Cuando un subprograma se llama a sí mismo una o más veces directamente. Indirecta:
Cuando se definen una serie de subprogramas usándose unos a otros.
7. Características.
Un algoritmo recursivo consta de una parte recursiva, otra iterativa o no recursiva y un a condición
de terminación. La parte recursiva y la condición de terminación siempre existen. En cambio la
parte no recursiva puede coincidir con la condición de terminación. Algo muy importante a tener en
cuenta cuando usemos la recursividad es que es necesario asegurarnos que llega un momento en
que no hacemos más llamadas recursivas. Si no se cumple esta condición el programa no parará
nunca.
Ventajas e inconvenientes. La principal ventaja es la simplicidad de comprensión y su gran
potencia, favoreciendo la resolución de problemas de manera natural, sencilla y elegante; y
facilidad para comprobar y convencerse de que la solución del problema es correcta. El principal
inconveniente es la ineficiencia tanto en tiempo como en memoria, dado que para permitir su uso
es necesario transformar el programa recursivo en otro iterativo, que utiliza bucles y pilas para
almacenar las variables.
Ejemplos de casos recursivos:
Barbero dormilón:
En ciencias de la computación, el problema del barbero durmiente es un problema de
sincronización. El problema consiste en una barbería en la que trabaja un barbero que tiene un
único sillón de barbero y varias sillas para esperar. Cuando no hay clientes, el barbero se sienta en
una silla y se duerme. Cuando llega un nuevo cliente, éste o bien despierta al barbero o —si el
barbero está afeitando a otro cliente— se sienta en una silla (o se va si todas las sillas están
ocupadas por clientes esperando). El problema consiste en realizar la actividad del barbero sin que
ocurran condiciones de carrera. La solución implica el uso de semáforos y objetos de exclusión
mutua para proteger la sección crítica.
Un semáforo es una variable protegida (o tipo abstracto de datos) que constituye el método clásico
para restringir o permitir el acceso a recursos compartidos (por ejemplo, un recurso de
almacenamiento) en un entorno de multiprocesamiento. Fueron inventados por Edsger Dijkstra y se
usaron por primera vez en el sistema operativo THEOS.
En electrónica y en programación concurrente, se conoce como condición de carrera al error que
se produce en programas o circuitos lógicos que no se han construido adecuadamente para su
ejecución simultánea con otros procesos.
https://www.youtube.com/watch?v=6hvYBJpEQRE
La cena de los filósofos:
El problema de los filósofos cenando es un problema clásico de las ciencias de la
computación propuesto por Edsger Dijkstra en 1965para representar el problema de la
sincronización de procesos en un sistema operativo. Cabe aclarar que la interpretación está
basada en pensadores chinos, quienes comían con dos palillos, donde es más lógico que se
necesite el del comensal que se siente al lado para poder comer.
8. Cinco filósofos se sientan alrededor de una mesa y pasan su vida cenando y pensando. Cada
filósofo tiene un plato de fideos y un tenedor a la izquierda de su plato. Para comer los fideos son
necesarios dos tenedores y cada filósofo sólo puede tomar los que están a su izquierda y derecha.
Si cualquier filósofo toma un tenedor y el otro está ocupado, se quedará esperando, con el tenedor
en la mano, hasta que pueda tomar el otro tenedor, para luego empezar a comer.
Si dos filósofos adyacentes intentan tomar el mismo tenedor a una vez, se produce una condición
de carrera: ambos compiten por tomar el mismo tenedor, y uno de ellos se queda sin comer.
Si todos los filósofos toman el tenedor que está a su derecha al mismo tiempo, entonces todos se
quedarán esperando eternamente, porque alguien debe liberar el tenedor que les falta. Nadie lo
hará porque todos se encuentran en la misma situación (esperando que alguno deje sus
tenedores). Entonces los filósofos se morirán de hambre. Este bloqueo mutuo se denomina
interbloqueo o deadlock .
El problema consiste en encontrar un algoritmo que permita que los filósofos nunca se mueran de
hambre.
https://www.youtube.com/watch?v=i1RfAyyFQ0w
ESTRUCTURAS LINEALES:
Las estructuras lineales de datos se caracterizan porque sus elementos están en secuencia,
relacionados en forma lineal, uno luego del otro.
Cada elemento de la estructura puede estar conformado por uno o varios subelementos o campos
que pueden pertenecer a cualquier tipo de dato, pero que normalmente son tipos básicos.
Listas:
es una de las estructuras de datos fundamentales, y puede ser usada para implementar otras
estructuras de datos. Consiste en una secuencia de nodos, en los que se guardan campos de
datos arbitrarios y una o dos referencias, enlaces o punteros al nodo anterior o posterior. El
principal beneficio de las listas enlazadas respecto a los vectores 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 autorreferenciado porque contienen un puntero o enlace (en
inglés link, del mismo significado) 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
9. que dicho punto está previamente identificado o localizado), pero no permiten un acceso aleatorio.
Existen diferentes tipos de listas enlazadas: listas enlazadas simples, listas doblemente enlazadas,
listas enlazadas circulares y listas enlazadas doblemente circulares.
Las listas enlazadas pueden ser implementadas en muchos lenguajes. Lenguajes tales
como Lisp y Scheme tiene estructuras de datos ya construidas, junto con operaciones para
acceder a las listas enlazadas. Lenguajes imperativos u orientados a objetos tales
como C o C++ y Java, respectivamente, disponen de referencias para crear listas enlazadas.
Colas:
Una cola constituye una estructura lineal de datos en la que los nuevos elementos se introducen
por un extremo y los ya existentes se eliminan por el otro. Es importante señalar que los
componentes de la cola se eliminan en el mismo orden en el cual se insertaron. Es decir, el primer
elemento que se introduce en la estructura será el que se eliminara en primer orden. Debido a esta
característica, las colas también reciben el nombre de estructuras FIFO (First -In, First-Out: el
primero en entrar es el primero en salir).
Implementación
Las colas, al igual que las pilas, no existen como estructuras de datos estándar en lenguajes de
programación. Este tipo de estructura de datos se puede representar mediante el uso de:
-Arreglos
-Listas
Pila:
Una pila (stack en inglés) es una lista ordenada o estructura de datos en la que el modo de acceso
a sus elementos es de tipo LIFO (del inglés Last In First Out, último en entrar, primero en salir)
que permite almacenar y recuperar datos. Esta estructura se aplica en multitud de ocasiones en el
área de informática debido a su simplicidad y ordenación implícita de la propia estructura.
Para el manejo de los datos se cuenta con dos operaciones básicas: apilar (push), que coloca un
objeto en la pila, y su operación inversa, retirar (o desapilar, pop), que retira el último elemento
apilado.
En cada momento sólo se tiene acceso a la parte superior de la pila, es decir, al último objeto
apilado (denominado TOS, Top of Stack en inglés). La operación retirar permite la obtención de
este elemento, que es retirado de la pila permitiendo el acceso al siguiente (apilado con
anterioridad), que pasa a ser el nuevo TOS.
10. Por analogía con objetos cotidianos, una operación apilar equivaldría a colocar un plato sobre una
pila de platos, y una operación retirara retirarlo.
Las pilas suelen emplearse en los siguientes contextos:
Evaluación de expresiones en notación postfija (notación polaca inversa).
Reconocedores sintácticos de lenguajes independientes del contexto
Implementación de recursividad.
ESTRUCTURAS NO LINEALES:
Arboles:
En ciencias de la informática, un árbol es una estructura de datos ampliamente usada que imita la
forma de un árbol (un conjunto de nodos conectados). Un nodo es la unidad sobre la que se
construye el árbol y puede tener cero o más nodos hijos conectados a él. Se dice que un nodo
es padre de un nodo si existe un enlace desde hasta (en ese caso, también decimos
que es hijo de ). Sólo puede haber un único nodo sin padres, que llamaremos raíz. Un nodo
que no tiene hijos se conoce como hoja. Los demás nodos (tienen padre y uno o varios hijos) se
les conoce como rama.
Formalmente, podemos definir un árbol de la siguiente forma:
Caso base: un árbol con sólo un nodo (es a la vez raíz del árbol y hoja). Un nuevo árbol a partir de
un nodo y árboles de raíces
con elementos cada uno, puede construirse estableciendo una relación
padre-hijo entre y cada una de las raíces de los árboles. El árbol resultante
de nodos tiene como raíz el nodo , los
nodos son los hijos de y el conjunto de nodos hoja está formado por la
unión de los conjuntos hojas iniciales. A cada uno de los árboles se les denota
ahora subárboles de la raíz.
11. METODOS DE RECORRIDO DE ARBOLES:
Una sucesión de nodos del árbol, de forma que entre cada dos nodos consecutivos de la sucesión
haya una relación de parentesco, decimos que es un árbol recorrido. Existen dos recorridos típicos
para listar los nodos de un árbol: en profundidad y en anchura. En el primer caso, se listan los
nodos expandiendo el hijo actual de cada nodo hasta llegar a una hoja, donde se vuelve al nodo
anterior probando por el siguiente hijo y así sucesivamente. En el segundo, por su parte, antes de
listar los nodos de nivel (a distancia aristas de la raíz), se deben haber listado
todos los de nivel . Otros recorridos típicos del árbol son preorden, postorden e inorden:
El recorrido en preorden, también llamado orden previo consiste en recorrer en primer lugar
la raíz y luego cada uno de los hijos en orden previo.
El recorrido en inorden, también llamado orden simétrico (aunque este nombre sólo cobra
significado en los árboles binarios) consiste en recorrer en primer lugar , luego la raíz y
luego cada uno de los hijos en orden simétrico.
El recorrido en postorden, también llamado orden posterior consiste en recorrer en primer
lugar cada uno de los hijos en orden posterior y por último la raíz.
12. https://www.youtube.com/watch?v=wBjBKc66q_0
METODOS DE ORDENAMIENTO Y BUSQUEDA:
ALGORITMO DE BURBUJA:
También conocido como ordenamiento de burbuja, es un algoritmo que se aplica para poder
ordenar una cantidad de datos ya sea de forma ascendente o descendente.
Es el algoritmo más fácil de implementar, pero a cambio pagamos un alto precio en procesamiento,
ya que este método evalúa una cantidad los datos muchas veces y en ocasiones innecesariamente
(como por ejemplo cuando son iguales).
A estas alturas posiblemente ya tengas conocimiento de sencillos pasos para ordenar datos, como
por ejemplo, Determinar cuál es el mayor o menor de dos números, pues aplicando este método
podremos ordenar array, estructuras y cualquier tipo de dato NO atómico (es decir que se pueda
dividir)
Este método necesita de lo siguiente para implementarse:
Un array o estructura que ordenar (>1 elemento).
13. Dos variables contadoras de ciclos (i,j por ejemplo).
Una variable temporal (para almacenar un dato momentáneamente).
Dos ciclos y un Condicional.
EJEMPLO:
....
//DE MENOR A MAYOR (Ascendente)
#define Nelementos 4
....
int i,j; //Variables contadoras del ciclo.
int lista[Nelementos]={6,9,3,1}; //Declaracion e inicialización de un arreglo de 4 elementos.
int temp=0; //Variable temporal.
for (i=1;i<Nelementos;i++)
{
for (j=0; j <= Nelementos-1 ;j++)
{
if (lista[j] > lista[j+1])//Condicion mayor-menor
{
temp=lista[j];
lista[j]=lista[j+1];
lista[j+1]=temp;
}
}
}
//Para cambiar el modo de ordenamiento solo debemos cambiar la condicion < ó >
'''<big>Explicando un poco lo que dice el código tenemos:</big>'''
# Iniciamos i a 1, de esta forma correremos el ciclo solamente 3 veces. Así evitamos correr
ciclos innecesariamente.
# El segundo for, se ejecutara 3 veces por cada primer ciclo.
# La condicion nos dice:
* Si, el valor de lista 0 es mayor al valor de lista 1, es decir
* '''Si, 6 > 9''', pero como la condicion no se cumple, pasamos del ciclo y '''J=1'''.
* Si, el valor de lista 1 es mayor al valor de lista 2, es decir
* '''Si, 9 > 3''', como es '''verdadera''' hacemos:
# Guardamos momentáneamente en la variable temporal el valor de lista 1, es decir 9.
# En la posición de lista 1, guardamos el valor de lista 2, es decir 3.
14. # En la posición de lista 2, guardamos el valor de temp, es decir 9
'''Volvemos''' nuevamente '''al ciclo''', ahora '''J=2'''. ..
* Si, el valor de lista 2 es mayor al valor de lista 3, es decir
* Si, '''9 > 1''', (recuerda que anteriormente '''movimos''' al 9 a la posición de 3), es
verdadera =>
# Guardamos momentáneamente en la variable temporal el valor de lista 2, es decir 9.
# En la posición de lista 2, guardamos el valor de lista 3, es decir 1.
# En la posición de lista 3, guardamos el valor de temp, es decir 9.
De esta forma el ciclo se repite hasta que todos los datos se hallan movido. Como veras
hasta ahora solo hemos estado moviendo el 9. Tu lista se vería así:
* ''Original:'' 6 '''9''' 3 1
:* ''1er Mov:'' 6 3 '''9''' 1
:* ''2do Mov:'' 6 3 1 '''9'''
Si bien ya está más ordenada que la original, aún falta bastante, pero recuerda que
estábamos en el primer ciclo del primer for (i).
'''Aca te dejo como seran los demas movimientos:'''
* ''Segundo Ciclo i:''
:* 3 6 1 9
:* 3 1 6 9
* ''Tercer Ciclo i:''
:* 1 3 6 9
:* YA!
En un principio, pensaba no presentarles las variaciones que hay de este algoritmo, pero
revisando en varios libros me percate de que aunque es el mismo algoritmo y funciona de
la misma forma (con sus ventajas y desventajas), Creo que es preferible mostrárselos para
evitar confusiones y enredos innecesarios.
== Variantes ==
Buscando en varios libros e Internet me he encontrado numerosas variantes, y por motivos
pedagógicos voy a mostrarlos, ya que considero que aunque sean el mismo algoritmo, su
diferente implementación puede llevar a confusiones.
En lo personal considero que el algoritmo planteado al principio es el que mejor se expresa
(entiende), y es el que recomiendo implementar, pero hay libros que lo implementan de
otra forma, por lo que los que se guían con él, pueden tener confusiones a la hora de
apoyarse con este libro.
==== Variante: 1 Ciclo. ====
15.
Aunque en realidad usa dos ciclos un while y un for, el while hace la función de nuestro
primer for. Sin embargo la demás implementaciones son técnicamente las mismas, solo
que en vez de usar una variable ya preparada (j en nuestro caso), este evalúa con la
variable i.
<source lang=c>
VARIANTE: RESTAR
Esta es una de las más usuales que he visto en los libros, folletos y otros. Todo es igual, pero
cambian las condiciones, de esta forma se trabaja a la inversa de nuestro algoritmo.
ALGORITMO DE ORDENAMIENTO RAPIDO O QUICKSORT:
Este algoritmo está basado en la técnica divide y vencerás (consiste en dividir el problema en
pequeños subproblemas más sencillos para luego estos ser resueltos con un cálculo más sencillo)
así crearemos arreglos más pequeños para ordenar estos.
Los pasos que realiza este algoritmo son:
1. Selecciona un valor del arreglo como pivote es decir un numero por el cual todos los elementos
van a ser comparados.
2. se realizan dos búsquedas: una de izquierda a derecha, buscando un elemento mayor que el
pivote, y otra de derecha a izquierda, buscando un elemento menor que el pivote. Cuando se han
encontrado los dos, se intercambian, y se sigue realizando la búsqueda hasta que las dos
búsquedas se encuentran.
3. luego se organizan los subarreglos que quedaron a mano derecha e izquierda.
Ejemplo:
Tenemos un arreglo que está definido con los valores {22,40,4,10,12,35} los pasos en quicksort
para arreglarlo son los siguientes:
1. se toma como pivote el numero 22 recuerden puede ser cualquier número.
2. la búsqueda de izquierda a derecha encuentra el valor 40 que es mayor a pivote y la búsqueda de
derecha a izquierda encuentra el valor 35 no lo toma porque es mayor a el numero pivote
(recuerden que la búsqueda de derecha a izquierda busca los menores) entonces continua y
encuentra a 12 que es menor que el pivote y se intercambian el resultado sería {21,12,4,10,40,35}
16. 3. si seguimos la búsqueda la primera encuentra el valor 40, y la segunda el valor 10,pero ya se han
cruzado, así que paramos. Para terminar la división, se coloca el pivote en su lugar el numero
encontrado por la segunda búsqueda, el 10 quedando: {10,12,4,22,40,35}
4. ahora tenemos dividido el arreglo en dos arreglos más pequeños que son {10,12,4} y el {40,35}, y
en ellos se repetirá el mismo proceso.
PSEUDOCODIGO
Principal
variables A: arreglo[1..100] entero
variables i,j,central:entero
variables primero, ultimo: entero
para i = 1 hasta 100
leer(A[i])
Fin para
primero = 1
ultimo = 100
short(A[],100)
Fin
Función qsort(primero, ultimo:entero)
i = primero
j = ultimo
central = A[(primero,ultimo) div 2]
repetir
mientras A[i]
j = j - 1
fin mientras
si i < = j
aux = A[i]
A[j] = A[i]
A[i] = aux
i = i + 1
j = j - 1
fin si
hasta que i > j
si primero < j
partir(primero,j)
fin si
si i < ultimo
partir(i, ultimo)
fin si
fin función qsort
17. IMPLEMENTACION EN JAVA
public void ordenarQuicksort(int[] vector, int primero, int ultimo){
int i=primero, j=ultimo;
int pivote=vector[(primero + ultimo) / 2];
int auxiliar;
do{
while(vector[i]<pivote) i++;
while(vector[j]>pivote) j--;
if (i<=j){
auxiliar=vector[j];
vector[j]=vector[i];
vector[i]=auxiliar;
i++;
j--;
}
} while (i<=j);
if(primero<j) ordenarQuicksort(vector,primero, j);
if(ultimo>i) ordenarQuicksort(vector,i, ultimo);
}
AQUI LES DEJO UN VIDEO PARA QUE PUEDAN VER MEJOR LA FORMA DE COMPARACION
QUE USA EL QUICKSORT ESPERO QUE SEA DE SU COMPRENSION ESTA VERSION VISUAL
DE COMO FUNCIONA ESTE ORDENAMIENTO!!!
http://www.youtube.com/watch?v=gd7DT-4jjzQ
ALGORITMO DE ORDENAMIENTO SHELL SORT
ORDENAMIENTO SHELL
El Shell Sort es una generalización del ordenamiento por inserción , teniendo en cuenta dos
observaciones:
El ordenamiento por inserción es eficiente si la entrada está "casi ordenada".
El ordenamiento por inserción es ineficiente, en general, porque mueve los valores sólo una
posición cada vez.
El algoritmo Shell sort mejora el ordenamiento por inserción comparando elementos separados por
un espacio de varias posiciones. Esto permite que un elemento haga "pasos más grandes" hacia
su posición esperada. Los pasos múltiples sobre los datos se hacen con tamaños de espacio cada
vez más pequeños. El último paso del Shell sort es un simple ordenamiento por inserción, pero
para entonces, ya está garantizado que los datos del vector están casi ordenados.
18. La sucesión propuesta por Shell:
Peor Caso = O( n^2).
Caso Medio = O(n^2 ).
Con la sucesión
: 2^k-1
Peor Caso = O( n^1.5)
Caso Medio = O( n^1.5 )
Con la sucesión :(4^k+1+3 2^k+1)
Peor Caso = O(n^1.333)
Caso Medio=O(n^1.166)
El mejor caso sería O(n logn).
Método:
public static void shellSort( int a[ ])
{
for( int gap = a.length / 2; gap > 0; gap = gap == 2 ? 1 : (int) ( gap / 2.2 ) ){
for( int i = gap; i < a.length; i++ ){
int tmp = a[ i ];
int j;
for(j = i; j >= gap && tmp < a[ j - gap ] ; j -= gap ){
a[ j ] = a[ j - gap ];
}
Ejemplo:
public class ShellSort {
public static void main(String args[]){
int arrayEntrada[]={321, 123, 213, 234, 1, 4, 5, 6}; Este es el array de elementos que vamos a
ordenar
shellSort(arrayEntrada); llamada al método shellSort
for (int i=0;i < arrayEntrada.length;i++){ Este bucle imprime el contenido del array
System.out.print(arrayEntrada[i]+" ");
}fin del for
}fin del main
public static void shellSort( int a[ ]){
for( int gap = a.length / 2; gap > 0; gap = gap == 2 ? 1 : (int) ( gap / 2.2 ) ){
for( int i = gap; i < a.length; i++ ){
int tmp = a[ i ];
19. int j;
for(j = i; j >= gap && tmp < a[ j - gap ] ; j -= gap ){
a[ j ] = a[ j - gap ];
}
a[ j ] = tmp;
}
}
}
}class ShellSort
ALGORITMO DE ORDENAMIENTO RADIX
El ordenamiento Radix (radix sort en inglés) es un algoritmo de ordenamiento que ordena enteros
procesando sus dígitos de forma individual. Como los enteros pueden representar cadenas de
caracteres (por ejemplo, nombres o fechas) y, especialmente, números en punto flotante
especialmente formateados, radix sort no está limitado sólo a los enteros.
La mayor parte de los ordenadores digitales representan internamente todos sus datos como
representaciones electrónicas de números binarios, por lo que procesar los dígitos de las
representaciones de enteros por representaciones de grupos de dígitos binarios es lo más
conveniente. Existen dos clasificaciones de radix sort: el de dígito menos significativo (LSD) y el de
dígito más significativo (MSD). Radix sort LSD procesa las representaciones de enteros
empezando por el dígito menos significativo y moviéndose hacia el dígito más significativo. Radix
sort MSD trabaja en sentido contrario.
Las representaciones de enteros que son procesadas por los algoritmos de ordenamiento se les
llamas a menudo "claves", que pueden existir por sí mismas o asociadas a otros datos. Radix sort
LSD usa típicamente el siguiente orden: claves cortas aparecen antes que las claves largas, y
claves de la misma longitud son ordenadas de forma léxica. Esto coincide con el orden normal de
las representaciones de enteros, como la secuencia "1, 2, 3, 4, 5, 6, 7, 8, 9, 10". Radix sorts MSD
usa orden léxico, que es ideal para la ordenación de cadenas de caracteres, como las palabras o
representaciones de enteros de longitud fija. Una secuencia como "b, c, d, e, f, g, h, i, j, ba" será
ordenada léxicamente como "b, ba, c, d, e, f, g, h, i, j". Si se usa orden léxico para ordenar
representaciones de enteros de longitud variable, entonces la ordenación de las representaciones
de los números del 1 al 10 será "1, 10, 2, 3, 4, 5, 6, 7, 8, 9", como si las claves más cortas
estuvieran justificadas a la izquierda y rellenadas a la derecha con espacios en blanco, para
hacerlas tan largas como la clave más larga, para el propósito de este ordenamiento.
Ejemplo.
Vector original:
V: 25 57 48 37 12 92 86 33
Asignamos los elementos en colas basadas en el dígito menos significativo de cada uno de ellos.
0:
1:
2: 12, 92
20. 3: 33
4:
5: 25
6: 86
7: 57, 37
8: 48
9:
Después de la primera pasada, la ordenación queda:
V: 12 92 33 25 86 57 37 48
Colas basadas en el dígito más significativo.
0:
1: 12
2: 25
3: 33, 37
4: 48
5: 57
6:
7:
8: 86
9: 92
Lista ordenada:
V: 12, 25, 33, 37, 48, 57, 86, 92
BUSQUEDA SECUENCIAL:
a búsqueda es el proceso de localizar un registro (elemento) con un valor de llave particular. La
búsqueda termina exitosamente cuando se localiza el registro que contenga la llave buscada, o
termina sin éxito, cuando se determina que no aparece ningún registro con esa llave. Búsqueda
secuencial, también se le conoce como búsqueda lineal. Supongamos una colección de registros
organizados como una lista lineal. El algoritmo básico de búsqueda secuencial consiste en
empezar al inicio de la lista e ir a través de cada registro hasta encontrar la l lave indicada (k), o
hasta al final de la lista.
La situación óptima es que el registro buscado sea el primero en ser examinado. El peor caso es
cuando las llaves de todos los n registros son comparados con k (lo que se busca). El caso
promedio es n/2 comparaciones. Este método de búsqueda es muy lento, pero si los datos no
están en orden es el único método que puede emplearse para hacer las búsquedas. Si los valores
de la llave no son únicos, para encontrar todos los registros con una llave particular, se requiere
buscar en toda la lista.
Mejoras en la eficiencia de la búsqueda secuencial
1)Muestreo de acceso
Este método consiste en observar que tan frecuentemente se solicita cada registro y ordenarlos de
21. acuerdo a las probabilidades de acceso detectadas. 2)Movimiento hacia el frente
Este esquema consiste en que la lista de registros se reorganicen dinámicamente. Con este
método, cada vez que búsqueda de una llave sea exitosa, el registro correspondiente se mueve a
la primera posición de la lista y se recorren una posición hacia abajo los que estaban antes que él.
3)Transposición
Este es otro esquema de reorganización dinámica que consiste en que, cada vez que se lleve a
cabo una búsqueda exitosa, el registro correspondiente se intercambia con el anterior. Con este
procedimiento, entre más accesos tenga el registro, más rápidamente avanzara hacia la primera
posición. Comparado con el método de movimiento al frente, el método requiere más tiempo de
actividad para reorganizar al conjunto de registros . Una ventaja de método de transposición es
que no permite que el requerimiento aislado de un registro, cambie de posición todo el conjunto de
registros. De hecho, un registro debe ganar poco a poco su derecho a alcanzar el inicio de la lista.
4)Ordenamiento
Una forma de reducir el número de comparaciones esperadas cuando hay una significativa
frecuencia de búsqueda sin éxito es la de ordenar los registros en base al valor de la llave. Esta
técnica es útil cuando la lista es una lista de excepciones, tales como una lista de decisiones, en
cuyo caso la mayoría de las búsquedas no tendrán éxito. Con este método una búsqueda sin éxito
termina cuando se encuentra el primer valor de la llave mayor que el buscado, en lugar de la final
de la lista.
EJEMPLO DE BÚSQUEDA SECUENCIAL
El siguiente programa cumple con los siguientes requerimientos:
Crea un menú de opciones (INSERTAR, CONSULTAR, ELIMINAR Y FINALIZAR).
INSERTAR: almacena el nombre de personas en vectores estáticos tipo String de tamaño 50.
CONSULTAR: Utilizando el algoritmo de búsqueda secuencial pide el nombre y si lo encuentra
imprime un mensaje de encontrado, en caso contrario un mensaje de no localizado.
ELIMINAR: Utilizando el algoritmo de búsqueda secuencial pide el nombre y si lo encuentra
imprime un mensaje de encontrado y elimina el nombre ajustando el vector para no dejar espacios
en blanco, en caso contrario un mensaje de no localizado.
FINALIZAR: Imprime los nombres almacenado y sale del programa.
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class BusquedaSecuencial {
static BufferedReader br = null;static String N= "";static int n=0;static String[] Nombre
= new String[50];static String[] APaterno = newString[50];static String[] AMaterno = new String[50];
public static void main (String args[]){br
= new BufferedReader(new InputStreamReader(System.in));
do{menu();}while(Integer.parseInt(N)!=4);}
public static void menu(){do{System.out.println("Selecciona una de las opciones del menú: n "+ "1-
22. INSERTAR n "+ "2- CONSULTAR "+ "n 3- ELIMINAR n "+ "4- FINALIZAR");try{N =
br.readLine();}catch(Exception e){e.printStackTrace();}}while(!esEntero(N) || conversor(N)<=0 ||
conversor(N)>=5 );
switch(Integer.parseInt(N)){case 1:insertar();break;case 2:consultar();break;case 3:eliminar();break;
case 4:imprimir();break;}}
public static void consultar(){if(n>0){String nombre="";int eureka=0;
try{System.out.println("Ingresa el Nombre : ");nombre = br.readLine();}catch(Exception
e){e.printStackTrace();}
for(int i=0; i<n;
i++){if(Nombre[i].equals(nombre)){eureka=1; }}i f(eureka==1){System.out.println("Nombre
encontrado!!!!!. ");}else{System.out.println("Nombre NO localizado!!!!!.
");}}else{System.out.println("No hay elementos en la lista. ");}
}
public static boolean esEntero(String cad) {for(int i = 0; i<cad.length(); i++)if(
!Character.isDigit(cad.charAt(i)) )return false;
return true;}
public static int conversor(String x){int valor=0;
try{valor= Integer.parseInt(x);}catch(NumberFormatException e){System.out.println("Valor
invalido");}
return valor;}
public static void insertar(){
if(n<50){System.out.println("Leyendo datos de la persona: " + (n+1));
try{System.out.println("Ingresa el Apellido Paterno: ");APaterno[n] = br.readLine();}catch(Exception
e){e.printStackTrace();}
try{System.out.println("Ingresa el Apellido Materno: ");AMaterno[n] = br.readLine();}catch(Exception
e){e.printStackTrace();}
try{System.out.println("Ingresa el nombre: ");Nombre[n] = br.readLine();}catch(Exception
e){e.printStackTrace();}
n++;}else{System.out.println("El vector está lleno, elimina personas para poder insertar");}
}
23. public static void eliminar(){String nombre="";int encontrados=0;
if(n>0){
try{System.out.println("Ingresa el Nombre : ");nombre = br.readLine();}catch(Exception
e){e.printStackTrace();}
for(int i=0; i<n; i++){if(Nombre[i].equals(nombre)){encontrados++;for(int j=i; j<n;
j++){Nombre[j]=Nombre[j+1];APaterno[j]=APaterno[j+1];AMaterno[j]=AMaterno[j+1];}i --;n--
;}}if(encontrados>0){System.out.println("Nombre encontrado, procediendo a eliminar!!!!!.
");}else{System.out.println("Nombre NO localizado!!!!!. ");}}else{System.out.println("No hay
elementos a eliminar.");}
}
public static void imprimir(){if(n>0){System.out.println("Apellido paterno Apellido Materno
Nombre");for(int i=0; i<n; i++){System.out.print(i+1+ ".- " + APaterno[i]
+ "t");System.out.print(AMaterno [i] + "t");System.out.print(Nombre [i] + ".");System.out.println();}}}
}