Proyecto de Aula – Practica 0
Carlos A. Hernández
Institución Universitaria Politécnico Grancolombiano
M2160-10554 IPA49019 Pensamiento Algorítmico
Instructor Gloria P. Perez
19 septiembre de 2021
2
Tabla de Contenido
Tabla de Contenido 2
Introducción 3
Objetivos 5
Complejidad de los Algoritmos 5
Complejidad temporal y complejidad espacial 6
Complejidad temporal: 6
Complejidad espacial: 7
Reaprovechamiento de cálculos 7
Complejidad Algorítmica de las Estructuras de Control Básicas 7
Complejidad de la combinación de algoritmos 9
Notación asintótica de la complejidad algorítmica 13
Tipos de notación asintótica 14
Comparación de complejidades 15
Propiedades de la notación O 17
Complejidad algorítmica de los tipos abstractos de datos 18
Complejidad del TAD de los números naturales 19
Complejidad del TAD Conjunto 20
Descripción de los Algoritmos con su Referencia 21
Clasificación 21
Estabilidad 23
Sort Burbuja – Pseudocódigo 25
3
Quicksort – Pseudocódigo 26
Eficiencia del Algoritmo 27
Bucket Sort – Pseudocódigo 27
Análisis Comparativo 27
Conclusiones 27
Bibliografía 27
s conocidas T1, T2, ..., Tn, entonces se puede aplicar un conjunto de reglas para calcular
la complejidad del algoritmo A.
• Si dos o más algoritmos se ejecutan secuencialmente
A1
A2
...
An
La complejidad total será la suma de las complejidades parciales, es decir,
T(N) = T1(N) + T2(N) + ... + Tn(N)
4
• Si en una estructura alternativa como la siguiente:
Sí (condición) entonces // condición se denota por C
A1
Sino
A2
Fsi
Entre dos subalgoritmos se ejecuta uno u otro, según se cumpla o no una expresión
(una condición); la complejidad del algoritmo dependerá de qué rama se ejecute. La rama
que se ejecute dependerá de los datos de entrada, pero no de su tamaño. Para diferentes
datos se ejecutará una rama u otra. Por lo tanto, en general no podremos calcular el coste
temporal exacto del algoritmo para datos de tamaño N. Por lo tanto, lo más apropiado
como medida de complejidad sería proporcionar una medida del coste medio para una
ejecución del algoritmo con datos de tamaño N, en función de las veces que se ejecuta
cada rama del algoritmo en término medio. Así pues, la complejidad total será la suma
ponderada de los valores posibles de la función de evaluación para la distribución de
probabilidades {p, 1 – p} más el coste de evaluar la condición TC(N), es decir:
T(N) = p · T1(N) + (1 – p) · T2(N) + TC(N).
Si no se conocen las probabilidades de cada valor resultado de evaluar la expresión,
no podremos calcular el coste medio de una ejecución del algoritmo para datos de tamaño
N. En este caso, una buena medida es considerar que la complejidad total es el máximo
de las complejidades parciales, es decir:
5
T(N) = máx(T1(N),T2(N)) + TC(N).
Eso corresponde a lo que se conoce como análisis del peor caso, que se desarrollará
más adelante, mientras que el caso anterior se corresponde a lo que se conoce como caso
medio. Del mismo modo, si la estructura alternativa sólo tiene una rama, se puede
considerar que, en el peor de los casos (cuando la condición es cierta), la complejidad
total es:
T(N) = T1(N) + TC(N)
• Si en una estructura repetitiva como la siguiente:
Mientras (condición) { // condición se denota por C
Un algoritmo A con complejidad TA(N) se ejecuta dentro de un bucle que depende
exactamente del tamaño del problema N, la complejidad resultante se calcula como:
T(N) = N · TA(N) + (N + 1) · TC(N).
Eso es cierto sólo si el algoritmo A mantiene una complejidad constante a lo largo de
todas las interacciones del bucle. Si no fuera así, y la complejidad dependiera de la interacción,
6
sería necesario hacer una descomposición de las diferentes ejecuciones del algoritmo A y hacer
un sumatorio de la complejidad en cada ejecución, es decir:
Cuando TA(N,j) = TA(N) para toda iteración j (es decir, la complejidad se mantiene
constante), el sumatorio se reduce a N · TA(N).
Del mismo modo, si el bucle ejecuta un número log(N) de veces, la complejidad total se
calcularía como:
Es decir, un bucle que depende del tamaño del problema en función de G(N), incrementa
la complejidad del problema original en G(N) veces, más el coste de evaluar la condición G(N) +
1 veces. Esta regla se aplica directamente en caso de dos o más bucles imbricados de la siguiente
forma:
Mientras (condición1) {// este bucle se ejecuta G1(N) veces
Mientras (condición2) {// y este G2(N) veces
y da una complejidad total (en este caso) de:
7
Es decir, la complejidad crece de manera multiplicativa por cada nivel de bucle.
• Si en una sentencia s (una instrucción o bien la evaluación de una expresión) se hace
una llamada a una función o procedimiento que tiene complejidad Ts (N), se
considera que la sentencia es un subalgoritmo con complejidad Ts (N) y se aplica la
regla de descomposición de un algoritmo en subalgoritmos.
Notación asintótica de la complejidad algorítmica
El uso del número de operaciones básicas ejecutadas por un algoritmo, denotado por
T(N), puede ser complicado para hacer comparaciones entre algoritmos. Para valores pequeños
de N, las constantes que acompañan los términos de T(N) pueden influir muy significativamente
en el coste total que se está evaluando, y pueden llevar a conclusiones erróneas respecto a la
eficiencia del algoritmo. En cambio, lo que interesa es conocer el comportamiento del algoritmo
para valores de N grandes, una situación en la que la elección correcta de un algoritmo puede
permitir reducir la complejidad algorítmica necesaria para resolver un problema de manera
significativa. Un ejemplo claro son los algoritmos de ordenación, en los que, para valores
pequeños de N, los algoritmos simples –como el de inserción, el de selección o el de la burbuja–
pueden ser más eficientes que otros más complejos –por ejemplo, el Quicksort, el shellsort o el
heapsort. En cambio, para valores grandes de N, es bien sabido que estos últimos algoritmos son
mucho más eficientes a pesar de su complejidad intrínseca.
8
Por lo tanto, es necesario disponer de una notación que permita comparar algoritmos
directamente, sin haber de preocuparse por los casos particulares que aparecen cuando se tienen
en cuenta las constantes de las diferentes funciones T(N). Por eso, se usa lo que se conoce como
notación asintótica, que permite hacerse una idea de la complejidad de un algoritmo cuando el
tamaño de la entrada de un problema se hace muy grande.
Tipos de notación asintótica
Dependiendo del tipo de análisis que se realice, podemos encontrar hasta cinco
notaciones asintóticas diferentes. Todas ellas están relacionadas, y algunas son más útiles que
otras según el uso que se les quiera dar. La notación más usada es la llamada O grande, y se
denota por O.
Es decir, informalmente, un algoritmo F tiene complejidad O(G) si el número de
operaciones necesarias queda fijado por el comportamiento de G para valores grandes de N.
Normalmente, las funciones G (órdenes de complejidad) son sencillas, sin constantes. En
realidad, O(G) corresponde a un conjunto de funciones, y con la notación O se consigue poner en
un mismo conjunto todas las funciones con costes “comparables”, es decir, equivalentes con
independencia de factores externos (hardware, sistema operativo, lenguaje de programación,
etc.). Respecto a la constante C, su presencia en la definición de la notación O hace que, en una
función G con complejidad O(G), todas las funciones de la forma C · G (en las que C es una
constante) pertenecen también a O(G). Por lo tanto, se habla de complejidad O(N), por ejemplo,
9
pero no de complejidad O(2 · N), ya que asintóticamente son equivalentes según la definición.
Así, por ejemplo, N pertenece a O(N) y 2 · N también pertenece a O(N).
La tabla 1 muestra las complejidades algorítmicas más importantes.
Es importante destacar que la notación O nos permite establecer un hito superior del
comportamiento de un algoritmo F. Es evidente que se pueden encontrar infinitas funciones G de
manera que se cumpla que F es de orden O(G). Por ejemplo, si un algoritmo tiene una
complejidad temporal T(N) = 2N2 + N, se puede decir que T(N) es de orden O(N2 ), pero
también O(N3 ). Sin embargo, el hecho de decir que T(N) es de orden O(N2 ) aporta mucha más
información que O(N3 ). Las complejidades T(N) que son combinaciones de potencias de N,
como la cuadrática o la cúbica, se denominan polinómicas, y son el umbral de lo que hoy se
considera computacionalmente tratable.
Comparación de complejidades
Es posible hacerse una idea del coste real que representa tener o no disponible un
algoritmo con una complejidad limitada a medida que la dimensión del problema va creciendo.
La tabla 2 muestra el tiempo proporcional (por ejemplo, en segundos) que necesita un algoritmo
según la medida de la entrada y de su complejidad.
10
Multiplicar la medida del problema por un orden de magnitud tiene un incremento muy
diferente dependiendo de la complejidad del algoritmo. Con la potencia de los superordenadores
actuales –que pueden ejecutar casi 1014 operaciones en un segundo, incluso con una medida de
sólo N = 100–, un algoritmo con complejidad exponencial no se puede resolver en un tiempo
razonable, ya que se necesitarían 1016 segundos o, lo que es lo mismo, 317 millones de años.
En general, los algoritmos con complejidad exponencial son computacionalmente
intratables según los estándares actuales, y no se pueden tratar directamente a partir de
mecanismos basados en la fuerza bruta que prueban todas las combinaciones posibles para llegar
a la solución deseada, sino que es necesario utilizar algoritmos que, a partir de una solución
aproximada, obtienen una solución más buena (o incluso óptima) a partir de técnicas
aproximadas o heurísticas.
11
Propiedades de la notación O
La primera propiedad, ya comentada, es que el orden de cualquier función de la forma
O(k · g) en la que k es una constante es O(g); es decir, sea cual sea la función de crecimiento
según el parámetro de entrada, su complejidad crece proporcionalmente a g.
Usando la definición de la notación O, vemos también que O(g1 + g2) es O(máx(g1,g2)).
Es decir, si un algoritmo G tiene dos o más subalgoritmos que se ejecutan secuencialmente sobre
el mismo conjunto de entrada, la complejidad (temporal o espacial) de G será la misma que la del
subalgoritmo que presente la complejidad (o función g) más elevada. En otras palabras, si un
algoritmo realiza dos tareas, la tarea que tenga un comportamiento más costoso es la que
12
determinará la complejidad total del algoritmo. El mismo criterio se puede aplicar al caso de la
estructura de control alternativa, tal y como se ha comentado.
Eso significa que, en general, si un algoritmo se puede descomponer en varias partes, y
una de ellas tiene una complejidad algorítmica superior al resto, la complejidad del algoritmo
quedará determinada por esta parte. Por ejemplo, si un algoritmo de búsqueda intenta ordenar los
datos de entrada con un algoritmo O(N2 ) o O(N log N) para aplicarle después una búsqueda
dicotómica –que es más eficiente que la búsqueda secuencial (O(log N) de la búsqueda
dicotómica delante de O(N) de la búsqueda secuencial)–, el resultado final es peor que si se
hiciese la búsqueda secuencial directamente, ya que la complejidad del algoritmo de ordenación
es superior
Del mismo modo –y hablando de la complejidad algorítmica de los tipos abstractos de
datos–, la complejidad de un TAD dependerá de las operaciones disponibles, su implementación
y la complejidad resultante; por lo cual, un TAD que sea eficiente para unas operaciones puede
no serlo para las otras. Por lo tanto, dependiendo del tipo de operaciones que sea necesario
ejecutar más a menudo, habrá que escoger los algoritmos y las implementaciones de cada una de
las operaciones en función de las necesidades. Así, en el ejemplo anterior, si sobre un conjunto
grande de datos se realizan muchas búsquedas, sí que puede ser interesante ordenarlo una vez,
aunque sea costoso, y reducir el coste global de las operaciones de búsqueda.
Complejidad algorítmica de los tipos abstractos de datos
En el caso concreto de los tipos abstractos de datos, cuando se habla de complejidad
algorítmica es interesante relacionar los conceptos de implementación, eficiencia y complejidad.
13
El objetivo es ser capaces de elegir el tipo de contenedor más adecuado para una cierta colección
de objetos en función de ciertas restricciones temporales y espaciales.
La idea básica es que cada TAD tiene una complejidad conocida en función de su
implementación. De hecho, se indica la complejidad de cada una de las operaciones que ofrece el
TAD. Así, por ejemplo, en la implementación de los números naturales usando el tipo int de Java
que ya conocemos, las operaciones de predecesor y sucesor de un número natural son ambas de
orden O(1); es decir, que se hacen en tiempo constante independientemente del número natural
que se manipula. Eso puede parecer muy eficiente (y, de hecho, lo es), pero es necesario tener en
cuenta que la representación de los números naturales utilizando el tipo entero de Java limita el
rango posible de números representados, lo que puede no ser válido para resolver algún
problema en el que haga falta manipular números naturales muy grandes. Por lo tanto, es posible
que la representación interna más eficiente no sea siempre válida o esté limitada a un cierto
tamaño de problema.
Complejidad del TAD de los números naturales
Para demostrar este hecho, podemos utilizar el ejemplo del TAD que implementa los
números naturales, donde se pueden encontrar dos implementaciones diferentes: una primera que
hace servir el tipo int de Java –en el que el número natural se almacena directamente por su
valor–, y una segunda implementación que usa un vector de elementos booleanos –en los que
cada número natural N se representa por un conjunto de elementos en los que los N primeros
elementos son ‘cierto’ y el resto son ‘falso’. A pesar de que este elemento está lejos de ser
realista, permite una primera comparación de las complejidades de las dos implementaciones.
14
En principio, la implementación utilizando un int resulta ideal: la complejidad espacial es
O(1), y la complejidad temporal de todos los métodos es también O(1); ya que se ejecutan en un
tiempo constante, independientemente del tamaño (la magnitud, en este caso) del número natural
representado. Podríamos decir que la eficiencia de esta implementación es perfecta.
En cambio, la representación basada en un vector de booleanos presenta una eficiencia
menor: la complejidad espacial es O(N) –ya que el espacio necesario crece linealmente con la
magnitud del número natural representado– y, por lo que respecta a la complejidad temporal, los
métodos pred() y succ() tienen una complejidad O(N), –ya que es necesario recorrer todos los
elementos del vector hasta encontrar la posición de lo que se representa. Lo mismo sucede en el
resto de métodos: la causa de esta complejidad temporal es la necesidad de ejecutar un bucle
dentro del método buscarUltimaPosicion() y duplicarVector().
Complejidad del TAD Conjunto
En este caso, un conjunto de N elementos se representa mediante un vector de al menos
N posiciones de tipo Objeto; por lo tanto, su complejidad espacial será O(N). Por lo que respecta
a la complejidad temporal, la tabla 3 muestra el resultado a partir de la implementación de cada
una de las operaciones disponibles.
15
Descripción de los Algoritmos con su Referencia
Algoritmo de ordenamiento. En computación y matemáticas un algoritmo de
ordenamiento es un algoritmo que pone elementos de una lista o un vector en una secuencia dada
por una relación de orden, es decir, el resultado de salida ha de ser una permutación — o
reordenamiento— de la entrada que satisfaga la relación de orden dada. Las relaciones de orden
más usadas son el orden numérico y el orden lexicográfico. Los ordenamientos eficientes son
importantes para optimizar el uso de otros algoritmos (como los de búsqueda y fusión) que
requieren listas ordenadas para una ejecución rápida. También es útil para poner datos en forma
canónica y para generar resultados legibles por humanos.
Desde los comienzos de la computación, el problema del ordenamiento ha atraído gran
cantidad de investigación, tal vez debido a la complejidad de resolverlo eficientemente a pesar de
su planteamiento simple y familiar. Por ejemplo, Bubble Sort fue analizado desde 1956. Aunque
muchos puedan considerarlo un problema resuelto, nuevos y útiles algoritmos de ordenamiento
se siguen inventado hasta el día de hoy (por ejemplo, el ordenamiento de biblioteca se publicó
por primera vez en el 2004). Los algoritmos de ordenamiento son comunes en las clases
introductorias a la computación, donde la abundancia de algoritmos para el problema
proporciona una gentil introducción a la variedad de conceptos núcleo de los algoritmos, como
notación de O mayúscula, algoritmos divide y vencerás, estructuras de datos, análisis de los
casos peor, mejor, y promedio, y límites inferiores.
Clasificación
Los algoritmos de ordenamiento se pueden clasificar de las siguientes maneras:
16
1. La más común es clasificar según el lugar donde se realice la ordenación
Algoritmos de ordenamiento interno: en la memoria del ordenador.
Algoritmos de ordenamiento externo: en un lugar externo como un disco duro.
2. Por el tiempo que tardan en realizar la ordenación, dadas entradas ya ordenadas o
inversamente ordenadas:
Algoritmos de ordenación natural: Tarda lo mínimo posible cuando la entrada está
ordenada.
Algoritmos de ordenación no natural: Tarda lo mínimo posible cuando la entrada
está inversamente ordenada.
3. Por estabilidad: un ordenamiento estable mantiene el orden relativo que tenían
originalmente los elementos con claves iguales. Por ejemplo, si una lista ordenada por
fecha se reordena en orden alfabético con un algoritmo estable, todos los elementos
cuya clave alfabética sea la misma quedarán en orden de fecha. Otro caso sería
cuando no interesan las mayúsculas y minúsculas, pero se quiere que si una clave
aBC estaba antes que AbC, en el resultado ambas claves aparezcan juntas y en el
orden original: aBC, AbC. Cuando los elementos son indistinguibles (porque cada
elemento se ordena por la clave completa) la estabilidad no interesa. Los algoritmos
de ordenamiento que no son estables se pueden implementar para que sí lo sean. Una
manera de hacer esto es modificar artificialmente la clave de ordenamiento de modo
que la posición original en la lista participe del ordenamiento en caso de coincidencia.
Los algoritmos se distinguen por las siguientes características:
17
Complejidad computacional (peor caso, caso promedio y mejor caso) en términos de n, el
tamaño de la lista o arreglo. Para esto se usa el concepto de orden de una función y se usa la
notación O(n). El mejor comportamiento para ordenar (si no se aprovecha la estructura de las
claves) es O(n log n). Los algoritmos más simples son cuadráticos, es decir O(n²). Los
algoritmos que aprovechan la estructura de las claves de ordenamiento (p. ej. bucket sort) pueden
ordenar en O(kn) donde k es el tamaño del espacio de claves. Como dicho tamaño es conocido a
priori, se puede decir que estos algoritmos tienen un desempeño lineal, es decir O(n).
Uso de memoria y otros recursos computacionales. También se usa la notación O(n).
Estabilidad
Los algoritmos de ordenamiento estable mantienen un relativo preorden total. Esto
significa que un algoritmo es estable solo cuando hay dos registros R y S con la misma clave y
con R apareciendo antes que S en la lista original.
Cuando elementos iguales (indistinguibles entre sí), como números enteros, o más
generalmente, cualquier tipo de dato en donde el elemento entero es la clave, la estabilidad no es
un problema. De todas formas, se asume que los siguientes pares de números están por ser
ordenados por su primer componente:
(4, 1) (3, 7) (3, 1) (5, 6)
En este caso, dos resultados diferentes son posibles, uno de los cuales mantiene un orden
relativo de registros con claves iguales, y una en la que no:
18
(3, 7) (3, 1) (4, 1) (5, 6) (orden mantenido).
(3, 1) (3, 7) (4, 1) (5, 6) (orden cambiado).
Los algoritmos de ordenamiento inestable pueden cambiar el orden relativo de registros
con claves iguales, pero los algoritmos estables nunca lo hacen. Los algoritmos inestables
pueden ser implementados especialmente para ser estables. Una forma de hacerlo es extender
artificialmente el cotejamiento de claves, para que las comparaciones entre dos objetos con
claves iguales sean decididas usando el orden de las entradas original. Recordar este orden entre
dos objetos con claves iguales es una solución poco práctica, ya que generalmente acarrea tener
almacenamiento adicional.
Ordenar según una clave primaria, secundaria, terciara, etc., puede ser realizado
utilizando cualquier método de ordenamiento, tomando todas las claves en consideración (en
otras palabras, usando una sola clave compuesta). Si un método de ordenamiento es estable, es
posible ordenar múltiples ítems, cada vez con una clave distinta. En este caso, las claves
necesitan estar aplicadas en orden de aumentar la prioridad. Ejemplo: ordenar pares de números,
usando ambos valores.
(4, 1) (3, 7) (3, 1) (4, 6) (original)
(4, 1) (3, 1) (4, 6) (3, 7) (después de ser ordenado por el segundo valor)
(3, 1) (3, 7) (4, 1) (4, 6) (después de ser ordenado por el primer valor)
19
Por otro lado: (3, 7) (3, 1) (4, 1) (4, 6) (después de ser ordenado por el primer valor)
(3, 1) (4, 1) (4, 6) (3, 7) (después de ser ordenando por el segundo valor, el
orden por el primer valor es perturbado)
Sort Burbuja – Pseudocódigo
Algoritmo de ordenamiento. Burbuja (Bubble Sort en inglés) es un sencillo algoritmo de
ordenamiento. Funciona revisando cada elemento de la lista que va a ser ordenada con el
siguiente, intercambiándolos de posición si están en el orden equivocado. Es necesario revisar
varias veces toda la lista hasta que no se necesiten más intercambios, lo cual significa que la lista
está ordenada. Este algoritmo obtiene su nombre de la forma con la que suben por la lista los
elementos durante los intercambios, como si fueran pequeñas burbujas. También es conocido
como el método del intercambio directo. Dado que solo usa comparaciones para operar
elementos, se lo considera un algoritmo de comparación, siendo el más sencillo de implementar.
El procedimiento de la burbuja es el siguiente: § Ir comparando desde la casilla 0 número
tras número hasta encontrar uno mayor, si este es realmente el mayor de todo el vector se llevará
hasta la última casilla, si no es así, será reemplazado por uno mayor que él. § Este procedimiento
seguirá así hasta que haya ordenado todas las casillas del vector. § Una de las deficiencias del
algoritmo es que ya cuando a ordenado parte del vector vuelve a compararlo cuando esto ya no
es necesario.
Dado un vector a1, a2, a3... an 1) Comparar a1 con a2 e intercambiarlos si a1>a2 (o a12)
2. Seguir hasta que todo se haya comparado an-1 con an 3) Repetir el proceso anterior n-1 veces
Algoritmo: Complejidad for(i=0; i < n-++){ T(n2)
20
Quicksort – Pseudocódigo
QuickSort (en inglés, ordenamiento rápido). Es un algoritmo basado en la técnica de
divide y vencerás, que permite, en promedio, ordenar n elementos en un tiempo proporcional a n
log n.
El algoritmo consta de los siguientes pasos:
1. Elegir un elemento de la lista de elementos a ordenar, al que llamaremos pivote.
2. Resituar los demás elementos de la lista a cada lado del pivote, de manera que a un
lado queden todos los menores que él, y al otro los mayores. Los elementos iguales al
pivote pueden ser colocados tanto a su derecha como a su izquierda, dependiendo de
21
la implementación deseada. En este momento, el pivote ocupa exactamente el lugar
que le corresponderá en la lista ordenada.
3. La lista queda separada en dos sublistas, una formada por los elementos a la izquierda
del pivote, y otra por los elementos a su derecha.
4. Repetir este proceso de forma recursiva para cada sublista mientras éstas contengan
más de un elemento. Una vez terminado este proceso todos los elementos estarán
ordenados.
Eficiencia del Algoritmo
La eficiencia del algoritmo depende de la posición en la que termine el pivote elegido. En
el mejor caso, el pivote termina en el centro de la lista, dividiéndola en dos sublistas de igual
tamaño. En este caso, el orden de complejidad del algoritmo es O(n•log n). En el peor caso, el
pivote termina en un extremo de la lista. El orden de complejidad del algoritmo es entonces de
O(n²). El peor caso dependerá de la implementación del algoritmo, aunque habitualmente ocurre
en listas que se encuentran ordenadas, o casi ordenadas. Pero principalmente depende del pivote,
si por ejemplo el algoritmo implementado toma como pivote siempre el primer elemento del
array, y el array que le pasamos está ordenado, siempre va a generar a su izquierda un array
vacío, lo que es ineficiente. En el caso promedio, el orden es O(n•log n). Y no es extraño, pues,
que la mayoría de optimizaciones que se aplican al algoritmo se centren en la elección del pivote.
Bucket Sort – Pseudocódigo
Análisis Comparativo
Conclusiones
Bibliografía
22
Wikipedia.
Explicación de los distintos métodos de ordenamiento en
Java. (pdf)
Discusión sobre varios algoritmos de ordenación y sus
características. (licencia GFDL) (pdf)
Animación de algoritmos de ordenamient.
Animación de algoritmos de ordenamiento. (en inglés)
ALT: Algorithm Learning Tool. Herramienta de apoyo a la
enseñanza de algoritmos que muestra gráficamente su
funcionamiento. Permite implementar algoritmos propios y
realizar una ejecución dinámica e interactiva.
Códigos de Ordenamiento en Python.
Estructuras de datos.
https://www.mclibre.org/
https://docs.python.org/3/
WIRTH, Niklaus. Algoritmos y Estructuras de Datos. Pentice
Hall, 1987.
WEISS, M. A., Estructuras de datos y algoritmos. Addison-
Wesley Iberoamericana, 1995.
WEISS, M. A., Estructuras de datos y algoritmos. Addison-
Wesley Iberoamericana, 1995.
Curso de programación II, Universidad de Ciencias
Informáticas.
https://www2.infor.uva.es/~jvalvarez/docencia/tema5.pdf
https://www.cs.us.es/~jalonso/cursos/i1m-19/temas/tema-28.html
http://uoc.gitlab.io/2010/EI/ei2.pdf
https://repository.icesi.edu.co/biblioteca_digital/bitstream/10906/85554/1/
T01842.pdf
http://uoc.gitlab.io/2010/EI/ei2.pdf