Algoritmos y Estructura de Datos

Descargar como pdf o txt
Descargar como pdf o txt
Está en la página 1de 80

Algoritmos y Estructura de Datos

Algoritmos y
Estructura
de Datos
2023

Cuadernillo

Melanie Aileen Roman Espitia

Manual 1
Algoritmos y Estructura de Datos

Contenido
INTRODUCCIÓN ............................................................................................................................... 5

1.0 ALGORITMOS FUNDAMENTALES ..................................................................................... 6

1.1 ALGORITMIA ...................................................................................................................... 6

1.1.1 ALGORITMO ..................................................................................................................... 6

1.1.2 PROBLEMAS PROGRAMABLES Y NO PROGRAMABLES ....................................... 6

1.1.3 CARACTERÍSTICAS DE UN ALGORITMO .................................................................. 6

1.1.4 PSEUDOCÓDIGO.............................................................................................................. 6

1.1.5 TIPOS DE ALGORITMOS ................................................................................................ 7

1.1.6 ABSTRACCIÓN DE DATOS ............................................................................................ 8

1.2 PROGRAMACIÓN ORIENTADA A OBJETOS EN C++ ................................................... 9

1.2.1 CLASE ................................................................................................................................ 9

1.2.2 OBJETO .............................................................................................................................. 9

1.2.3 DEFINICIÓN DE ABSTRACCIÓN .................................................................................. 9

1.2.4 DEFINICIÓN DE ENCAPSULACIÓN ............................................................................. 9

1.2.5 DEFINICIÓN DE HERENCIA .......................................................................................... 9

1.2.6 DEFINICIÓN DE POLIMORFISMO ................................................................................ 9

1.3 ALGORITMOS DE ORDENAMIENTO ............................................................................. 9

1.3.1 ORDENAMIENTO POR INSERCIÓN ............................................................................. 9

1.3.2 ORDENAMIENTO POR SELECCIÓN ........................................................................... 10

1.3.3 ORDENAMIENTO BURBUJA ....................................................................................... 11

1.3.4 ORDENAMIENTO DE BURBUJA MEJORADA .......................................................... 11

1.3.5 ORDENAMIENTO MERGE SORT ................................................................................ 12

1.3.6 ORDENAMIENTO MEZCLA ......................................................................................... 14

1.3.7 CONCLUSIONES ............................................................................................................ 14

1.4 ALGORITMOS DE BÚSQUEDA ...................................................................................... 18

1.4.1 MÉTODO 1....................................................................................................................... 18

1.4.2 MÉTODO 2....................................................................................................................... 19

Manual 2
Algoritmos y Estructura de Datos

1.4.3 MÉTODO 3....................................................................................................................... 19

1.4.4 MÉTODO 4....................................................................................................................... 20

1.4.5 MÉTODO 5....................................................................................................................... 20

1.4.6 MÉTODO 6....................................................................................................................... 21

1.4.7 MÉTODO 7....................................................................................................................... 21

1.4.8 MÉTODO 8....................................................................................................................... 22

1.4.9 MÉTODO 9....................................................................................................................... 22

1.4.10 MÉTODO 10 BÚSQUEDA BINARIA .......................................................................... 23

1.4.11 MÉTODO 11 BÚSQUEDA HASHING ......................................................................... 24

1.4.12 CONCLUSIONES .......................................................................................................... 25

1.5 BÚSQUEDA EXHAUSTIVA Y PROGRAMACIÓN VUELTA ATRÁS ........................... 31

1.5.1 PROBLEMA DEL AGENTE VIAJERO (BÚSQUEDA EXHAUSTIVA) ....................... 31

1.5.2 PROBLEMA DE LAS N REINAS (PROGRAMACIÓN VUELTA ATRÁS) ................. 32

1.5.3 PROBLEMA DE LA MOCHILA ..................................................................................... 33

1.5.4 PRGRAMACIÓN ORIENTADA A OBJETOS ................................................................ 35

2.0 ESTRUCTURAS DE DATOS LINEALES ............................................................................ 39

2.1 PILA (STACK)......................................................................................................................... 39

2.1.1 PILA DINÁMICA............................................................................................................. 39

2.1.2 PILA ESTÁTICA .............................................................................................................. 41

2.1.3 PILAS POO....................................................................................................................... 43

2.1.4 EJERCICIO PRÁCTICO .................................................................................................. 44

2.2 COLAS..................................................................................................................................... 45

2.2.1 COLAS DINÁMICAS ...................................................................................................... 46

2.2.2 COLAS ESTÁTICAS ....................................................................................................... 48

2.2.3 COLAS POO..................................................................................................................... 50

2.3 LISTAS ENLAZADAS ........................................................................................................... 53

2.3.1 LISTAS SIMPLEMENTE ENLAZADAS ....................................................................... 54

2.3.2 LISTAS DOBLEMENTE ENLAZADAS ........................................................................ 58

Manual 3
Algoritmos y Estructura de Datos

2.3.3 LISTAS CIRCULARES SIMPLEMENTE ENLAZADAS ............................................. 61

2.3.4 LISTAS CIRCULARES DOBLEMENTE ENLAZADA ................................................. 64

3.0 ESTRUCTURAS DE DATOS NO LINEALES .......................................................................... 68

3.1 ÁRBOLES ............................................................................................................................... 68

3.2 ÁRBOLES BINARIOS............................................................................................................ 69

3.2.1 BÚSQUEDA BINARIA ................................................................................................... 71

3.2.2 ÁRBOL ROJO NEGRO ................................................................................................... 75

Manual 4
Algoritmos y Estructura de Datos

INTRODUCCIÓN
En el presente manual se abordan los temas que corresponden a la unidad de aprendizaje de
Algoritmos y Estructura de Datos, abarcando desde lo que es algoritmia, pasando por algoritmos
fundamentales, estructuras de datos lineales y estructuras de datos no lineales.
El objetivo principal de este trabajo es generar un documento de apoyo para los interesados en la
programación estructurada que es una teoría orientada a mejorar la claridad, calidad y tiempo de
desarrollo utilizando únicamente subrutinas o funciones. en el lenguaje de programación de C++.

Manual 5
Algoritmos y Estructura de Datos

1.0 ALGORITMOS FUNDAMENTALES


1.1 ALGORITMIA
1.1.1 ALGORITMO
Podemos referirnos a un algoritmo como un proceso utilizado para resolver un problema programable
o realizar un cálculo.
Los algoritmos son caracterizados por ser una secuencia de pasos organizados y finitos que describen
el proceso a seguir para dar solución al problema establecido.
1.1.2 PROBLEMAS PROGRAMABLES Y NO PROGRAMABLES
• Los problemas programables se refieren decisiones repetitivas y rutinarias, en los que se ha
establecido un procedimiento definido para manejarlas, cuentan con la característica de
encontrar una solución finita con resultados computacionales, es decir, que una computadora
puede procesar, evaluar y calcular.
• Los problemas no programables son decisiones no estructuradas. No existe un método
definido para manejar el problema porque su naturaleza es compleja, o porque requiere un
tratamiento específico, por lo que no se pueden computar los datos.
1.1.3 CARACTERÍSTICAS DE UN ALGORITMO
• Es corto y preciso
• Lleva un orden estructurado
• Está bien definido (si se sigue el algoritmo dos veces, se debe obtener el mismo resultado cada
vez).
• Contiene un número finito de pasos
1.1.4 PSEUDOCÓDIGO
El pseudocódigo es una herramienta de programación en la que las instrucciones se escriben en
palabras similares al inglés, que facilitan tanto la escritura como la lectura de programas. En esencia,
el pseudocódigo se puede definir como un lenguaje de especificaciones de algoritmos.
El pseudocódigo nació como un lenguaje similar al inglés y era un medio para representar las
estructuras de control de programación estructurada (if, switch, for y while). El pseudocódigo se
considera un borrador inicial, puesto que el pseudocódigo tiene que traducirse posteriormente a un
lenguaje de programación. El pseudocódigo no puede ser ejecutado por consola.
La ventaja del pseudocódigo es que se usa en la planificación de un programa, el programador se
puede concentrar en la lógica y en las estructuras de control y no preocuparse con las reglas de un
lenguaje específico. Es también fácil de modificar en caso de descubrir errores o anomalías en la

Manual 6
Algoritmos y Estructura de Datos

lógica del programa. Otra ventaja del pseudocódigo es que puede ser traducido a lenguajes
estructurados como C, C++ o Java, por sólo mencionar algunos.
El pseudocódigo original utiliza palabras reservadas en inglés, similares a sus homónimos en los
lenguajes de programación, tales como start, end, stop, if-then-else, while-end, repeat-unitl, etcétera.
La escritura del pseudocódigo exige normalmente, por términos prácticos de identificación, la sangría
en el margen izquierdo de diferentes líneas.
Ejemplo de un pseudocódigo de un problema de cálculo del salario neto de un trabajador.
start
//cálculo de impuesto y salarios
read nombre, horas, precio
salario <- horas * precio
tasas <- 0.25 * salario
salario_neto <- salario - tasa
write nombre, salario, tasas, salario_neto
end
1.1.5 TIPOS DE ALGORITMOS
• Algoritmo cualitativo
Son todos aquellos algoritmos en los que los pasos que lo componen se describen de una forma
narrada con un lenguaje natural.
Los algoritmos cualitativos se emplean con frecuencia en la vida diaria para la resolución de
problemas. Por ejemplo: las instrucciones de uso que traen los equipos electrónicos, una receta de
cocina, las instrucciones para el montaje de un equipo, las técnicas de laboratorio para evaluar ácidos,
etc.
Por tanto, un algoritmo puede darse en cualquier proceso que involucre el análisis de una situación y
una solución posible, sin que necesariamente se haga desde una perspectiva científica.
En pocas palabras, estos algoritmos se centran en los problemas no programables.
• Algoritmo cuantitativo
Los algoritmos cuantitativos son aquellos algoritmos que utilizan operaciones algebraicas y cálculos
numéricos específicos para definir un proceso, obteniendo valores concretos. Por ejemplo, el
resultado de una resta o una multiplicación.
En las ciencias de la computación, en las matemáticas y otras disciplinas afines, un algoritmo es un
conjunto finito y ordenado de instrucciones que permite efectuar una actividad por medio de pasos
sucesivos que no generan dudas a quien deba ejecutar estas acciones, llevando a la solución de un
determinado problema.

Manual 7
Algoritmos y Estructura de Datos

Resulta significativo destacar la importancia de los algoritmos, porque representan un elemento


básico para la informática, la robótica y las matemáticas, ya que por medio de ellos se logra ordenar
las ideas. Ellos llevan a la ejecución correcta de las actividades y a ideas con un orden, concernientes
a cualquier aspecto.
Algunos ejemplos en matemáticas son el algoritmo de la división para calcular el cociente de dos
números, el algoritmo de multiplicación para calcular un producto, el método de Gauss para
solucionar un sistema de ecuaciones lineales, o el algoritmo de Euclides para obtener el máximo
común divisor de dos enteros.
1.1.6 ABSTRACCIÓN DE DATOS
Una abstracción es la simplificación de un objeto o de un proceso de la realidad en la que sólo se
consideran los aspectos más relevantes.
La abstracción se utiliza por los programadores para dar sencillez de expresión al algoritmo.
La abstracción tiene dos puntos de vista en programación:
1. Funcional
2. De datos
• Abstracción funcional
Permite dotar a la aplicación de operaciones que no están definidas en el lenguaje en el que se está
trabajando.
Se corresponden con el mecanismo del subprograma (acción que se realiza y argumentos a través de
los cuales toma información y devuelve resultados).
Es irrelevante cómo realiza la acción y no importa su tiempo de ejecución.
• Abstracciones de datos (=Clase)
Permiten utilizar nuevos tipos de datos que se definirán especificando sus posibles valores y las
operaciones que los manipulan.
Cada operación constituye una abstracción funcional.
TAD
Un tipo abstracto de datos (TAD) es un tipo definido por el usuario que:
• Tiene un conjunto de valores y un conjunto de operaciones.
• Cumple con los principios de abstracción, ocultación de la información y se puede manejar
sin conocer la representación interna.
Es decir, los TAD ponen a disposición del programador un conjunto de objetos junto con sus
operaciones básicas que son independientes de la implementación elegida.

Manual 8
Algoritmos y Estructura de Datos

1.2 PROGRAMACIÓN ORIENTADA A OBJETOS EN C++


1.2.1 CLASE
Una clase, es simplemente una abstracción que hacemos de nuestra experiencia sensible. El ser
humano tiende a agrupar seres o cosas (objetos) con características similares en grupos o bien, clases.
1.2.2 OBJETO
Un objeto es un conjunto de atributos y métodos, un objeto se deriva de una clase, se tienen atributos
que se refieren a las características (cómo es) y los métodos que son las acciones que puede realizar.
1.2.3 DEFINICIÓN DE ABSTRACCIÓN
Proceso mental de extracción de las características esenciales de algo, ignorando los detalles
superfluos, esos detalles son adjetivos del objeto, que son irrelevantes en la programación.
1.2.4 DEFINICIÓN DE ENCAPSULACIÓN
Proceso por el que se ocultan los detalles del soporte de las características de una abstracción.
1.2.5 DEFINICIÓN DE HERENCIA
La herencia es donde una clase nueva se crea a partir de una clase existente, heredando todos sus
atributos y métodos.
1.2.6 DEFINICIÓN DE POLIMORFISMO
Por polimorfismo entendemos aquella cualidad que poseen los objetos para responder de distinto
modo ante un mismo mensaje.

1.3 ALGORITMOS DE ORDENAMIENTO


1.3.1 ORDENAMIENTO POR INSERCIÓN
int AUX, k, sw; // Se declaran variables auxiliares: AUX, k y sw
cout << "ordenado: incersion" << endl; // Muestra un mensaje en la consola

for (int iii = 0; iii < 5; iii++) { // Ciclo for que itera sobre los elementos del arreglo x
AUX = x[iii]; // Se guarda el valor del elemento actual en AUX
k = iii - 1; // k se inicializa en el índice anterior al elemento actual
sw = 0; // Se inicializa sw en 0

while ((sw == 0) && (k >= 0)) { // Ciclo while que se ejecuta mientras sw sea 0 y k sea
mayor o igual a 0
if (AUX < x[k]) { // Si el valor en AUX es menor que el valor en x[k]
x[k + 1] = x[k]; // Se desplaza el valor de x[k] una posición a la derecha
k--; // Se decrementa k
} else { // Si AUX no es menor que el valor en x[k]
sw = 1; // Se establece sw en 1 para salir del ciclo while
}
x[k + 1] = AUX; // Se guarda el valor de AUX en la posición correcta
}
}
La complejidad del algoritmo de ordenación por inserción en el programa es O(n^2), donde n es el
tamaño del arreglo x.

Manual 9
Algoritmos y Estructura de Datos

El bucle for externo itera n veces, ya que tiene la condición iii < 5, que es el tamaño del arreglo x.
Por lo tanto, el bucle for externo tiene una complejidad de O(n).
Dentro del bucle for externo, hay un bucle while que se ejecuta en el peor de los casos hasta que k
llegue a 0. En cada iteración del bucle while, se realiza una comparación y un desplazamiento de
elementos en el arreglo. En el peor de los casos, el bucle while ejecutará n-1, n-2, n-3, ..., 2, 1
iteraciones.
Por lo tanto, la complejidad del bucle while en el peor de los casos es la suma de los números naturales
de 1 a n-1, lo cual es equivalente a n(n-1)/2. Simplificando, obtenemos O(n^2).
En resumen, la complejidad total del algoritmo de ordenación por inserción en este programa es
O(n^2). Es importante tener en cuenta que esta complejidad es para un arreglo de tamaño fijo (5 en
este caso), y si el tamaño del arreglo cambia, la complejidad también cambiará proporcionalmente.
1.3.2 ORDENAMIENTO POR SELECCIÓN
cout << "ordenado: seleccion" << endl; // Muestra un mensaje en la consola

int i, j, aux, min; // Se declaran variables auxiliares: i, j, aux y min

for (int i = 0; i < 5; i++) { // Ciclo for que itera sobre los elementos del arreglo x
min = i; // Se asume que el elemento actual es el mínimo
for (j = i + 1; j < 5; j++) { // Ciclo for que busca el mínimo en los elementos restantes
if (x[j] < x[min]) { // Si el valor en x[j] es menor que el valor en x[min]
min = j; // Se actualiza el índice del mínimo
}
}
aux = x[i]; // Se guarda el valor del elemento actual en aux
x[i] = x[min]; // Se intercambia el elemento actual con el mínimo encontrado
x[min] = aux; // Se coloca el mínimo en la posición correcta
}
El programa implementa el algoritmo de ordenación por selección. La complejidad de este algoritmo
es O(n^2), donde n es el tamaño del arreglo x.
El bucle for externo itera n veces, ya que tiene la condición i < 5, que asumiremos que es el tamaño
del arreglo x. Por lo tanto, el bucle for externo tiene una complejidad de O(n).
Dentro del bucle for externo, hay otro bucle for interno que busca el mínimo elemento en los
elementos restantes del arreglo. En cada iteración del bucle for externo, el bucle for interno realiza
comparaciones entre los elementos restantes y actualiza el índice del mínimo si se encuentra un valor
menor. En el peor de los casos, el bucle for interno realizará (n-1) + (n-2) + (n-3) + ... + 2 + 1
comparaciones, lo cual es equivalente a n(n-1)/2. Simplificando, obtenemos O(n^2).
Además, dentro del bucle for externo, se realizan tres operaciones constantes: el intercambio de
elementos y la asignación de valores a las variables aux, x[i] y x[min]. Estas operaciones constantes
no afectan la complejidad general del algoritmo.

Manual 10
Algoritmos y Estructura de Datos

En resumen, la complejidad total del algoritmo de ordenación por selección en este programa es
O(n^2). Es importante tener en cuenta que esta complejidad es para un arreglo de tamaño fijo (5 en
este caso), y si el tamaño del arreglo cambia, la complejidad también cambiará proporcionalmente.
1.3.3 ORDENAMIENTO BURBUJA
cout << "ordenado: burbuja" << endl; // Muestra un mensaje en la consola indicando
que se está utilizando el algoritmo de ordenación de burbuja.

int auxi; // Se declara una variable auxiliar llamada auxi.

for (int i = 0; i < 5; i++) { // Bucle for externo que itera sobre los elementos del arreglo x.
for (int j = 1; j < 4; j++) { // Bucle for interno que recorre los elementos del arreglo x
desde el índice 1 hasta el índice 4.
if (x[j] > x[j + 1]) { // Si el elemento en el índice j es mayor que el elemento en el índice
j+1.
auxi = x[j]; // Se guarda el valor del elemento en auxi.
x[j] = x[j + 1]; // Se intercambian los elementos en los índices j y j+1.
x[j + 1] = auxi; // Se coloca el valor guardado en auxi en el índice j+1.
}
}
}
La complejidad del algoritmo de ordenación de burbuja en el programa es O(n^2), donde n es el
tamaño del arreglo x.
El bucle for externo itera n veces, ya que tiene la condición i < 5, lo cual asumiremos que es el tamaño
del arreglo x. Por lo tanto, el bucle for externo tiene una complejidad de O(n).
Dentro del bucle for externo, hay otro bucle for interno que itera desde el índice 1 hasta el índice 4.
Este bucle for interno tiene un tamaño fijo de 4 iteraciones independientemente del tamaño del arreglo
x.
Dentro del bucle for interno, hay una serie de operaciones constantes: una comparación if y un
intercambio de elementos. Estas operaciones constantes no afectan la complejidad general del
algoritmo.
En resumen, la complejidad total del algoritmo de ordenación de burbuja en este programa es O(n^2).
Es importante tener en cuenta que esta complejidad es para un arreglo de tamaño fijo (5 en este caso),
y si el tamaño del arreglo cambia, la complejidad también cambiará proporcionalmente.
1.3.4 ORDENAMIENTO DE BURBUJA MEJORADA
cout << "ordenado: burbuja mejorada" << endl; // Muestra un mensaje en la consola in-
dicando que se está utilizando el algoritmo de burbuja mejorada.

int ayuda; // Se declara una variable auxiliar llamada ayuda.

for (int i = 1; i < 4; i++) { // Bucle for externo que itera desde el índice 1 hasta el índice 3.
for (int j = 0; j < (5 - i); j++) { // Bucle for interno que recorre los elementos del
arreglo hasta (5 - i).
if (x[j] > x[j + 1]) { // Si el elemento en el índice j es mayor que el elemento en el índice
j+1.
ayuda = x[j]; // Se guarda el valor del elemento en ayuda.
x[j] = x[j + 1]; // Se intercambian los elementos en los índices j y j+1.

Manual 11
Algoritmos y Estructura de Datos

x[j + 1] = ayuda; // Se coloca el valor guardado en ayuda en el índice j+1.


}
}
}
La complejidad del algoritmo de burbuja mejorada en el código es O(n^2), donde n es el tamaño del
arreglo x.
El bucle for externo itera n-1 veces, ya que tiene la condición i < 4, asumiendo que el tamaño del
arreglo x es 5. Por lo tanto, el bucle for externo tiene una complejidad de O(n).
Dentro del bucle for externo, hay otro bucle for interno que itera desde 0 hasta (5 - i), donde i es el
índice del bucle for externo. En la primera iteración del bucle externo (i = 1), el bucle interno itera
hasta (5 - 1) = 4. En la segunda iteración (i = 2), el bucle interno itera hasta (5 - 2) = 3, y así
sucesivamente. Por lo tanto, el número de iteraciones del bucle interno disminuye en cada iteración
del bucle externo.
El número total de iteraciones del bucle interno se puede calcular sumando las iteraciones en cada
iteración del bucle externo:
(5 - 1) + (5 - 2) + (5 - 3) + (5 - 4) = 4 + 3 + 2 + 1 = 10
Por lo tanto, el número total de iteraciones del bucle interno es constante y no depende del tamaño
del arreglo x.
Dentro del bucle for interno, hay una serie de operaciones constantes: una comparación if y un
intercambio de elementos. Estas operaciones constantes no afectan la complejidad general del
algoritmo.
En resumen, la complejidad total del algoritmo de burbuja mejorada en este programa es O(n^2).
Aunque hay una optimización en la cantidad de iteraciones del bucle interno en comparación con el
algoritmo de burbuja tradicional, sigue existiendo el factor cuadrático debido al bucle externo que
itera n-1 veces.
1.3.5 ORDENAMIENTO MERGE SORT
El siguiente código implementa el algoritmo Merge Sort utilizando una implementación recursiva.
Divide el arreglo original en mitades hasta que el tamaño de las partes sea 1. Luego, combina las
partes ordenadas para obtener el arreglo final ordenado. La función merge_sort realiza la división y
llamadas recursivas, mientras que la función merge combina las mitades ordenadas en un arreglo
auxiliar y luego la copia de vuelta al arreglo original.
void Ordenar::merge_sort(int izquierda, int derecha) {
int medio;
// Comprueba si el índice izquierda es menor que el índice derecha
if (izquierda < derecha) {
medio = (izquierda + derecha) / 2; // Calcula el punto medio del rango de elementos
merge_sort(izquierda, medio); // Ordena la mitad izquierda del rango recursivamente
merge_sort(medio + 1, derecha); // Ordena la mitad derecha del rango recursiva-
mente
merge(izquierda, medio, derecha); // Combina las dos mitades ordenadas

Manual 12
Algoritmos y Estructura de Datos

}
}

void Ordenar::merge(int izquierda, int medio, int derecha) {


int h, i, j, b[5], k;
h = izquierda;
i = izquierda;
j = medio + 1;

// Combina las dos mitades ordenadas en un arreglo auxiliar b[]


while ((h <= medio) && (j <= derecha)) {
if (x[h] <= x[j]) {
b[i] = x[h]; // Guarda el elemento de la mitad izquierda en el arreglo auxiliar
h++; // Incrementa el índice de la mitad izquierda
} else {
b[i] = x[j]; // Guarda el elemento de la mitad derecha en el arreglo auxiliar
j++; // Incrementa el índice de la mitad derecha
}
i++; // Incrementa el índice del arreglo auxiliar
}

// Copia los elementos restantes de la mitad izquierda al arreglo auxiliar


if (h > medio) {
for (k = j; k <= derecha; k++) {
b[i] = x[k];
i++;
}
} else {
// Copia los elementos restantes de la mitad derecha al arreglo auxiliar
for (k = h; k <= medio; k++) {
b[i] = x[k];
i++;
}
}

// Copia los elementos del arreglo auxiliar de vuelta al arreglo original x[]
for (k = izquierda; k <= derecha; k++) {
x[k] = b[k];
}
}
La complejidad del algoritmo Merge Sort en este código es O(n log n), donde n es el tamaño del
arreglo x.
El algoritmo Merge Sort se caracteriza por su enfoque de "divide y conquista". En cada paso divide
el arreglo en dos mitades aproximadamente iguales hasta que se llega a arreglos de tamaño 1. Luego,
combina las mitades ordenadas para obtener un arreglo final ordenado.
La función merge_sort realiza las divisiones recursivas en aproximadamente log n pasos, ya que
divide el arreglo a la mitad en cada llamada recursiva. Por lo tanto, su complejidad es O(log n).
La función merge combina las mitades ordenadas en un arreglo auxiliar y luego los copia de vuelta
al arreglo original. El bucle while dentro de la función merge ejecuta un máximo de n comparaciones,
donde n es el tamaño del arreglo x. Por lo tanto, la complejidad del bucle while es O(n).
Dado que la función merge_sort llama a merge en cada paso, y la función merge tiene una
complejidad de O(n), la complejidad total del algoritmo Merge Sort es O(n log n).

Manual 13
Algoritmos y Estructura de Datos

En resumen, el código implementa el algoritmo Merge Sort con una complejidad Big O de O(n log
n), lo que lo hace eficiente para ordenar arreglos grandes.
1.3.6 ORDENAMIENTO MEZCLA
Este algoritmo toma dos parámetros: inicio y fin, que representan los índices del rango de elementos
a ordenar. Primero, verifica si el rango tiene más de un elemento (es decir, inicio es menor que fin).
Si se cumple esta condición, se procede a dividir el rango en dos mitades calculando el punto medio
medio. Luego, se aplica recursivamente la función omezcla al subarreglo izquierdo (desde inicio
hasta medio) y al subarreglo derecho (desde medio + 1 hasta fin). Esto se realiza para realizar la
división recursiva del arreglo original hasta que queden subarreglos de tamaño 1. Finalmente, se llama
a la función mezcla para combinar los subarreglos ordenados en un solo arreglo ordenado.
Este enfoque de dividir y conquistar es lo que caracteriza al algoritmo Merge Sort. La recursión
permite dividir el arreglo original en subarreglos más pequeños, ordenarlos individualmente y luego
combinarlos en un arreglo final ordenado.
if (inicio < fin) {
int medio = inicio + (fin - inicio) / 2; // Calcula el punto medio del rango
omezcla(inicio, medio); // Aplica recursivamente el merge sort al subarreglo iz-
quierdo
omezcla(medio + 1, fin); // Aplica recursivamente el merge sort al subarreglo dere-
cho
mezcla(inicio, medio, fin); // Combina los subarreglos ordenados en un solo arreglo
}
La cantidad total de operaciones realizadas por el programa sigue siendo O(n log n). Cada nivel de
recursión divide el rango de elementos a la mitad, lo cual requiere log n niveles de recursión. En cada
nivel, se realiza una operación de combinación (mezcla) lineal que toma O(n) tiempo. Por lo tanto, el
tiempo de ejecución total del programa es O(n log n) en el peor caso.
1.3.7 CONCLUSIONES
Los algoritmos fundamentales de ordenamiento nos ayudan a comprender la optimización de
algoritmos con la notación BigO, comprendiendo que, aunque cada algoritmo otorga el mismo
resultado, que es un arreglo ordenado, el tiempo y la saturación de memoria en cada uno es distinto.
A continuación, se muestra el código completo en Programación Orientada a Objetos (POO).
#include <iostream>
#include <time.h>
#include<conio.h>

using namespace std;

class Ordenar{
private:
int x[5];
public:
Ordenar();
~Ordenar();
void llenar();
void imprimir();

Manual 14
Algoritmos y Estructura de Datos

void incersion();
void seleccion();
void burbuja();
void burbujam();;
void merge(int,int,int);
void merge_sort(int izquierda,int derecha);
void mezcla(int inicio, int medio, int fin);
void omezcla(int inicio, int fin);
};

Ordenar::Ordenar(){

}
Ordenar::~Ordenar(){

}
void Ordenar::llenar(){
for(int i=0;i<5;i++){
x[i]=rand()%6;
}
}
void Ordenar::imprimir(){
for(int i=0;i<5;i++){
cout<<x[i]<<endl;
}
}
void Ordenar::incersion() {
int AUX, k, sw; // Se declaran variables auxiliares: AUX, k y sw
cout << "ordenado: incersion" << endl; // Muestra un mensaje en la consola

for (int iii = 0; iii < 5; iii++) { // Ciclo for que itera sobre los elementos del arreglo x
AUX = x[iii]; // Se guarda el valor del elemento actual en AUX
k = iii - 1; // k se inicializa en el índice anterior al elemento actual
sw = 0; // Se inicializa sw en 0

while ((sw == 0) && (k >= 0)) { // Ciclo while que se ejecuta mientras sw sea 0 y k sea
mayor o igual a 0
if (AUX < x[k]) { // Si el valor en AUX es menor que el valor en x[k]
x[k + 1] = x[k]; // Se desplaza el valor de x[k] una posición a la derecha
k--; // Se decrementa k
} else { // Si AUX no es menor que el valor en x[k]
sw = 1; // Se establece sw en 1 para salir del ciclo while
}
x[k + 1] = AUX; // Se guarda el valor de AUX en la posición correcta
}
}
}

void Ordenar::seleccion() {
cout << "ordenado: seleccion" << endl; // Muestra un mensaje en la consola

int i, j, aux, min; // Se declaran variables auxiliares: i, j, aux y min

for (int i = 0; i < 5; i++) { // Ciclo for que itera sobre los elementos del arreglo x
min = i; // Se asume que el elemento actual es el mínimo
for (j = i + 1; j < 5; j++) { // Ciclo for que busca el mínimo en los elementos restantes
if (x[j] < x[min]) { // Si el valor en x[j] es menor que el valor en x[min]
min = j; // Se actualiza el índice del mínimo
}
}

Manual 15
Algoritmos y Estructura de Datos

aux = x[i]; // Se guarda el valor del elemento actual en aux


x[i] = x[min]; // Se intercambia el elemento actual con el mínimo encontrado
x[min] = aux; // Se coloca el mínimo en la posición correcta
}
}

void Ordenar::burbuja(){
cout << "ordenado: burbuja" << endl; // Muestra un mensaje en la consola indicando
que se está utilizando el algoritmo de ordenación de burbuja.

int auxi; // Se declara una variable auxiliar llamada auxi.

for (int i = 0; i < 5; i++) { // Bucle for externo que itera sobre los elementos del arreglo x.
for (int j = 1; j < 4; j++) { // Bucle for interno que recorre los elementos del arreglo x
desde el índice 1 hasta el índice 4.
if (x[j] > x[j + 1]) { // Si el elemento en el índice j es mayor que el elemento en el índice
j+1.
auxi = x[j]; // Se guarda el valor del elemento en auxi.
x[j] = x[j + 1]; // Se intercambian los elementos en los índices j y j+1.
x[j + 1] = auxi; // Se coloca el valor guardado en auxi en el índice j+1.
}
}
}

}
void Ordenar::burbujam(){
cout << "ordenado: burbuja mejorada" << endl; // Muestra un mensaje en la consola in-
dicando que se está utilizando el algoritmo de burbuja mejorada.

int ayuda; // Se declara una variable auxiliar llamada ayuda.

for (int i = 1; i < 4; i++) { // Bucle for externo que itera desde el índice 1 hasta el índice 3.
for (int j = 0; j < (5 - i); j++) { // Bucle for interno que recorre los elementos del
arreglo hasta (5 - i).
if (x[j] > x[j + 1]) { // Si el elemento en el índice j es mayor que el elemento en el índice
j+1.
ayuda = x[j]; // Se guarda el valor del elemento en ayuda.
x[j] = x[j + 1]; // Se intercambian los elementos en los índices j y j+1.
x[j + 1] = ayuda; // Se coloca el valor guardado en ayuda en el índice j+1.
}
}
}

}
void Ordenar::merge_sort(int izquierda, int derecha) {
int medio;
// Comprueba si el índice izquierda es menor que el índice derecha
if (izquierda < derecha) {
medio = (izquierda + derecha) / 2; // Calcula el punto medio del rango de elementos
merge_sort(izquierda, medio); // Ordena la mitad izquierda del rango recursivamente
merge_sort(medio + 1, derecha); // Ordena la mitad derecha del rango recursiva-
mente
merge(izquierda, medio, derecha); // Combina las dos mitades ordenadas
}
}

void Ordenar::merge(int izquierda, int medio, int derecha) {


int h, i, j, b[5], k;
h = izquierda;
i = izquierda;

Manual 16
Algoritmos y Estructura de Datos

j = medio + 1;

// Combina las dos mitades ordenadas en un arreglo auxiliar b[]


while ((h <= medio) && (j <= derecha)) {
if (x[h] <= x[j]) {
b[i] = x[h]; // Guarda el elemento de la mitad izquierda en el arreglo auxiliar
h++; // Incrementa el índice de la mitad izquierda
} else {
b[i] = x[j]; // Guarda el elemento de la mitad derecha en el arreglo auxiliar
j++; // Incrementa el índice de la mitad derecha
}
i++; // Incrementa el índice del arreglo auxiliar
}

// Copia los elementos restantes de la mitad izquierda al arreglo auxiliar


if (h > medio) {
for (k = j; k <= derecha; k++) {
b[i] = x[k];
i++;
}
} else {
// Copia los elementos restantes de la mitad derecha al arreglo auxiliar
for (k = h; k <= medio; k++) {
b[i] = x[k];
i++;
}
}

// Copia los elementos del arreglo auxiliar de vuelta al arreglo original x[]
for (k = izquierda; k <= derecha; k++) {
x[k] = b[k];
}
}

void Ordenar::mezcla(int inicio, int medio, int fin){


int i,j,k;
int n1=medio-inicio+1;
int n2=fin-medio;
int izquierda[n1],derecha[n2];
for (i=0;i<n1;i++)
izquierda[i]=x[inicio+i];
for (j=0;j<n2;j++)
derecha[j]=x[medio+1+j];
i=0;
j=0;
k=inicio;
while (i<n1&&j<n2){
if (izquierda[i]<=derecha[j]){
x[k]=izquierda[i];
i++;
}else{
x[k]=derecha[j];
j++;
}
k++;
}
while (i < n1) {
x[k] = izquierda[i];
i++;
k++;

Manual 17
Algoritmos y Estructura de Datos

}
while (j < n2) {
x[k] = derecha[j];
j++;
k++;
}
}
void Ordenar::omezcla(int inicio, int fin) {
if (inicio < fin) {
int medio = inicio + (fin - inicio) / 2; // Calcula el punto medio del rango
omezcla(inicio, medio); // Aplica recursivamente el merge sort al subarreglo iz-
quierdo
omezcla(medio + 1, fin); // Aplica recursivamente el merge sort al subarreglo dere-
cho
mezcla(inicio, medio, fin); // Combina los subarreglos ordenados en un solo arreglo
}
}

int main()
{
srand(time(NULL));
Ordenar I;
I.llenar();
I.imprimir();
cout<<"DECIDA CÓMO ORDENAR EL ARREGLO"<<endl<<"1) Ordenamiento por Incer-
sión"<<endl<<"2) Ordenamiento por Selección"<<endl<<"3) Ordenamiento por Bur-
buja"<<endl
<<"4) Ordenamiento por Burbuja Mejorada"<<endl<<"5) Ordenamiento por Mez-
cla"<<endl<<"6) Ordenamiendo por mezcla del profe xd"<<endl;
int s;
cin>>s;
switch(s){
case 1:{ I.incersion(); I.imprimir(); break;}
case 2:{ I.seleccion(); I.imprimir(); break;}
case 3:{ I.burbuja(); I.imprimir(); break;}
case 4:{ I.burbujam(); I.imprimir(); break;}
case 5:{cout<<"ordenado:mezcla"<<endl;I.merge_sort(0,4); I.imprimir(); break;}
case 6:{cout<<"ordenado:mezcla del profe xd"<<endl;I.omezcla(0,4); I.imprimir();
break;}
default: cout<<"Opcion no valida unu";
}

return 0;
}

1.4 ALGORITMOS DE BÚSQUEDA


1.4.1 MÉTODO 1
void Buscar::metodo1(){
for (int i = 0; i < 5; i++) {
// Itera sobre el arreglo con un índice 'i' desde 0 hasta 4
// (suponiendo que el arreglo tiene 5 elementos)

if (x[i] == t) {
// Compara si el elemento en la posición 'i' del arreglo 'x'
// es igual al valor 't' que se está buscando

cout << "Elemento encontrado :D en la posición: " << endl << i + 1 << endl;

Manual 18
Algoritmos y Estructura de Datos

// Si se cumple la condición, muestra un mensaje indicando


// que se ha encontrado el elemento en la posición 'i+1'
// (se suma 1 para mostrar la posición en términos humanos)

}
}
La complejidad del código es O(n), donde 'n' representa el tamaño del arreglo. Esto se debe a que el
bucle for itera sobre el arreglo con una cantidad fija de elementos (en este caso, 5 elementos) y realiza
una comparación en cada iteración. La complejidad no aumenta en función del tamaño del arreglo,
por lo que se considera una complejidad lineal.
1.4.2 MÉTODO 2
int i = 0;
while ((x[i] != t) && (i <= 5)) {
// Se repite el ciclo mientras el elemento en la posición 'i' del arreglo 'x'
// no sea igual al valor 't' que se está buscando, y 'i' sea menor o igual a 5
i++;
// Incrementa el valor de 'i' en 1 en cada iteración para pasar a la siguiente posición del
arreglo
}
if (x[i] == t) {
// Después de salir del ciclo, se verifica si el elemento en la posición 'i' es igual a 't'
cout << "Elemento encontrado :D en la posición: " << endl << i + 1 << endl;
// Si se encuentra una coincidencia, muestra un mensaje indicando que se ha encon-
trado
// el elemento en la posición 'i+1' (se suma 1 para mostrar la posición en términos huma-
nos)
} else {
cout << "Elemento no se encuentra en el vector D:";
// Si no se encuentra ninguna coincidencia, muestra un mensaje indicando que el ele-
mento no se encuentra en el vector
}
La complejidad del código es O(n), donde 'n' representa el tamaño del arreglo. Esto se debe a que el
bucle while itera hasta encontrar el elemento buscado t o hasta que se haya recorrido todo el arreglo.
En el peor caso, si el elemento no está presente en el arreglo, se recorrerán todos los elementos del
arreglo una vez, lo que implica una complejidad lineal. Si el elemento buscado se encuentra en una
posición temprana del arreglo, la complejidad será menor. En general, se considera una complejidad
lineal ya que el número de iteraciones depende del tamaño del arreglo.
1.4.3 MÉTODO 3
int i = 0; // Se inicializa el contador i en 0
while ((x[i] != t) && (i < 5)) { // Mientras el elemento en la posición i sea diferente de t y i
sea menor a 5
i++; // Se incrementa el contador i
}
if (x[i] == t) { // Si se encontró el elemento t en el arreglo
cout << "Elemento encontrado :D en la posición: " << endl << i + 1 << endl; // Se muestra un
mensaje indicando la posición donde se encontró
} else { // Si no se encontró el elemento t en el arreglo
cout << t << " no se encuentra en el vector D:"; // Se muestra un mensaje indicando que
el elemento no se encuentra
}

Manual 19
Algoritmos y Estructura de Datos

La complejidad Big O del código es O(n), donde n es el tamaño del arreglo. Esto se debe a que en el
peor caso, se debe recorrer todo el arreglo para buscar el elemento t. El bucle while se ejecutará hasta
que se encuentre t o se llegue al final del arreglo, lo cual implica un número de iteraciones
proporcional al tamaño del arreglo. Por lo tanto, la complejidad del algoritmo es lineal en función del
tamaño del arreglo.
1.4.4 MÉTODO 4
int i = 0; // Se inicializa una variable i en 0, que servirá como índice para recorrer el
arreglo
while (i < 5) { // Mientras i sea menor que 5 (el tamaño del arreglo)
if (t == x[i]) { // Si el elemento buscado t es igual al elemento en la posición i del arreglo
cout << "Se encontró el elemento :D en la posición: " << i + 1 << endl; // Se muestra un
mensaje indicando que se encontró el elemento en la posición i+1
i++; // Se incrementa i para avanzar al siguiente elemento del arreglo
} else {
i++; // Si los elementos no son iguales, se incrementa i sin mostrar ningún mensaje
}
}
La complejidad Big O de este código es O(1) en el mejor caso y O(n) en el peor caso, donde "n" es
el tamaño del arreglo (en este caso, 5).
En el mejor caso, cuando el elemento buscado t se encuentra en la primera posición del arreglo, el
bucle se ejecuta una sola vez y encuentra la coincidencia de inmediato. Por lo tanto, la complejidad
es constante, O(1).
En el peor caso, cuando el elemento buscado t no se encuentra en el arreglo o se encuentra en la
última posición, el bucle se ejecutará las 5 iteraciones completas antes de determinar que no hay
coincidencia. En este caso, la complejidad es lineal, O(n).
En promedio, la complejidad se acerca más a O(n) ya que no podemos predecir la ubicación del
elemento buscado en el arreglo.
1.4.5 MÉTODO 5
x[5] = t; // Asigna el valor de t al elemento en la posición 5 del arreglo x (¡Cuidado! El
arreglo debe tener al menos 6 elementos)

int i = 0; // Inicializa la variable i en 0

while (x[i] != t) { // Mientras el valor en la posición i del arreglo x no sea igual a t


i++; // Incrementa el valor de i en 1
}

if (i == 5) { // Si i es igual a 5
cout << "No se ha encontrado el elemento D:" << endl; // Muestra un mensaje indicando
que el elemento no ha sido encontrado
} else {
cout << "Se ha encontrado el elemento :D en la posición: " << i+1 << endl; // Muestra un
mensaje indicando que el elemento ha sido encontrado en la posici ón i+1
}
La complejidad Big O de ese código es O(n), donde n es el tamaño del arreglo x.

Manual 20
Algoritmos y Estructura de Datos

El código recorre el arreglo x en un bucle while hasta encontrar el elemento t o llegar al final del
arreglo. En el peor caso, si el elemento t no está presente en el arreglo, se recorrerán todos los
elementos del arreglo una vez, lo que implica un tiempo proporcional al tamaño del arreglo.
Por lo tanto, la complejidad del algoritmo es lineal, ya que el tiempo de ejecución crece de manera
proporcional al tamaño del arreglo x.
1.4.6 MÉTODO 6
int i = 0, encontrado = 0; // Se declaran variables para el índice y el indicador de encon-
trado
while ((encontrado == 0) && (i < 5)) { // Bucle while que se ejecuta mientras no se en-
cuentre el elemento y no se haya alcanzado el final del arreglo
if (x[i] == t) { // Si el elemento en la posición i es igual a t
encontrado = 1; // Se marca como encontrado
}
i++; // Se incrementa el índice
}
if (encontrado == 1) { // Si se encontró el elemento
cout << "Elemento encontrado :D en la posición: " << i << endl; // Se muestra un mensaje
indicando la posición
} else { // Si no se encontró el elemento
cout << "El elemento no está en el vector D:"; // Se muestra un mensaje indicando que el
elemento no está en el vector
}
La complejidad Big O del código que has proporcionado es O(n), donde n es el tamaño del arreglo x.
Esto se debe a que el código realiza una búsqueda lineal en el arreglo, recorriendo cada elemento
hasta encontrar el elemento buscado (t) o llegar al final del arreglo. En el peor caso, cuando el
elemento buscado no está presente en el arreglo o se encuentra en la última posición, el código deberá
recorrer todos los elementos del arreglo, lo que implica una complejidad lineal O(n).
1.4.7 MÉTODO 7
int i = 0, en = 0; // Se inicializan las variables i y en en 0
while (i < 5) { // Ciclo while que se ejecuta mientras i sea menor que 5
if (x[i] == t) { // Si el elemento en la posición i del arreglo x es igual a t
en = 1; // Se asigna 1 a la variable en, indicando que se ha encontrado el elemento
cout << "Elemento encontrado :D en la posición: " << i + 1 << endl; // Se muestra un men-
saje indicando la posición del elemento encontrado
}
i++; // Se incrementa el valor de i en 1
}
if (en == 0) { // Si la variable en es igual a 0, significa que el elemento no se encontró en el
arreglo
cout << "El número no está en el vector" << endl; // Se muestra un mensaje indicando que
el número no está en el vector
}
La complejidad Big O de ese código es O(n), donde n es el tamaño del arreglo x. Esto se debe a que
el código realiza una búsqueda lineal en el arreglo, recorriendo cada elemento hasta encontrar el
elemento buscado o llegar al final del arreglo. En el peor caso, cuando el elemento buscado no está
presente en el arreglo o se encuentra en la última posición, el código deberá recorrer todos los
elementos del arreglo, lo que implica una complejidad lineal O(n).

Manual 21
Algoritmos y Estructura de Datos

1.4.8 MÉTODO 8
int enc = 0, i = 0; // Variables para indicar si el elemento fue encontrado y el índice de
iteración

do {
if (x[i] == t) { // Si el elemento en x[i] es igual a t
cout << "Elemento encontrado :D en la posición: " << i + 1 << endl; // Muestra un mensaje
indicando la posición del elemento encontrado
enc = 1; // Se marca el elemento como encontrado
}
i++; // Incrementa el índice de iteración
} while (enc == 0 && i < 4); // Se repite el bucle mientras el elemento no se haya encon-
trado y no se haya alcanzado el límite de iteraciones

void Buscar::metodo9() {
int si = 0; // Variable para indicar si el elemento está presente en el arreglo

for (int i = 0; i < 5; i++) {


if (x[i] == t) { // Si el elemento en x[i] es igual a t
si = 1; // Se marca si como 1 para indicar que el elemento está presente
}
}

if (si == 1) {
cout << "Elemento encontrado :D" << endl; // Si si es igual a 1, muestra un mensaje indi-
cando que el elemento fue encontrado
} else {
cout << "Elemento no encontrado D:" << endl; // Si si es igual a 0, muestra un mensaje
indicando que el elemento no fue encontrado
}
}
La complejidad Big O del programa puede ser representada como O(n), donde n es el tamaño del
arreglo x. Ambos bloques de código recorren el arreglo x una vez en cada caso, ya sea mediante un
bucle do-while o un bucle for. El número de iteraciones en ambos casos depende del tamaño del
arreglo, lo que hace que la complejidad sea lineal.
En resumen, la complejidad Big O del programa es lineal, ya que el tiempo de ejecución aumenta
proporcionalmente al tamaño del arreglo.
1.4.9 MÉTODO 9
int si = 0; // Variable para indicar si el elemento está presente en el arreglo

for (int i = 0; i < 5; i++) {


if (x[i] == t) { // Si el elemento en x[i] es igual a t
si = 1; // Se marca si como 1 para indicar que el elemento está presente
}
}

if (si == 1) {
cout << "Elemento encontrado :D" << endl; // Si si es igual a 1, muestra un mensaje indi-
cando que el elemento fue encontrado
} else {
cout << "Elemento no encontrado D:" << endl; // Si si es igual a 0, muestra un mensaje
indicando que el elemento no fue encontrado
}

Manual 22
Algoritmos y Estructura de Datos

La complejidad Big O de este código es O(n), donde n es el tamaño del arreglo x. El bucle for itera
sobre todos los elementos del arreglo una vez, realizando una comparación en cada iteración. La
cantidad de iteraciones es proporcional al tamaño del arreglo, lo que hace que la complejidad sea
lineal.
En resumen, el tiempo de ejecución aumenta linealmente con el tamaño del arreglo, por lo tanto, la
complejidad es O(n).
1.4.10 MÉTODO 10 BÚSQUEDA BINARIA
El primer bloque de código realiza un ordenamiento de burbuja en el arreglo x. Itera sobre los
elementos del arreglo y compara elementos adyacentes, intercambiándolos si están en el orden
incorrecto. Esto se repite hasta que el arreglo esté ordenado de forma ascendente.
El segundo bloque de código realiza una búsqueda binaria en el arreglo ordenado x para encontrar el
valor t. Se establece un rango de búsqueda utilizando las variables b (inicio) y a (fin), y se calcula el
punto medio central. Luego, se compara t con el valor en la posición central y se ajusta el rango de
búsqueda en función del resultado de la comparación. Esto se repite hasta que se encuentre t o el
rango de búsqueda se reduzca a cero.
Finalmente, se imprime un mensaje indicando si se encontró el elemento t en el arreglo y en qué
posición, o si no se encontró en absoluto.
int auxi;
for(int i = 0; i < 5; i++) {
for(int j = 1; j < 4; j++) {
if(x[j] > x[j+1]) { // Compara dos elementos adyacentes y los intercambia si están en
el orden incorrecto
auxi = x[j];
x[j] = x[j+1];
x[j+1] = auxi;
}
}
}

int b = 0, a = 5, central; // Variables para definir el rango de búsqueda


central = (b + a) / 2; // Calcula el punto medio del rango

while(b <= a && x[central] != t) { // Realiza la búsqueda binaria


if(t < x[central]) {
a = central - 1; // Ajusta el rango hacia la mitad inferior
}
else {
b = central + 1; // Ajusta el rango hacia la mitad superior
}
central = (b + a) / 2; // Actualiza el punto medio del rango
}

if(t == x[central]) {
cout << "Elemento encontrado :D en la posición: " << central+1 << endl; // Si se encuentra
el elemento, muestra un mensaje con su posición
}

Manual 23
Algoritmos y Estructura de Datos

else {
cout << "El elemento no está en el vector D:"; // Si no se encuentra el elemento, muestra
un mensaje indicando que no está en el vector
}
Este código tiene dos partes principales: el algoritmo de ordenamiento de burbuja y la búsqueda
binaria.
La complejidad del algoritmo de ordenamiento de burbuja es O(n^2), donde n es el tamaño del arreglo
x. Esto se debe a que hay dos bucles anidados: uno que recorre los elementos del arreglo y otro que
realiza las comparaciones y los intercambios. El peor caso ocurre cuando el arreglo está en orden
inverso, lo que requiere realizar el máximo número de comparaciones y movimientos.
La complejidad de la búsqueda binaria es O(log n), donde n es el tamaño del rango de búsqueda. En
cada iteración, el rango de búsqueda se reduce a la mitad. Esto se debe a que el algoritmo aprovecha
la propiedad de que el arreglo está ordenado. En cada iteración, se descarta la mitad del rango de
búsqueda, lo que lleva a una búsqueda eficiente.
Dado que las partes de ordenamiento y búsqueda se ejecutan de forma secuencial, la complejidad
total del código será la suma de ambas partes: O(n^2 + log n). Sin embargo, en términos de
complejidad asintótica, la parte dominante es el algoritmo de ordenamiento de burbuja, por lo que la
complejidad total se considera O(n^2).
1.4.11 MÉTODO 11 BÚSQUEDA HASHING
El código utiliza una función de hash para determinar la posición en la que se almacena un elemento
en un arreglo x. La función hash_function toma una clave y la reduce a un valor dentro del rango de
0 a 4 utilizando el operador de módulo (%).
El método search utiliza la función de hash para calcular la clave del elemento a buscar. Luego,
realiza una búsqueda en el arreglo x utilizando la estrategia de resolución de colisiones mediante
sondas lineales. Comienza en la posición determinada por la clave y, si encuentra un elemento
diferente de cero, verifica si es igual al elemento buscado. Si encuentra el elemento, devuelve la clave
correspondiente. Si no lo encuentra, calcula una nueva clave incrementando en 1 y repite el proceso
hasta encontrar el elemento o hasta que se encuentre un espacio vacío.
El método desarrollo llama a search para buscar el elemento t y luego muestra un mensaje indicando
si el elemento fue encontrado o no.
int Buscar::hash_function(int key) {
int n;
n = key % 5; // Aplica la función de hash dividiendo la clave entre 5 y tomando el resto
return n; // Devuelve el resultado de la función de hash
}

int Buscar::search(int item) {


int key = hash_function(item); // Calcula la clave aplicando la función de hash al ele-
mento a buscar
while (x[key] != 0) { // Mientras el valor en la posición de la clave no sea cero (indi-
cando espacio vacío)

Manual 24
Algoritmos y Estructura de Datos

if (x[key] == item) { // Si el valor en la posición de la clave coincide con el elemento


buscado
return key; // Retorna la clave como índice donde se encuentra el elemento
}
key = hash_function(key + 1); // Calcula una nueva clave incrementando en 1 y apli-
cando la función de hash
}
return -1; // Si no se encontró el elemento, retorna -1 indicando que no fue encon-
trado
}

void Buscar::desarrollo() {
int a;
a = search(t); // Realiza la búsqueda del elemento t

if (a < 0) {
printf("Elemento no encontrado D:"); // Si el resultado de búsqueda es -1, el elemento
no fue encontrado
} else {
printf("Elemento encontrado :D"); // Si el resultado es una clave válida, el elemento
fue encontrado
}
}
La complejidad del código depende principalmente de la implementación de la función de hash y de
la estrategia de resolución de colisiones utilizada. En este caso, el código utiliza una función de hash
muy simple que realiza una operación de módulo (%), lo cual tiene un tiempo de ejecución constante
O(1) ya que no depende del tamaño de los datos.
La estrategia de resolución de colisiones utilizada es la sonda lineal, donde se busca la siguiente
posición disponible en caso de que la posición inicial esté ocupada. La complejidad de la búsqueda
utilizando sondas lineales puede variar dependiendo de la cantidad de colisiones que ocurran en el
arreglo.
En el mejor caso, donde no hay colisiones y se encuentra el elemento en la primera posición evaluada,
la complejidad de búsqueda es O(1). En el peor caso, donde todas las posiciones están ocupadas y se
tiene que recorrer todo el arreglo hasta encontrar una posición vacía o se llega al final sin encontrar
el elemento, la complejidad de búsqueda es O(N), donde N es la cantidad de elementos en el arreglo.
En resumen, la complejidad del código en general puede ser considerada como O(N), donde N es la
cantidad de elementos almacenados en el arreglo. Sin embargo, la complejidad exacta puede variar
dependiendo de la distribución de los elementos y las colisiones que ocurran en el proceso de
búsqueda.
1.4.12 CONCLUSIONES
Los algoritmos fundamentales de búsqueda nos ayudan, nuevamente, a comprender la optimización
de algoritmos con la notación BigO, comprendiendo que, aunque cada algoritmo otorga el mismo
resultado, que es la búsqueda de un elemento en un arreglo, el tiempo y la saturación de memoria en
cada uno es distinto.

Manual 25
Algoritmos y Estructura de Datos

A continuación, se muestra el código completo en POO.


#include <iostream>
#include <time.h>

using namespace std;

class Buscar{
private:
int x[5], t;

public:
Buscar();
~Buscar();
void llenar();
void imprimir();
void metodo1();
void metodo2();
void metodo3();
void metodo4();
void metodo5();
void metodo6();
void metodo7();
void metodo8();
void metodo9();
void metodo10();
int hash_function(int key);
int search(int item);
void desarrollo();
};
Buscar::Buscar(){

}
Buscar::~Buscar(){

}
void Buscar::llenar(){
for(int i=0;i<5;i++){
x[i]=rand()%6;
}
}
void Buscar::imprimir(){
for(int i=0;i<5;i++){
cout<<x[i]<<endl;
}
cout<<"¿Qué valor desea buscar? uwu >///<"<<endl;
cin>>t;
}
void Buscar::metodo1(){
for (int i = 0; i < 5; i++) {
// Itera sobre el arreglo con un índice 'i' desde 0 hasta 4
// (suponiendo que el arreglo tiene 5 elementos)

if (x[i] == t) {
// Compara si el elemento en la posición 'i' del arreglo 'x'
// es igual al valor 't' que se está buscando

cout << "Elemento encontrado :D en la posición: " << endl << i + 1 << endl;
// Si se cumple la condición, muestra un mensaje indicando
// que se ha encontrado el elemento en la posición 'i+1'

Manual 26
Algoritmos y Estructura de Datos

// (se suma 1 para mostrar la posición en términos humanos)

}
}

}
void Buscar::metodo2(){
int i = 0;
while ((x[i] != t) && (i <= 5)) {
// Se repite el ciclo mientras el elemento en la posición 'i' del arreglo 'x'
// no sea igual al valor 't' que se está buscando, y 'i' sea menor o igual a 5
i++;
// Incrementa el valor de 'i' en 1 en cada iteración para pasar a la siguiente posición del
arreglo
}
if (x[i] == t) {
// Después de salir del ciclo, se verifica si el elemento en la posición 'i' es igual a 't'
cout << "Elemento encontrado :D en la posición: " << endl << i + 1 << endl;
// Si se encuentra una coincidencia, muestra un mensaje indicando que se ha encon-
trado
// el elemento en la posición 'i+1' (se suma 1 para mostrar la posición en términos huma-
nos)
} else {
cout << "Elemento no se encuentra en el vector D:";
// Si no se encuentra ninguna coincidencia, muestra un mensaje indicando que el ele-
mento no se encuentra en el vector
}
}
void Buscar::metodo3(){
int i = 0; // Se inicializa el contador i en 0
while ((x[i] != t) && (i < 5)) { // Mientras el elemento en la posición i sea diferente de t y i
sea menor a 5
i++; // Se incrementa el contador i
}
if (x[i] == t) { // Si se encontró el elemento t en el arreglo
cout << "Elemento encontrado :D en la posición: " << endl << i + 1 << endl; // Se muestra un
mensaje indicando la posición donde se encontró
} else { // Si no se encontró el elemento t en el arreglo
cout << t << " no se encuentra en el vector D:"; // Se muestra un mensaje indicando que
el elemento no se encuentra
}
}
void Buscar::metodo4(){
int i = 0; // Se inicializa una variable i en 0, que servirá como índice para recorrer el
arreglo
while (i < 5) { // Mientras i sea menor que 5 (el tamaño del arreglo)
if (t == x[i]) { // Si el elemento buscado t es igual al elemento en la posición i del arreglo
cout << "Se encontró el elemento :D en la posición: " << i + 1 << endl; // Se muestra un
mensaje indicando que se encontró el elemento en la posición i+1
i++; // Se incrementa i para avanzar al siguiente elemento del arreglo
} else {
i++; // Si los elementos no son iguales, se incrementa i sin mostrar ningún mensaje
}
}
}
void Buscar::metodo5(){
x[5] = t; // Asigna el valor de t al elemento en la posición 5 del arreglo x (¡Cuidado! El
arreglo debe tener al menos 6 elementos)

int i = 0; // Inicializa la variable i en 0

Manual 27
Algoritmos y Estructura de Datos

while (x[i] != t) { // Mientras el valor en la posición i del arreglo x no sea igual a t


i++; // Incrementa el valor de i en 1
}

if (i == 5) { // Si i es igual a 5
cout << "No se ha encontrado el elemento D:" << endl; // Muestra un mensaje indicando
que el elemento no ha sido encontrado
} else {
cout << "Se ha encontrado el elemento :D en la posición: " << i+1 << endl; // Muestra un
mensaje indicando que el elemento ha sido encontrado en la posici ón i+1
}

}
void Buscar::metodo6(){
int i = 0, encontrado = 0; // Se declaran variables para el índice y el indicador de encon-
trado
while ((encontrado == 0) && (i < 5)) { // Bucle while que se ejecuta mientras no se en-
cuentre el elemento y no se haya alcanzado el final del arreglo
if (x[i] == t) { // Si el elemento en la posición i es igual a t
encontrado = 1; // Se marca como encontrado
}
i++; // Se incrementa el índice
}
if (encontrado == 1) { // Si se encontró el elemento
cout << "Elemento encontrado :D en la posición: " << i << endl; // Se muestra un mensaje
indicando la posición
} else { // Si no se encontró el elemento
cout << "El elemento no está en el vector D:"; // Se muestra un mensaje indicando que el
elemento no está en el vector
}
}
void Buscar::metodo7(){
int i = 0, en = 0; // Se inicializan las variables i y en en 0
while (i < 5) { // Ciclo while que se ejecuta mientras i sea menor que 5
if (x[i] == t) { // Si el elemento en la posición i del arreglo x es igual a t
en = 1; // Se asigna 1 a la variable en, indicando que se ha encontrado el elemento
cout << "Elemento encontrado :D en la posición: " << i + 1 << endl; // Se muestra un men-
saje indicando la posición del elemento encontrado
}
i++; // Se incrementa el valor de i en 1
}
if (en == 0) { // Si la variable en es igual a 0, significa que el elemento no se encontró en el
arreglo
cout << "El número no está en el vector" << endl; // Se muestra un mensaje indicando que
el número no está en el vector
}
}
void Buscar::metodo8(){
int enc = 0, i = 0; // Variables para indicar si el elemento fue encontrado y el índice de
iteración

do {
if (x[i] == t) { // Si el elemento en x[i] es igual a t
cout << "Elemento encontrado :D en la posición: " << i + 1 << endl; // Muestra un mensaje
indicando la posición del elemento encontrado
enc = 1; // Se marca el elemento como encontrado
}
i++; // Incrementa el índice de iteración

Manual 28
Algoritmos y Estructura de Datos

} while (enc == 0 && i < 4); // Se repite el bucle mientras el elemento no se haya encon-
trado y no se haya alcanzado el límite de iteraciones

void Buscar::metodo9() {
int si = 0; // Variable para indicar si el elemento está presente en el arreglo

for (int i = 0; i < 5; i++) {


if (x[i] == t) { // Si el elemento en x[i] es igual a t
si = 1; // Se marca si como 1 para indicar que el elemento está presente
}
}

if (si == 1) {
cout << "Elemento encontrado :D" << endl; // Si si es igual a 1, muestra un mensaje indi-
cando que el elemento fue encontrado
} else {
cout << "Elemento no encontrado D:" << endl; // Si si es igual a 0, muestra un mensaje
indicando que el elemento no fue encontrado
}
}

}
void Buscar::metodo10(){
int auxi;
for(int i = 0; i < 5; i++) {
for(int j = 1; j < 4; j++) {
if(x[j] > x[j+1]) { // Compara dos elementos adyacentes y los intercambia si están en
el orden incorrecto
auxi = x[j];
x[j] = x[j+1];
x[j+1] = auxi;
}
}
}

int b = 0, a = 5, central; // Variables para definir el rango de búsqueda


central = (b + a) / 2; // Calcula el punto medio del rango

while(b <= a && x[central] != t) { // Realiza la búsqueda binaria


if(t < x[central]) {
a = central - 1; // Ajusta el rango hacia la mitad inferior
}
else {
b = central + 1; // Ajusta el rango hacia la mitad superior
}
central = (b + a) / 2; // Actualiza el punto medio del rango
}

if(t == x[central]) {
cout << "Elemento encontrado :D en la posición: " << central+1 << endl; // Si se encuentra
el elemento, muestra un mensaje con su posición
}
else {
cout << "El elemento no está en el vector D:"; // Si no se encuentra el elemento, muestra
un mensaje indicando que no está en el vector
}
}
int Buscar::hash_function(int key) {
int n;
n = key % 5; // Aplica la función de hash dividiendo la clave entre 5 y tomando el resto

Manual 29
Algoritmos y Estructura de Datos

return n; // Devuelve el resultado de la función de hash


}

int Buscar::search(int item) {


int key = hash_function(item); // Calcula la clave aplicando la función de hash al ele-
mento a buscar
while (x[key] != 0) { // Mientras el valor en la posición de la clave no sea cero (indi-
cando espacio vacío)
if (x[key] == item) { // Si el valor en la posición de la clave coincide con el elemento
buscado
return key; // Retorna la clave como índice donde se encuentra el elemento
}
key = hash_function(key + 1); // Calcula una nueva clave incrementando en 1 y apli-
cando la función de hash
}
return -1; // Si no se encontró el elemento, retorna -1 indicando que no fue encon-
trado
}

void Buscar::desarrollo() {
int a;
a = search(t); // Realiza la búsqueda del elemento t

if (a < 0) {
printf("Elemento no encontrado D:"); // Si el resultado de búsqueda es -1, el elemento
no fue encontrado
} else {
printf("Elemento encontrado :D"); // Si el resultado es una clave válida, el elemento
fue encontrado
}
}

int main(){
srand(time(NULL));
Buscar U;
U.llenar();
U.imprimir();
cout<<"DECIDA CÓMO BUSCAR EL DATO"<<endl<<"1) Búsqueda Secuencial 1"<<endl<<"2)
Búsqueda secuencial 2"<<endl<<"3) Búsqueda secuencial 3"<<endl<<
"4) Búsqueda secuencial 4"<<endl<<"5) Búsqueda secuencial 5"<<endl<<"6) Búsqueda
secuencial 6"<<endl<<"7) Búsqueda secuencial 7"<<endl<<
"8) Búsqueda secuencial 8"<<endl<<"9) Búsqueda secuencial 9"<<endl<<"10) Búsqueda
Binaria"<<endl<<"11) Búsqueda Hashing"<<endl;
int s;
cin>>s;
switch(s){
case 1:{ U.metodo1(); break;}
case 2:{ U.metodo2(); break;}
case 3:{ U.metodo3(); break;}
case 4:{ U.metodo4(); break;}
case 5:{ U.metodo5(); break;}
case 6:{ U.metodo6(); break;}
case 7:{ U.metodo7(); break;}
case 8:{ U.metodo8(); break;}
case 9:{ U.metodo9(); break;}
case 10:{ U.metodo10(); break;}
case 11:{U.desarrollo(); break;}
default: cout<<"Opcion no valida unu";

Manual 30
Algoritmos y Estructura de Datos

return 0;
}
1.5 BÚSQUEDA EXHAUSTIVA Y PROGRAMACIÓN VUELTA ATRÁS
1.5.1 PROBLEMA DEL AGENTE VIAJERO (BÚSQUEDA EXHAUSTIVA)
Este código implementa el algoritmo de fuerza bruta para resolver el problema del viajante de
comercio (TSP). El objetivo es encontrar el camino más corto que pasa por todos los vértices de un
grafo completo.
La función swap se utiliza para intercambiar dos elementos en un arreglo.
La función tsp es la función principal que realiza las permutaciones de los caminos posibles y
encuentra el camino de costo mínimo. Utiliza recursión para generar todas las permutaciones posibles
y realiza backtracking para restaurar el arreglo de caminos original después de probar una
permutación.
La función viajero es el punto de entrada principal. Define una matriz de adyacencia que representa
el grafo, inicializa los arreglos necesarios y llama a la función tsp para encontrar el camino más corto.
Luego, imprime el costo mínimo y el mejor camino encontrado.
La complejidad del algoritmo de fuerza bruta para TSP es factorial, es decir, O(N!). Esto se debe a
que todas las permutaciones posibles de los vértices deben ser generadas y evaluadas.
void Exhaustiva::swap(int *x, int *y) {
int temp = *x;
*x = *y;
*y = temp;
}
void Exhaustiva::tsp(int graph[][N], int *path, int start, int curr_cost, int *min_cost, int
*best_path) {
int i, j;
// Caso base: si se ha llegado al último vértice
if (start == N - 1) {
int cost = curr_cost + graph[path[N-1]][path[0]]; // Calcular el costo total del ciclo
if (cost < *min_cost) {
*min_cost = cost; // Actualizar el costo mínimo
for (i = 0; i < N; i++) {
best_path[i] = path[i]; // Actualizar el mejor camino encontrado
}
}
return;
}
// Realizar permutaciones para generar todos los posibles caminos
for (i = start; i < N; i++) {
swap(&path[start], &path[i]); // Intercambiar elementos en el arreglo de caminos
tsp(graph, path, start+1, curr_cost + graph[path[start-1]][path[start]], min_cost,
best_path); // Llamada recursiva para el siguiente vértice
swap(&path[start], &path[i]); // Restaurar el arreglo de caminos original para pro-
bar otras permutaciones
}
}
void Exhaustiva::viajero(){

Manual 31
Algoritmos y Estructura de Datos

int i, j;
int graph[N][N] = {
{0, 10, 15, 20},
{10, 0, 35, 25},
{15, 35, 0, 30},
{20, 25, 30, 0}
};
int path[N];
int best_path[N];
int min_cost = INT_MAX;
// Inicializar el arreglo de caminos con valores de 0 a N-1
for (i = 0; i < N; i++) {
path[i] = i;
}
// Llamar a la función tsp para encontrar el mejor camino
tsp(graph, path, 1, 0, &min_cost, best_path);
// Imprimir el costo mínimo y el mejor camino encontrado
printf("Min cost: %d\n", min_cost);
printf("Best path: ");
for (i = 0; i < N; i++) {
printf("%d ", best_path[i]);
}
printf("%d\n", best_path[0]);
}

La complejidad del algoritmo de fuerza bruta para el problema del viajante de comercio (TSP)
implementado en el código dado es de O(N!), donde N es el número de vértices en el grafo.
El algoritmo realiza todas las permutaciones posibles de los vértices y evalúa el costo de cada posible
camino. Dado que el número de permutaciones de N elementos es factorial, la complejidad es O(N!).
Es importante tener en cuenta que el problema del TSP es conocido por ser NP-duro, lo que significa
que no hay algoritmos conocidos que puedan resolverlo en tiempo polinómico para todos los casos.
Por lo tanto, el enfoque de fuerza bruta es válido para grafos pequeños, pero no es factible para
tamaños grandes debido a la explosión combinatoria de las permutaciones.
1.5.2 PROBLEMA DE LAS N REINAS (PROGRAMACIÓN VUELTA ATRÁS)
Este programa resuelve el problema de las N reinas utilizando un enfoque exhaustivo de fuerza bruta.
La función esValido verifica si es seguro colocar una reina en una posición determinada del tablero,
considerando las reinas ya colocadas en filas anteriores. La función colocarReinas utiliza un enfoque
recursivo para colocar las reinas en todas las filas del tablero, comprobando en cada paso si la posición
actual es válida. Finalmente, la función nreinas recibe el tamaño del tablero, crea un arreglo para
almacenar las posiciones de las reinas, llama a colocarReinas y muestra el número de soluciones
encontradas.
bool Exhaustiva::esValido(int arr[], int fila, int columna) {
for (int i = 0; i < fila; i++) {
if (arr[i] == columna || abs(arr[i] - columna) == abs(i - fila)) {
return false; // Hay una amenaza, no es válido colocar una reina en esta posición
}
}

Manual 32
Algoritmos y Estructura de Datos

return true; // No hay amenazas, es válido colocar una reina en esta posición
}
void Exhaustiva::colocarReinas(int arr[], int fila, int n, int &contador) {
if (fila == n) {
contador++; // Se ha encontrado una solución válida, se incrementa el contador
return;
}
for (int i = 0; i < n; i++) {
if (esValido(arr, fila, i)) {
arr[fila] = i; // Se coloca una reina en la posición (fila, i)
colocarReinas(arr, fila + 1, n, contador); // Se llama recursivamente para colocar
las reinas en las filas restantes
}
}
}
void Exhaustiva::nreinas() {
int n, contador = 0;
cout << "Ingrese el tamaño del tablero: ";
cin >> n; // Se lee el tamaño del tablero (n x n)
int arr[n]; // Arreglo para almacenar las posiciones de las reinas en cada fila
colocarReinas(arr, 0, n, contador); // Se inicia el proceso de colocar las reinas desde
la fila 0
cout << "Número de soluciones encontradas: " << contador << endl; // Se imprime el nú-
mero de soluciones encontradas
}
La complejidad Big O de este código depende del tamaño del tablero, que se denota como n.
• La función esValido recorre las filas anteriores para verificar si hay amenazas en la posición
actual. En el peor caso, se recorren fila filas. Por lo tanto, su complejidad es O(fila).
• La función colocarReinas utiliza un enfoque recursivo para colocar las reinas en cada fila.
En el peor caso, se invocará recursivamente n veces, una vez por cada fila. En cada llamada
recursiva, se realiza un bucle de tamaño n para probar todas las columnas posibles. Por lo
tanto, la complejidad total de esta función es O(n^n).
• La función nreinas lee el tamaño del tablero y luego llama a colocarReinas. No tiene un
impacto significativo en la complejidad total del algoritmo.
En resumen, la complejidad Big O del programa completo es O(n^n), donde n es el tamaño del
tablero. Esto implica que el algoritmo tiene una complejidad exponencial y puede volverse muy lento
para tableros grandes.
1.5.3 PROBLEMA DE LA MOCHILA
Este programa resuelve el problema de la mochila utilizando programación dinámica. La función
knapsack implementa el algoritmo y devuelve un par que contiene el valor máximo alcanzado y los
índices de los objetos seleccionados. La función mochila define los valores iniciales y muestra el
resultado obtenido. La complejidad del algoritmo depende del tamaño de la mochila (W) y del número
de objetos (n).
pair<int, vector<int>> knapsack(int W, vector<int>& wt, vector<int>& val, int n) {
// Crear una matriz de tamaño (n+1) x (W+1) inicializada con ceros
vector<vector<int>> dp(n+1, vector<int>(W+1, 0));

Manual 33
Algoritmos y Estructura de Datos

// Iterar desde la fila 1 hasta la fila n


for (int i = 1; i <= n; i++) {
// Iterar desde la columna 1 hasta la columna W
for (int w = 1; w <= W; w++) {
// Verificar si el peso del objeto actual es menor o igual a la capacidad actual
if (wt[i-1] <= w) {
// Calcular el máximo entre no incluir el objeto actual y incluirlo
dp[i][w] = max(dp[i-1][w], val[i-1] + dp[i-1][w-wt[i-1]]);
} else {
// El objeto actual no puede ser incluido, se toma el valor de la fila anterior
dp[i][w] = dp[i-1][w];
}
}
}

// Obtener el valor máximo alcanzado en la mochila


int max_value = dp[n][W];
// Crear un vector para almacenar los índices de los objetos seleccionados
vector<int> items;
// Variable para rastrear la capacidad restante
int w = W;
// Recorrer desde la última fila hacia la primera
for (int i = n; i >= 1 && max_value > 0; i--) {
// Verificar si el valor máximo es igual al valor de la fila anterior en la misma columna
if (max_value == dp[i-1][w])
continue;
else {
// El objeto actual fue incluido en la mochila, actualizar la lista de índices y la capa-
cidad restante
items.push_back(i-1);
max_value -= val[i-1];
w -= wt[i-1];
}
}
// Invertir el orden de los elementos en el vector de índices
reverse(items.begin(), items.end());
// Devolver un par que contiene el valor máximo y la lista de índices
return {dp[n][W], items};
}

void Exhaustiva::mochila(){
// Capacidad máxima de la mochila
int W = 50;
// Pesos de los objetos
vector<int> wt = {10, 20, 30};
// Valores de los objetos
vector<int> val = {60, 100, 120};
// Número total de objetos
int n = wt.size();

// Llamar a la función knapsack para obtener el resultado


auto result = knapsack(W, wt, val, n);

// Imprimir el valor máximo obtenido


cout << "Maximum value: " << result.first << endl;
// Imprimir los índices de los objetos seleccionados
cout << "Selected items: ";
for (auto idx : result.second) {
cout << idx << " ";
}

Manual 34
Algoritmos y Estructura de Datos

cout << endl;


}
La complejidad Big O del programa depende del algoritmo de la mochila implementado en la función
knapsack. En este caso, se utiliza un enfoque de programación dinámica para resolver el problema,
lo que resulta en una complejidad de tiempo de O(n * W), donde n es el número de objetos y W es la
capacidad máxima de la mochila.
En la función knapsack, se crea una matriz de tamaño (n+1) x (W+1) y se realizan dos bucles
anidados para llenarla. Cada celda de la matriz se actualiza en función de los valores de las celdas
anteriores. Por lo tanto, la complejidad de tiempo es proporcional al número de elementos en la
matriz, que es n * W.
Además de la complejidad de tiempo, también hay una complejidad de espacio asociada debido a la
matriz auxiliar utilizada para almacenar los valores parciales. La matriz tiene un tamaño de (n+1) x
(W+1), lo que resulta en una complejidad de espacio de O(n * W).
En resumen, la complejidad Big O del programa es O(n * W) tanto en tiempo como en espacio.
1.5.4 PRGRAMACIÓN ORIENTADA A OBJETOS
#include <iostream>
#include <limits.h>
#include<cstdlib>
#include<cmath>
#include <vector>
#include <algorithm>
#include <string.h>
#include <stdio.h>
#include <bits/stdc++.h>
#include <functional>
#define N 4

using namespace std;

class Exhaustiva{
private:
//VIAJERO
void swap(int *x, int *y);
void tsp(int graph[][N], int *path, int start, int curr_cost, int *min_cost, int *best_path) ;
//MOCHILA
pair<int, vector<int>> knapsack(int W, vector<int>& wt, vector<int>& val, int n);
//NREINAS
bool esValido(int arr[], int fila, int columna);
void colocarReinas(int arr[], int fila, int n, int &contador);
public:
Exhaustiva();
~Exhaustiva();
void viajero();
void nreinas();
void mochila();
};
Exhaustiva::Exhaustiva(){

}
Exhaustiva::~Exhaustiva(){

Manual 35
Algoritmos y Estructura de Datos

}
void Exhaustiva::swap(int *x, int *y) {
int temp = *x;
*x = *y;
*y = temp;
}

void Exhaustiva::tsp(int graph[][N], int *path, int start, int curr_cost, int *min_cost, int
*best_path) {
int i, j;

// Caso base: si se ha llegado al último vértice


if (start == N - 1) {
int cost = curr_cost + graph[path[N-1]][path[0]]; // Calcular el costo total del ciclo
if (cost < *min_cost) {
*min_cost = cost; // Actualizar el costo mínimo
for (i = 0; i < N; i++) {
best_path[i] = path[i]; // Actualizar el mejor camino encontrado
}
}
return;
}

// Realizar permutaciones para generar todos los posibles caminos


for (i = start; i < N; i++) {
swap(&path[start], &path[i]); // Intercambiar elementos en el arreglo de caminos
tsp(graph, path, start+1, curr_cost + graph[path[start-1]][path[start]], min_cost,
best_path); // Llamada recursiva para el siguiente vértice
swap(&path[start], &path[i]); // Restaurar el arreglo de caminos original para pro-
bar otras permutaciones
}
}

void Exhaustiva::viajero(){
int i, j;
int graph[N][N] = {
{0, 10, 15, 20},
{10, 0, 35, 25},
{15, 35, 0, 30},
{20, 25, 30, 0}
};
int path[N];
int best_path[N];
int min_cost = INT_MAX;

// Inicializar el arreglo de caminos con valores de 0 a N-1


for (i = 0; i < N; i++) {
path[i] = i;
}

// Llamar a la función tsp para encontrar el mejor camino


tsp(graph, path, 1, 0, &min_cost, best_path);

// Imprimir el costo mínimo y el mejor camino encontrado


printf("Min cost: %d\n", min_cost);
printf("Best path: ");
for (i = 0; i < N; i++) {
printf("%d ", best_path[i]);
}

Manual 36
Algoritmos y Estructura de Datos

printf("%d\n", best_path[0]);
}

pair<int, vector<int>> knapsack(int W, vector<int>& wt, vector<int>& val, int n) {


// Crear una matriz de tamaño (n+1) x (W+1) inicializada con ceros
vector<vector<int>> dp(n+1, vector<int>(W+1, 0));

// Iterar desde la fila 1 hasta la fila n


for (int i = 1; i <= n; i++) {
// Iterar desde la columna 1 hasta la columna W
for (int w = 1; w <= W; w++) {
// Verificar si el peso del objeto actual es menor o igual a la capacidad actual
if (wt[i-1] <= w) {
// Calcular el máximo entre no incluir el objeto actual y incluirlo
dp[i][w] = max(dp[i-1][w], val[i-1] + dp[i-1][w-wt[i-1]]);
} else {
// El objeto actual no puede ser incluido, se toma el valor de la fila anterior
dp[i][w] = dp[i-1][w];
}
}
}

// Obtener el valor máximo alcanzado en la mochila


int max_value = dp[n][W];
// Crear un vector para almacenar los índices de los objetos seleccionados
vector<int> items;
// Variable para rastrear la capacidad restante
int w = W;
// Recorrer desde la última fila hacia la primera
for (int i = n; i >= 1 && max_value > 0; i--) {
// Verificar si el valor máximo es igual al valor de la fila anterior en la misma columna
if (max_value == dp[i-1][w])
continue;
else {
// El objeto actual fue incluido en la mochila, actualizar la lista de índices y la capa-
cidad restante
items.push_back(i-1);
max_value -= val[i-1];
w -= wt[i-1];
}
}
// Invertir el orden de los elementos en el vector de índices
reverse(items.begin(), items.end());
// Devolver un par que contiene el valor máximo y la lista de índices
return {dp[n][W], items};
}

void Exhaustiva::mochila(){
// Capacidad máxima de la mochila
int W = 50;
// Pesos de los objetos
vector<int> wt = {10, 20, 30};
// Valores de los objetos
vector<int> val = {60, 100, 120};
// Número total de objetos
int n = wt.size();

// Llamar a la función knapsack para obtener el resultado


auto result = knapsack(W, wt, val, n);

Manual 37
Algoritmos y Estructura de Datos

// Imprimir el valor máximo obtenido


cout << "Maximum value: " << result.first << endl;
// Imprimir los índices de los objetos seleccionados
cout << "Selected items: ";
for (auto idx : result.second) {
cout << idx << " ";
}
cout << endl;
}

bool Exhaustiva::esValido(int arr[], int fila, int columna) {


for (int i = 0; i < fila; i++) {
if (arr[i] == columna || abs(arr[i] - columna) == abs(i - fila)) {
return false; // Hay una amenaza, no es válido colocar una reina en esta posición
}
}
return true; // No hay amenazas, es válido colocar una reina en esta posición
}

void Exhaustiva::colocarReinas(int arr[], int fila, int n, int &contador) {


if (fila == n) {
contador++; // Se ha encontrado una solución válida, se incrementa el contador
return;
}
for (int i = 0; i < n; i++) {
if (esValido(arr, fila, i)) {
arr[fila] = i; // Se coloca una reina en la posición (fila, i)
colocarReinas(arr, fila + 1, n, contador); // Se llama recursivamente para colocar
las reinas en las filas restantes
}
}
}

void Exhaustiva::nreinas() {
int n, contador = 0;
cout << "Ingrese el tamaño del tablero: ";
cin >> n; // Se lee el tamaño del tablero (n x n)
int arr[n]; // Arreglo para almacenar las posiciones de las reinas en cada fila
colocarReinas(arr, 0, n, contador); // Se inicia el proceso de colocar las reinas desde
la fila 0
cout << "Número de soluciones encontradas: " << contador << endl; // Se imprime el nú-
mero de soluciones encontradas
}

int main()
{
Exhaustiva I;
cout<<"Busqueda exhaustiva con tres problemas en la mo-
chila"<<endl<<"MANU"<<endl<<"1) Problema del viajero"<<endl
<<"2) Problema de las N reinas"<<endl<<"3) Problema de la mochila"<<endl;
int s;
cin>>s;
switch(s){
case 1:{ I.viajero(); break;}
case 2:{ I.nreinas(); break;}
case 3:{ I.mochila(); break;}
default: cout<<"Opcion no valida unu";
}

Manual 38
Algoritmos y Estructura de Datos

return 0;
}

2.0 ESTRUCTURAS DE DATOS LINEALES


2.1 PILA (STACK)
Una pila (stack) es un tipo de lista lineal en la que la inserción y borrado de nuevos
elementos se realiza sólo por un extremo que se denomina cima o tope.
Las pilas son estructuras LIFO (Last-in, first-out), esto quiere decir que el último
elemento de la pila que entra es el primero en salir.

Push Pop
(Meter, poner o apilar) (Quitar, sacar o desapilar)
Se inserta un elemento a la pila Se elimina un elemento a la pila

Representación de una pila:

2.1.1 PILA DINÁMICA


#include <iostream>
#include <conio.h>
#include <stdlib.h>

using namespace std;

struct Nodo
{
int dato;
Nodo *siguiente;
};

Manual 39
Algoritmos y Estructura de Datos

void agregarPila(Nodo *&, int); // Función prototipo para agregar un nodo a la pila
void sacarPila(Nodo *&, int &);

int main()
{
Nodo *pila = NULL; // Inicializar la pila como vacía
int dato;
cout << "Digite un numero: ";
cin >> dato;
agregarPila(pila, dato); // Agregar el dato a la pila
cout << "Digite otro numero: ";
cin >> dato;
agregarPila(pila, dato); // Agregar el dato a la pila

cout << "\nSacando los elementos de la pila ";

while (pila != NULL) // Mientras la pila no esté vacía


{
sacarPila(pila, dato); // Sacar el elemento de la pila
if (pila != NULL)
{
cout << dato << ", ";
}
else
{
cout << dato << ".";
}
}

return 0;
}

void agregarPila(Nodo *&pila, int n)


{
Nodo *nuevo_nodo = new Nodo(); // Crear un nuevo nodo
nuevo_nodo->dato = n; // Asignar el dato al nuevo nodo
nuevo_nodo->siguiente = pila; // Hacer que el nuevo nodo apunte al nodo siguiente en
la pila
pila = nuevo_nodo; // Actualizar el inicio de la pila al nuevo nodo
cout << "Elemento " << n << " agregado a la pila correctamente" << endl;
}

void sacarPila(Nodo *&pila, int &n)


{
Nodo *aux = pila; // Crear un nodo auxiliar apuntando al inicio de la pila
n = aux->dato; // Obtener el dato del nodo auxiliar
pila = aux->siguiente; // Actualizar el inicio de la pila al siguiente nodo
delete aux; // Liberar la memoria del nodo auxiliar
}

La complejidad Big O del código se puede analizar por separado para las funciones agregarPila y
sacarPila, así como para el bucle principal en main.
1. Función agregarPila:
• Crear un nuevo nodo y asignar el dato: O(1)
• Actualizar los punteros: O(1)

Manual 40
Algoritmos y Estructura de Datos

• Imprimir un mensaje: O(1) Por lo tanto, la complejidad de agregarPila es O(1).


2. Función sacarPila:
• Asignar el valor del nodo al parámetro de salida: O(1)
• Actualizar los punteros: O(1)
• Liberar la memoria: O(1) Por lo tanto, la complejidad de sacarPila es O(1).
3. Bucle principal en main:
• Leer datos de entrada: O(1)
• Llamar a agregarPila: O(1) por cada llamada
• Bucle while hasta que la pila esté vacía:
• Llamar a sacarPila: O(1) por cada llamada
• Imprimir el dato: O(1) En el peor caso, si se agregan n elementos a la pila, el
bucle principal se ejecutará n veces. Por lo tanto, la complejidad del bucle
principal es O(n).
En general, la complejidad total del código se ve dominada por el bucle principal, que es O(n).
2.1.2 PILA ESTÁTICA
#include <iostream>
#include <conio.h>
//Estructura de la pila
#define MAX_SIZE 10 //Define el tamaño de la pila
using namespace std;
//Estructura de la pila
typedef struct{
int data[MAX_SIZE];//Arreglo para almacenar los datos de la pila
int top;
}Stack;
//Funicón para inicializar la pila
void initialize (Stack *s){
s->top=-1;//Inicializa el ínidce (bandera) de la pila en -1
}
//Función para indicar si la pila está vacía
int is_empti(Stack *s){
return s->top==-1;
}
//Función para verificar si la pila está llena
int is_full(Stack *s){
return s->top==MAX_SIZE-1;
}
//Función para agregar un elemento a la pila
void push(Stack *s, int x){
if(is_full(s)){//Verifica si la pila está llena
cout<<"Error, PILA LENAAAAAAAAAAA"<<endl;
exit(EXIT_FAILURE);
}
s->top++;//Incrementa la cima de la pila
s->data[s->top]=x;//Agrega el elemento en la cima de la pila
}
//Función que elimina un elemento de la pila
int pop(Stack *s){
if(is_empti(s)){//verifica si la cima está em la cima

Manual 41
Algoritmos y Estructura de Datos

cout<<"Error: Pila vacía"<<endl;


exit(EXIT_FAILURE);
}
int x=s->data[s->top];//Obtiene elementos de la cima
s->top--;//Decrementa el índice de la cima de la pila
return x;//Retorna el elemento eliminado de la pila
}

int main(){
Stack s; //Declaro una pila uwu
initialize(&s);
push(&s,1);//Agregar elemento 1 a la pila
push(&s,2);
push(&s,3);
cout<<pop(&s); //Elimina el elemento 3 de la pila y lo imprime épicamente
cout<<pop(&s);
cout<<pop(&s);

return 0;
}

La complejidad Big O de este código se puede analizar por separado para cada una de las operaciones
en la estructura de la pila.
1. Operación initialize:
• Asignar -1 a s->top: O(1) Por lo tanto, la complejidad de initialize es O(1).
2. Operación is_empty:
• Verificar si s->top es igual a -1: O(1) Por lo tanto, la complejidad de is_empty es O(1).
3. Operación is_full:
• Verificar si s->top es igual a MAX_SIZE-1: O(1) Por lo tanto, la complejidad de
is_full es O(1).
4. Operación push:
• Verificar si la pila está llena: O(1)
• Incrementar s->top: O(1)
• Asignar el elemento x a s->data[s->top]: O(1) Por lo tanto, la complejidad de push
es O(1).
5. Operación pop:
• Verificar si la pila está vacía: O(1)
• Obtener el elemento de la cima s->data[s->top]: O(1)
• Decrementar s->top: O(1)
• Retornar el elemento obtenido: O(1) Por lo tanto, la complejidad de pop es O(1).
En el bucle principal en la función main, se realizan operaciones de inserción y eliminación de
elementos en la pila utilizando las funciones push y pop. En este caso, se realizan 3 inserciones y 3

Manual 42
Algoritmos y Estructura de Datos

eliminaciones. Por lo tanto, la complejidad del bucle principal es O(1) * 3 = O(3), que se puede
considerar como O(1).
En general, la complejidad total del código es O(1).
2.1.3 PILAS POO
#include <iostream>

using namespace std;

class Bateria{
private:
struct Nodo{
int dato;
Nodo *siguiente;
};
void agregarPila(Nodo *&,int); //Función prototipo agregar un nodo a la pila
void sacarPila(Nodo *&, int &);
public:
Bateria();
~Bateria();
void desarrollo();
};
Bateria::Bateria(){

}
Bateria::~Bateria(){

}
void Bateria::agregarPila(Nodo *&pila,int n)
{
Nodo *nuevo_nodo = new Nodo();
nuevo_nodo ->dato = n;
nuevo_nodo ->siguiente = pila;
pila = nuevo_nodo;
cout<<"Elemento "<<n<<" agregado a la pila correctamente"<<endl;
}

void Bateria::sacarPila(Nodo *&pila, int &n)


{
Nodo *aux = pila;
n = aux ->dato;
pila = aux ->siguiente;
delete aux;
}
void Bateria::desarrollo(){
Nodo *pila = NULL;
int dato;
cout<<"Digite un numero: ";
cin>>dato;
agregarPila(pila,dato);
cout<<"Digite otro numero: ";
cin>>dato;
agregarPila(pila,dato);

cout<<"\n Sacando los elementos de la pila ";

while(pila != NULL) //mientras no sea el final de la pila


{

Manual 43
Algoritmos y Estructura de Datos

sacarPila(pila,dato);
if(pila != NULL)
{
cout<<dato<<" , ";
}
else
{
cout<<dato<<".";
}
}
}

int main()
{
Bateria G;
G.desarrollo();
return 0;
}
2.1.4 EJERCICIO PRÁCTICO
Con los conocimientos adquiridos hasta este momento, cree un programa que separe las letras, dígitos
y caracteres especiales de una cadena de caracteres ingresada por el usuario.
EJEMPLO
Entrada: lagrasa.vive@lalucha_sigue.com
Salida:
Letras: l a g r a s a v i v e l a l u c h a s i
Digitos:
Otros caracteres: . @ _
EJERCICIO RESUELTO
Este programa toma una secuencia de caracteres de entrada y los clasifica en tres categorías: letras,
dígitos y otros caracteres. Luego, imprime cada categoría por separado. La complejidad del programa
depende de la longitud de la secuencia de caracteres ingresada, pero en términos de complejidad
asintótica, se puede considerar como O(n), donde n es la longitud de la secuencia de caracteres.
#include <iostream>
using namespace std;

class Correo {
private:
char pila[20]; // Pila principal
int cima1 = 0, cima2 = 0, cima3 = 0; // Variables para las cimas de las pilas auxiliares
char pilaletras[20], piladigitos[20], pilaotroscaracteres; // Pilas auxiliares para le-
tras, dígitos y otros caracteres
char elemento; // Variable para almacenar el elemento actual

void printL(); // Función para imprimir las letras


void printD(); // Función para imprimir los dígitos
void printO(); // Función para imprimir los otros caracteres

public:
void evaluar(); // Función principal para evaluar los caracteres
};

Manual 44
Algoritmos y Estructura de Datos

void Correo::evaluar() {
while ((elemento = getchar()) != 26 && cima1 < 20 && cima2 < 20 && cima3 < 20) {
if ((elemento > 'A' && elemento <= 'Z') || (elemento >= 'a' && elemento <= 'z')) {
pilaletras[++cima1] = elemento; // Almacena el elemento en la pila de letras
} else if (elemento >= '0' && elemento <= '9') {
piladigitos[++cima2] = elemento; // Almacena el elemento en la pila de dígitos
} else {
pilaotroscaracteres[++cima3] = elemento; // Almacena el elemento en la pila de
otros caracteres
}
}
printL(); // Imprime las letras
printD(); // Imprime los dígitos
printO(); // Imprime los otros caracteres
}

void Correo::printL() {
cout << "Letras: ";
for (int i = 1; i <= cima1; i++) {
cout << pilaletras[i] << " "; // Imprime cada letra almacenada en la pila de letras
}
}

void Correo::printD() {
cout << endl << "Dígitos: ";
for (int i = 1; i <= cima2; i++) {
cout << piladigitos[i] << " "; // Imprime cada dígito almacenado en la pila de dígitos
}
}

void Correo::printO() {
cout << endl << "Otros caracteres: ";
for (int i = 1; i <= cima3; i++) {
cout << pilaotroscaracteres[i] << " "; // Imprime cada otro carácter almacenado en la
pila de otros caracteres
}
cout << endl;
}

int main() {
Correo W;
W.evaluar(); // Inicia el proceso de evaluación de caracteres
return 0;
}
2.2 COLAS
Una cola es una estructura de datos, caracterizada por ser una secuencia de elementos en la que la
operación de inserción se realiza por un extremo y la extracción por otro.
Son estructuras FIFO (Primero en entrar, primero en salir).

Manual 45
Algoritmos y Estructura de Datos

Las operaciones usuales en las colas son insertar (Push) y quitar (Pop). La operación insertar añade
un elemento por el extremo final de la cola, y la operación quitar elimina o extrae un elemento por el
extremo opuesto.

2.2.1 COLAS DINÁMICAS


El programa implementa una cola utilizando una lista enlazada simple. Los comentarios explican las
funciones y los procesos dentro del programa. La complejidad de las operaciones en la cola es O(1)
en promedio para la inserción y eliminación de elementos, y O(n) para vaciar la cola, donde n es el
número de elementos en la cola.

Manual 46
Algoritmos y Estructura de Datos

// FUNCIÓN PARA INSERTAR ELEMENTOS EN LA COLA


void Colauwu::insertarCola(Nodo *&start, Nodo *&end, int n) {
Nodo *nuevo_nodo = new Nodo(); // Crea un nuevo nodo
nuevo_nodo->dato = n; // Asigna el valor n al dato del nuevo nodo
nuevo_nodo->siguiente = NULL; // Establece el siguiente del nuevo nodo como NULL

if (cola_vacia(start)) { // Verifica si la cola está vacía


start = nuevo_nodo; // El nuevo nodo se convierte en el inicio de la cola
} else {
end->siguiente = nuevo_nodo; // El siguiente del último nodo apunta al nuevo nodo
}

end = nuevo_nodo; // El nuevo nodo se convierte en el último nodo de la cola


cout << "\tElemento " << n << " insertado correctamente" << endl;
}

// FUNCIÓN PARA DETERMINAR SI LA COLA ESTÁ VACÍA O NO


bool Colauwu::cola_vacia(Nodo *start) {
return (start == NULL) ? true : false; // Retorna true si el inicio de la cola es NULL, de lo
contrario, retorna false
}

// FUNCIÓN PARA ELIMINAR UN ELEMENTO DE LA COLA


void Colauwu::suprimirCola(Nodo *&start, Nodo *&end, int &n) {
n = start->dato; // Guarda el valor del dato del inicio de la cola en n
Nodo *aux = start; // Crea un nodo auxiliar y lo asigna al inicio de la cola

if (start == end) { // Verifica si hay un solo elemento en la cola


start = NULL; // El inicio y el fin de la cola se establecen como NULL
end = NULL;
} else {
start = start->siguiente; // El inicio de la cola se mueve al siguiente nodo
}

delete aux; // Libera la memoria ocupada por el nodo eliminado


}

void Colauwu::dinamic() {
Nodo *start = NULL; // Inicio de la cola
Nodo *end = NULL; // Fin de la cola
int dato;

cout << "Digite un número: ";


cin >> dato;
insertarCola(start, end, dato); // Inserta el número en la cola

cout << "Digite un número: ";


cin >> dato;
insertarCola(start, end, dato); // Inserta el número en la cola

cout << "Digite un número: ";


cin >> dato;
insertarCola(start, end, dato); // Inserta el número en la cola

cout << "\nQuitando los elementos de la cola ";

while (start != NULL) { // Mientras la cola no esté vacía


suprimirCola(start, end, dato); // Elimina un elemento de la cola
if (start != NULL)
cout << dato << ", "; // Imprime el dato eliminado

Manual 47
Algoritmos y Estructura de Datos

else
cout << dato << ".";
}
}

La complejidad Big O de este programa depende de las operaciones principales que se realizan:
• La operación insertarCola tiene una complejidad de O(1) porque siempre se inserta un
elemento al final de la cola, independientemente del tamaño de la cola.
• La operación suprimirCola también tiene una complejidad de O(1) porque siempre se
elimina el elemento al inicio de la cola, sin importar el tamaño de la cola.
Por lo tanto, la complejidad general del programa es lineal, O(n), donde n es el número total de
elementos que se insertan en la cola. Esto se debe a que las operaciones principales se ejecutan n
veces en el bucle while al eliminar los elementos de la cola.
En resumen, la complejidad Big O del programa es O(n), donde n es el número total de elementos
insertados en la cola.
2.2.2 COLAS ESTÁTICAS
//Función para inicializar la cola
void Colauwu::initializeQueue(struct Queue *q){
q->front=-1; // Inicializa el frente de la cola en -1
q->endo=-1; // Inicializa el final de la cola en -1
}

//Función para verificar si la cola está vacía


int Colauwu::emptyqueue (struct Queue *q){
return q->front==-1 && q->endo==-1; // Retorna verdadero si el frente y el final de
la cola están en -1
}

//Función para verificar si la cola está llena


int Colauwu::fullqueue (struct Queue *q){
return q->endo==MAX_SIZE-1; // Retorna verdadero si el final de la cola está en la úl-
tima posición permitida
}

//Funcion para agregar elementos a la cola


void Colauwu::addqueue (struct Queue *q, int value){
if(fullqueue(q)){ // Verifica si la cola está llena
cout<<"La cola está llena. No se pueden agregar elementos: "<<value<<endl;
return;
}
if(emptyqueue(q)){ // Verifica si la cola está vacía
q->front=0; // Si está vacía, se establece el frente de la cola en 0
}
q->endo++; // Incrementa el final de la cola
q->c[q->endo]=value; // Agrega el elemento al final de la cola
cout<<"Se agregó el elemento correctamente :D : "<<value<<endl;
}

//Función para mostrar los elementos de la cola


void Colauwu::printqueue (struct Queue *q){
if(emptyqueue(q)){ // Verifica si la cola está vacía
cout<<"La cola está vacía unu"<<endl;

Manual 48
Algoritmos y Estructura de Datos

return;
}
cout<<"Los elementos de la cola son:"<<endl;
for (int i=q->front; i<=q->endo; i++){ // Itera desde el frente hasta el final de la cola
cout<<q->c[i]<<"\t"; // Imprime cada elemento de la cola
}
cout<<endl;
}

//Función para eliminar elementos de la cola


void Colauwu::suprqueue(struct Queue *q){
if(emptyqueue(q)){ // Verifica si la cola está vacía
cout<<"La cola está vacía, no se puede eliminar ningún elemento ):"<<endl;
return;
}
int value=q->c[q->front]; // Obtiene el valor del frente de la cola
if(q->front>=q->endo){ // Verifica si solo hay un elemento en la cola
q->front=-1; // Si es así, se reinicia el frente y el final de la cola en -1
q->endo=-1;
}
else{
q->front++; // Si hay más de un elemento, se incrementa el frente de la cola
}
cout<<"Se eliminó el elemento "<<value<<" correctamente"<<endl;
}

void Colauwu::staticc(){
struct Queue q;
initializeQueue(&q); // Inicializa la cola
for(int i=1;i<=10;i++){
addqueue(&q,i); // Agrega elementos a la cola del 1 al 10
}
addqueue(&q,11); // Intenta agregar un elemento adicional cuando la cola está llena
printqueue(&q); // Muestra los elementos de la cola
for(int ii=1;ii<=3;ii++){
suprqueue(&q); // Elimina 3 elementos de la cola
}
printqueue(&q); // Muestra los elementos restantes en la cola
addqueue(&q,11); // Agrega un elemento adicional a la cola
addqueue(&q,12); // Agrega otro elemento adicional a la cola
}
La complejidad Big O del código proporcionado es la siguiente:
• La función initializeQueue: Tiene una complejidad de O(1) ya que solo realiza asignaciones
simples.
• La función emptyqueue: Tiene una complejidad de O(1) ya que solo realiza una operación
de comparación.
• La función fullqueue: Tiene una complejidad de O(1) ya que solo realiza una operación de
comparación.
• La función addqueue: Tiene una complejidad de O(1) en el caso promedio, ya que la mayoría
de las operaciones (verificación de cola llena, asignación de valor, incremento de final) son
operaciones de tiempo constante. Sin embargo, en el peor caso, cuando la cola está llena y se

Manual 49
Algoritmos y Estructura de Datos

imprime el mensaje de error, la complejidad es O(1). Esto se debe a que el tamaño máximo
de la cola es constante.
• La función printqueue: Tiene una complejidad de O(N) ya que recorre la cola e imprime
todos los elementos, donde N es el número de elementos en la cola.
• La función suprqueue: Tiene una complejidad de O(1) en el caso promedio, ya que la mayoría
de las operaciones (verificación de cola vacía, asignación de valor, incremento del frente) son
operaciones de tiempo constante. Sin embargo, en el peor caso, cuando solo hay un elemento
en la cola y se reinicia el frente y el final, la complejidad es O(1).
• La función staticc: Tiene una complejidad de O(N) en el caso promedio, ya que implica
agregar N elementos a la cola y luego eliminar 3 elementos de la cola. La función addqueue
y suprqueue tienen complejidades de tiempo constante en el caso promedio, por lo que su
efecto acumulado es lineal. La función printqueue también tiene una complejidad de O(N)
al imprimir todos los elementos en la cola al final.
En resumen, la complejidad Big O del código proporcionado es:
• O(1) para las funciones initializeQueue, emptyqueue, fullqueue, addqueue, suprqueue en
el caso promedio.
• O(N) para la función printqueue, donde N es el número de elementos en la cola.
2.2.3 COLAS POO
#include <iostream>
#define MAX_SIZE 10

using namespace std;

class Colauwu{
private:
//DINÁMICA
struct Nodo{
int dato;
Nodo *siguiente;
};
void insertarCola(Nodo *&, Nodo *&, int);
bool cola_vacia(Nodo *);
void suprimirCola(Nodo *&, Nodo*&, int &);
//ESTÁTICA
struct Queue{
int c[MAX_SIZE];
int front;
int endo;
};
void initializeQueue(struct Queue *q);
int emptyqueue (struct Queue *q);
int fullqueue (struct Queue *q);
void addqueue (struct Queue *q, int value);
void printqueue (struct Queue *q);
void suprqueue(struct Queue *q);
public:
Colauwu();

Manual 50
Algoritmos y Estructura de Datos

~Colauwu();
//DINAMICA
void dinamic();
//ESTÁTICA
void staticc();
};
Colauwu::Colauwu(){

}
Colauwu::~Colauwu(){

}
// FUNCIÓN PARA INSERTAR ELEMENTOS EN LA COLA
void Colauwu::insertarCola(Nodo *&start, Nodo *&end, int n) {
Nodo *nuevo_nodo = new Nodo(); // Crea un nuevo nodo
nuevo_nodo->dato = n; // Asigna el valor n al dato del nuevo nodo
nuevo_nodo->siguiente = NULL; // Establece el siguiente del nuevo nodo como NULL

if (cola_vacia(start)) { // Verifica si la cola está vacía


start = nuevo_nodo; // El nuevo nodo se convierte en el inicio de la cola
} else {
end->siguiente = nuevo_nodo; // El siguiente del último nodo apunta al nuevo nodo
}

end = nuevo_nodo; // El nuevo nodo se convierte en el último nodo de la cola


cout << "\tElemento " << n << " insertado correctamente" << endl;
}

// FUNCIÓN PARA DETERMINAR SI LA COLA ESTÁ VACÍA O NO


bool Colauwu::cola_vacia(Nodo *start) {
return (start == NULL) ? true : false; // Retorna true si el inicio de la cola es NULL, de lo
contrario, retorna false
}

// FUNCIÓN PARA ELIMINAR UN ELEMENTO DE LA COLA


void Colauwu::suprimirCola(Nodo *&start, Nodo *&end, int &n) {
n = start->dato; // Guarda el valor del dato del inicio de la cola en n
Nodo *aux = start; // Crea un nodo auxiliar y lo asigna al inicio de la cola

if (start == end) { // Verifica si hay un solo elemento en la cola


start = NULL; // El inicio y el fin de la cola se establecen como NULL
end = NULL;
} else {
start = start->siguiente; // El inicio de la cola se mueve al siguiente nodo
}

delete aux; // Libera la memoria ocupada por el nodo eliminado


}

void Colauwu::dinamic() {
Nodo *start = NULL; // Inicio de la cola
Nodo *end = NULL; // Fin de la cola
int dato;

cout << "Digite un número: ";


cin >> dato;
insertarCola(start, end, dato); // Inserta el número en la cola

cout << "Digite un número: ";


cin >> dato;

Manual 51
Algoritmos y Estructura de Datos

insertarCola(start, end, dato); // Inserta el número en la cola

cout << "Digite un número: ";


cin >> dato;
insertarCola(start, end, dato); // Inserta el número en la cola

cout << "\nQuitando los elementos de la cola ";

while (start != NULL) { // Mientras la cola no esté vacía


suprimirCola(start, end, dato); // Elimina un elemento de la cola
if (start != NULL)
cout << dato << ", "; // Imprime el dato eliminado
else
cout << dato << ".";
}
}

//Función para inicializar la cola


void Colauwu::initializeQueue(struct Queue *q){
q->front=-1; // Inicializa el frente de la cola en -1
q->endo=-1; // Inicializa el final de la cola en -1
}

//Función para verificar si la cola está vacía


int Colauwu::emptyqueue (struct Queue *q){
return q->front==-1 && q->endo==-1; // Retorna verdadero si el frente y el final de
la cola están en -1
}

//Función para verificar si la cola está llena


int Colauwu::fullqueue (struct Queue *q){
return q->endo==MAX_SIZE-1; // Retorna verdadero si el final de la cola está en la úl-
tima posición permitida
}

//Funcion para agregar elementos a la cola


void Colauwu::addqueue (struct Queue *q, int value){
if(fullqueue(q)){ // Verifica si la cola está llena
cout<<"La cola está llena. No se pueden agregar elementos: "<<value<<endl;
return;
}
if(emptyqueue(q)){ // Verifica si la cola está vacía
q->front=0; // Si está vacía, se establece el frente de la cola en 0
}
q->endo++; // Incrementa el final de la cola
q->c[q->endo]=value; // Agrega el elemento al final de la cola
cout<<"Se agregó el elemento correctamente :D : "<<value<<endl;
}

//Función para mostrar los elementos de la cola


void Colauwu::printqueue (struct Queue *q){
if(emptyqueue(q)){ // Verifica si la cola está vacía
cout<<"La cola está vacía unu"<<endl;
return;
}
cout<<"Los elementos de la cola son:"<<endl;
for (int i=q->front; i<=q->endo; i++){ // Itera desde el frente hasta el final de la cola
cout<<q->c[i]<<"\t"; // Imprime cada elemento de la cola
}
cout<<endl;

Manual 52
Algoritmos y Estructura de Datos

//Función para eliminar elementos de la cola


void Colauwu::suprqueue(struct Queue *q){
if(emptyqueue(q)){ // Verifica si la cola está vacía
cout<<"La cola está vacía, no se puede eliminar ningún elemento ):"<<endl;
return;
}
int value=q->c[q->front]; // Obtiene el valor del frente de la cola
if(q->front>=q->endo){ // Verifica si solo hay un elemento en la cola
q->front=-1; // Si es así, se reinicia el frente y el final de la cola en -1
q->endo=-1;
}
else{
q->front++; // Si hay más de un elemento, se incrementa el frente de la cola
}
cout<<"Se eliminó el elemento "<<value<<" correctamente"<<endl;
}

void Colauwu::staticc(){
struct Queue q;
initializeQueue(&q); // Inicializa la cola
for(int i=1;i<=10;i++){
addqueue(&q,i); // Agrega elementos a la cola del 1 al 10
}
addqueue(&q,11); // Intenta agregar un elemento adicional cuando la cola está llena
printqueue(&q); // Muestra los elementos de la cola
for(int ii=1;ii<=3;ii++){
suprqueue(&q); // Elimina 3 elementos de la cola
}
printqueue(&q); // Muestra los elementos restantes en la cola
addqueue(&q,11); // Agrega un elemento adicional a la cola
addqueue(&q,12); // Agrega otro elemento adicional a la cola
}

int main()
{
Colauwu Q;
cout<<"MANU"<<endl<<"1) Cola dinámica"<<endl<<"2) Cola estática"<<endl;
int s;
cin>>s;
switch(s){
case 1:{Q.dinamic(); break;}
case 2:{Q.staticc(); break;}
}
return 0;
}
2.3 LISTAS ENLAZADAS
Una lista enlazada consta de un número de nodos con dos componentes (campos), un enlace al
siguiente nodo de la lista y un valor, que puede ser de cualquier tipo.

Manual 53
Algoritmos y Estructura de Datos

2.3.1 LISTAS SIMPLEMENTE ENLAZADAS


Cada nodo (elemento) contiene un único enlace que conecta ese nodo al nodo siguiente o nodo
sucesor. La lista es eficiente en recorridos directos (Adelante).

#include <iostream>
#include <conio.h>

using namespace std;

class Lista{
private:
public:
struct Node{
int value;
Node *next;
};

// FUNCIONES PROTOTIPO

// Insertar un elemento en la lista


void insertlist (Node *&,int);

// Mostrar los elementos de la lista


void showlist(Node*);

// Buscar un elemento en la lista


void searchlist (Node*,int);

// Eliminar un elemento de la lista


void suprlist(Node*&,int);

// Dejar vacía la lista


void deletelist(Node*&,int&);

// Imprimir el valor mayor y menor de la lista


void assess(Node*);
};

void Lista::insertlist (Node *&list,int n){


Node *new_node=new Node();
new_node->value=n;
Node *aux1=list;
Node *aux2;

// Buscar la posición adecuada para insertar el nuevo nodo en orden ascendente


while ((aux1!=NULL)&&(aux1->value<n)){
aux2=aux1;
aux1=aux1->next;
}

// Si la lista está vacía o el nuevo elemento es el menor, se actualiza el inicio de la lista


if(list==aux1){
list=new_node;
}

Manual 54
Algoritmos y Estructura de Datos

else{
aux2->next=new_node;
}

// Se enlaza el nuevo nodo con el nodo siguiente


new_node->next=aux1;

cout<<"\t Elemento "<<n<<" insertado correctamente a la lista uwu"<<endl;


}

void Lista::showlist(Node *list){


Node *current =new Node();
current=list;

// Recorrer la lista y mostrar los elementos


while(current!=NULL){
cout<<current->value<<"->";
current=current->next;
}
}

void Lista::searchlist(Node *list, int n){


bool band=false;
Node *current=new Node();
current=list;

// Buscar el elemento en la lista


while((current!=NULL)&&(current->value<=n)){
if(current->value==n){
band=true;
}
current=current->next;
}

// Imprimir el resultado de la búsqueda


if(band==true){
cout<<"Elemento "<<n<<" fue encontrado en la lista"<<endl;
}
else{
cout<<"Elemento "<<n<<" NO fue encontrado en la lista"<<endl;
}
}

void Lista::suprlist(Node*&list,int n){


// Verificar si la lista no está vacía
if(list!=NULL){
Node *aux_delete;
Node *former=NULL;

// Recorrer la lista y encontrar el nodo a eliminar


while((aux_delete!=NULL)&&(aux_delete->value!=n)){
former=aux_delete;
aux_delete=aux_delete->next;
}

// Si el elemento no ha sido encontrado


if(aux_delete==NULL){
cout<<"Elemento no ha sido encontrado"<<endl;
}

Manual 55
Algoritmos y Estructura de Datos

// Si el primer elemento es el que se va a eliminar


else if(former==NULL){
list=list->next;
delete aux_delete;
}

// Si el elemento está en la lista pero no es el primer elemento


else{
former->next=aux_delete->next;
delete aux_delete;
}
}
}

void Lista::deletelist(Node *&list, int &n){


Node *aux=list;
n=aux->value;
list=aux->next;
delete aux;
}

void Lista::assess(Node*list){
Node *current =new Node();
Node *aux =new Node();
current=list;

cout<<"El menor valor de la lista es "<<current->value<<endl;

// Encontrar el mayor valor de la lista


while(current!=NULL){
current=current->next;
if(current->next==NULL){
cout<<"El mayor valor de la lista es "<<current->value<<endl;
}
}
}

int main()
{
Lista L;
Lista::Node *list=NULL;
int value, op=0;

do{
system("CLS");

// Mostrar el menú y leer la opción del usuario


cout<<endl<<"\tMANU:"<<endl<<"1) Insertar elemento en la lista"<<endl<<"2) Mostrar
los elementos de la lista"<<endl<<"3) Buscar un elemento en la lista"<<endl<<"4) Eliminar
un elemento de la lista"<<endl<<"5) Dejar vacía la lista"
<<endl<<"6) Determinar el mayor y menos numero de la lista"<<endl<<"7) Salir"<<endl;

cin>>op;

switch(op){
case 1:{
cout<<"Digite el valor: "<<endl;
cin>>value;
L.insertlist(list,value);
break;

Manual 56
Algoritmos y Estructura de Datos

}
case 2:{
L.showlist(list);
break;
}
case 3:{
cout<<"\nDigite un valor a buscar"<<endl;
cin>>value;
L.searchlist(list,value);
cout<<"\n";
break;
}
case 4:{
cout<<"\nDigite un valor a eliminar: ";
cin>>value;
L.suprlist(list,value);
cout<<"\n";
break;
}
case 5:{
while(list!=NULL){
L.deletelist(list,value);
cout<<value<<"->";
}
break;
}
case 6:{
L.assess(list);
break;
}
}

system("PAUSE");
}while(op!=7);

return 0;
}

La complejidad Big O de este código es la siguiente:


1. La función insertlist tiene una complejidad O(n), ya que en el peor caso debe recorrer la lista
hasta encontrar la posición correcta para insertar el nuevo elemento.
2. La función showlist tiene una complejidad O(n), ya que debe recorrer todos los elementos de
la lista para mostrarlos.
3. La función searchlist tiene una complejidad O(n), ya que debe recorrer la lista hasta encontrar
el elemento buscado o llegar al final de la lista.
4. La función suprlist tiene una complejidad O(n), ya que en el peor caso debe recorrer la lista
hasta encontrar el elemento a eliminar.
5. La función deletelist tiene una complejidad O(1), ya que simplemente elimina el primer
elemento de la lista.
6. La función assess tiene una complejidad O(n), ya que debe recorrer la lista para encontrar el
menor y el mayor valor.

Manual 57
Algoritmos y Estructura de Datos

En el programa principal, el bucle principal tiene una complejidad O(n), ya que el número de
iteraciones depende de la opción seleccionada por el usuario. En cada iteración, se llama a una función
que tiene una complejidad determinada.
En resumen, la complejidad total del programa depende de las funciones llamadas y de la cantidad de
operaciones que se realizan en cada una de ellas. En general, la complejidad es O(n) debido a los
bucles que recorren la lista.
2.3.2 LISTAS DOBLEMENTE ENLAZADAS
Cada nodo (elemento) contiene dos enlaces, uno a su nodo predecesor y el otro a su nodo sucesor. La
lista es eficiente tanto en recorrido directo (adelante) como en recorrido inverso (atrás)

#include <iostream>

using namespace std;

class Lista{
private:
public:
struct Node{
int data;
struct Node* prev;
struct Node* next;
};
Node* newNode(int data); // Función para crear un nuevo nodo
void append(Node** headRef, int data); // Función para agregar un nodo al final de la
lista
void deleteNode(Node** headRef, Node* delNode); // Función para eliminar un nodo de
la lista
void printlist(Node* head); // Función para imprimir la lista
struct Node* search(struct Node* head, int key); // Función para buscar un valor en
la lista
};

// FUNCIÓN PARA CREAR UN NUEVO NODO


Lista::Node* Lista::newNode(int data){
Node* node=(Node*)malloc(sizeof(Node));
node->data=data;
node->next=NULL;
node->prev=NULL;
return node;
}

// FUNCIÓN PARA AGREGAR UN NODO AL FINAL DE LA LISTA


void Lista::append(Lista::Node** headRef, int data){
Node* newNode=(Node*)malloc(sizeof(Node));
newNode->data=data;
newNode->next=NULL;
newNode->prev=NULL;
if(*headRef==NULL){
*headRef=newNode;
return;

Manual 58
Algoritmos y Estructura de Datos

}
Node* lastNode=*headRef;
while(lastNode->next!=NULL){
lastNode=lastNode->next;
}
lastNode->next=newNode;
newNode->prev=lastNode;
}

// FUNCIÓN PARA ELIMINAR UN NODO DE LA LISTA


void Lista::deleteNode(Node** headRef, Node* delNode){
// VERIFICA SI EL NODO ESTÁ VACÍO
if(*headRef==NULL||delNode==NULL){
return;
}
if(*headRef==delNode){
*headRef=delNode->next;
}
if(delNode->next!=NULL){
delNode->next->prev=delNode->prev;
}
if(delNode->prev!=NULL){
delNode->prev->next=delNode->next;
}
free(delNode);
}

// FUNCIÓN PARA IMPRIMIR LA LISTA


void Lista::printlist(Node* head){
while(head!=NULL){
cout<<head->data;
head=head->next;
}
cout<<endl;
}

// FUNCIÓN PARA BUSCAR UN VALOR EN LA LISTA


Lista::Node* Lista::search(struct Node* head, int key){
struct Node* current=head;
while(current!=NULL&&current->data!=key){
current=current->next;
}
return current;
}

int main(){
Lista V;
Lista::Node* head=NULL;
int s,d,b,value;
cout<<"Men\'u:"<<endl<<"1) Insertar dato"<<endl<<"2) Imprimir lista"<<endl<<"3) Elimi-
nar valor"<<endl<<"4) Buscar valor"<<endl<<"5) Salir"<<endl;
cin>>s;
while(s!=5){
switch(s){
case 1:{
cout<<"valor a insertar: ";
cin>>d;
V.append(&head,d);
break;
}

Manual 59
Algoritmos y Estructura de Datos

case 2:{
cout<<"Lista:"<<endl;
V.printlist(head);
break;
}
case 3:{
cout<<"Ingrese el valor a eliminar: ";
cin>>value;
Lista::Node* found_node=V.search(head,value);
if(found_node!=NULL){
// ELIMINAR EL NODO DE LA LISTA
cout<<"Eliminando nodo con el valor: "<<found_node->data<<endl;
V.deleteNode(&head,found_node);
}
break;
}
case 4:{
cout<<"Ingrese el valor a buscar: "<<endl;
cin>>b;
Lista::Node* found_node=V.search(head,b);
if(found_node!=NULL){
cout<<"El elemento: "<<found_node->data<<" se encuentra en la lista :D";
} else{
cout<<"El elemento no se encuentra en la lista D:";
}
break;
}
case 5:{
break;
}
default:
cout<<"Opcion no valida unu";
}
cout<<"Men\'u:"<<endl<<"1) Insertar dato"<<endl<<"2) Imprimir lista"<<endl<<"3) Elimi-
nar valor"<<endl<<"4) Buscar valor"<<endl<<"5) Salir"<<endl;
cin>>s;
}
return 0;
}

La complejidad Big O del código es la siguiente:


1. La función newNode tiene una complejidad O(1), ya que simplemente crea un nuevo nodo.
2. La función append tiene una complejidad O(n), ya que en el peor caso debe recorrer toda la
lista hasta llegar al último nodo para agregar un nuevo nodo al final.
3. La función deleteNode tiene una complejidad O(1), ya que elimina un nodo de la lista sin
necesidad de recorrerla.
4. La función printlist tiene una complejidad O(n), ya que debe recorrer toda la lista para
imprimir sus elementos.
5. La función search tiene una complejidad O(n), ya que en el peor caso debe recorrer toda la
lista hasta encontrar el nodo con el valor buscado.

Manual 60
Algoritmos y Estructura de Datos

En el programa principal, el bucle principal tiene una complejidad O(n), ya que el número de
iteraciones depende de la opción seleccionada por el usuario. En cada iteración, se llama a una función
que tiene una complejidad determinada.
En resumen, la complejidad total del programa depende de las funciones llamadas y de la cantidad de
operaciones que se realizan en cada una de ellas. En general, la complejidad es O(n) debido a los
bucles que recorren la lista.
2.3.3 LISTAS CIRCULARES SIMPLEMENTE ENLAZADAS
Una lista circular simplemente enlazada es aquella en el que el último elemento (cola) se enlaza al
primer elemento (cabeza) de tal modo que la lista puede ser recorrida de manera circular (en anillo).

#include <iostream>

using namespace std;

class Listacir{
private:
public:
typedef struct node{
int data;
struct node* next;
} node;
node *first=NULL; // Puntero al primer nodo de la lista
node *last=NULL; // Puntero al último nodo de la lista

void insertNode(); // Función para insertar un nodo en la lista


void printList(); // Función para imprimir la lista
void searchNode(); // Función para buscar un nodo en la lista
void modifyNode(); // Función para modificar el valor de un nodo
void deleteNode(); // Función para eliminar un nodo de la lista
};

void Listacir::insertNode(){
node* neww=(node*)malloc(sizeof(node));
cout<<"\nIngrese el nuevo dato que contendrá el nuevo nodo: ";
cin>>neww->data;

if(first==NULL){
// Si la lista está vacía, el nuevo nodo se convierte en el primer y último nodo
first=neww;
first->next=first;
last=first;
}
else{
// Si la lista no está vacía, se agrega el nuevo nodo al final y se ajustan los punteros
last->next=neww;
neww->next=first;
last=neww;

Manual 61
Algoritmos y Estructura de Datos

}
cout<<"\nNodo ingresado con éxito :3\n"<<endl;
}

void Listacir::printList(){
node* current=(node*)malloc(sizeof(node));
current=first;
if(first!=NULL){
// Si la lista no está vacía, se recorre circularmente y se imprime cada nodo
do{
cout<<"\n"<<current->data;
current=current->next;
}while(current!=first);
}
else{
cout<<"\nLa lista está vacía :("<<endl;
}
}

void Listacir::searchNode(){
node* current=(node*)malloc(sizeof(node));
current=first;
int searched=0, found=0;
cout<<"\nIngrese el nodo a buscar: ";
cin>>searched;
if(first!=NULL){
// Si la lista no está vacía, se recorre circularmente y se busca el nodo con el valor
especificado
do{
if(current->data==searched){
cout<<"\nNodo con el dato "<<current->data<<" encontrado"<<endl;
found=1;
}
current=current->next;
}while(current!=first&&found!=1);
if(found==0){
cout<<"\nNodo no encontrado :("<<endl;
}
}
else{
cout<<"\nLa lista está vacía"<<endl;
}
}

void Listacir::modifyNode(){
node* current=(node*)malloc(sizeof(node));
current=first;
int searched=0, found=0;
cout<<"\nIngrese el nodo a buscar: ";
cin>>searched;
if(first!=NULL){
// Si la lista no está vacía, se recorre circularmente y se busca el nodo con el valor
especificado
do{
if(current->data==searched){
cout<<"\nNodo con el dato "<<current->data<<" encontrado"<<endl;
cout<<"\nIngrese el nuevo dato para este nodo: "<<endl;
cin>>current->data;
found=1;
}

Manual 62
Algoritmos y Estructura de Datos

current=current->next;
}while(current!=first&&found!=1);
if(found==0){
cout<<"\nNodo no encontrado :("<<endl;
}
}
else{
cout<<"\nLa lista está vacía"<<endl;
}
}

void Listacir::deleteNode(){
node* current=(node*)malloc(sizeof(node));
current=first;
node* former=(node*)malloc(sizeof(node));
former=NULL;
int searched=0, found=0;
cout<<"\nIngrese el nodo a buscar para eliminar: ";
cin>>searched;
if(first!=NULL){
// Si la lista no está vacía, se recorre circularmente y se busca el nodo con el valor
especificado
do{
if(current->data==searched){
cout<<"\nNodo con el dato "<<current->data<<" encontrado"<<endl;
if(current==first){
// Si el nodo a eliminar es el primer nodo, se ajustan los punteros y se cambia el
primer nodo
first=first->next;
last->next=first;
}else if(current==last){
// Si el nodo a eliminar es el último nodo, se ajustan los punteros y se cambia el
último nodo
former->next=first;
last=former;
}else{
// Si el nodo a eliminar está en el medio de la lista, se ajustan los punteros
former->next=current->next;
}
cout<<"\nNodo eliminado :("<<endl;
found=1;
}
former=current;
current=current->next;
}while(current!=first&&found!=1);
if(found==0){
cout<<"\nNodo no encontrado :("<<endl;
}else{
free(former);
}
}else{
cout<<"\nLa lista está vacía"<<endl;
}
}

int main(){
Listacir LC;
int s=0;

do{

Manual 63
Algoritmos y Estructura de Datos

cout<<"\t Menú "<<endl<<"1) Insertar un elemento a la lista\n2) Imprimir la lista\n3)


Buscar un nodo\n4) Modificar un nodo\n5) Borrar un nodo\n6) Salir\n";
cin>>s;
switch(s){
case 1:{LC.insertNode();break;}
case 2:{LC.printList();break;}
case 3:{LC.searchNode();break;}
case 4:{LC.modifyNode();break;}
case 5:{LC.deleteNode();break;}
case 6:{break;}
default: cout<<"Opción no válida :(";
}
}while(s!=6);
return 0;
}

La complejidad Big O del código es la siguiente:


1. La función insertNode tiene una complejidad O(1), ya que simplemente crea un nuevo nodo
y realiza algunas operaciones de asignación.
2. La función printList tiene una complejidad O(n), donde n es el número de nodos en la lista
circular. Esto se debe a que se recorre la lista circular para imprimir todos los nodos.
3. La función searchNode tiene una complejidad O(n), donde n es el número de nodos en la lista
circular. Esto se debe a que se recorre la lista circular en busca de un nodo específico.
4. La función modifyNode tiene una complejidad O(n), donde n es el número de nodos en la
lista circular. Esto se debe a que se recorre la lista circular en busca de un nodo específico
para modificar su valor.
5. La función deleteNode tiene una complejidad O(n), donde n es el número de nodos en la lista
circular. Esto se debe a que se recorre la lista circular en busca de un nodo específico para
eliminarlo.
En el programa principal, el bucle principal tiene una complejidad O(n), donde n es el número de
veces que el usuario interactúa con el menú. En cada iteración, se llama a una función que tiene una
complejidad determinada.
En resumen, la complejidad total del programa depende del número de nodos en la lista circular y de
las operaciones realizadas en cada función. En general, la complejidad es O(n) debido a los bucles
que recorren la lista circular.
2.3.4 LISTAS CIRCULARES DOBLEMENTE ENLAZADA
Una lista circular doblemente enlazada es aquella en el que el último elemento (cola) se enlaza al
primer elemento (cabeza) y viceversa. Esta lista se puede recorrer de modo circular (en anillo) tanto
en dirección directa (adelante) como inversa (atrás).

Manual 64
Algoritmos y Estructura de Datos

#include <iostream>

using namespace std;

class Lista_c_de {
private:
public:
struct Node {
int data;
Node* prev;
Node* next;
};

void insert(int data); // Función para insertar un dato en la lista


void remove(int data); // Función para eliminar un dato de la lista
void modify(int oldData, int newData); // Función para modificar un dato en la lista
void search(int data); // Función para buscar un dato en la lista
void display(); // Función para imprimir la lista
};

Lista_c_de::Node* head = nullptr; // Puntero al primer nodo de la lista

void Lista_c_de::insert(int data) {


Node* newNode = new Node; // Se crea un nuevo nodo
newNode->data = data;
newNode->prev = nullptr;
newNode->next = nullptr;

if (head == nullptr) {
// Si la lista está vacía, el nuevo nodo se convierte en el primer nodo
head = newNode;
} else {
// Si la lista no está vacía, se agrega el nuevo nodo al final
Node* current = head;
while (current->next != nullptr) {
current = current->next;
}
current->next = newNode;
newNode->prev = current;
}
cout << "Dato " << data << " insertado correctamente." << endl;
}

void Lista_c_de::remove(int data) {


if (head == nullptr) {
cout << "La lista está vacía." << endl;
return;
}

Node* current = head;


while (current != nullptr && current->data != data) {
current = current->next;

Manual 65
Algoritmos y Estructura de Datos

if (current == nullptr) {
cout << "El dato " << data << " no se encontró en la lista." << endl;
return;
}

if (current->prev != nullptr) {
current->prev->next = current->next;
} else {
head = current->next;
}

if (current->next != nullptr) {
current->next->prev = current->prev;
}

delete current;
cout << "Dato " << data << " eliminado correctamente." << endl;
}

void Lista_c_de::modify(int oldData, int newData) {


if (head == nullptr) {
cout << "La lista está vacía." << endl;
return;
}

Node* current = head;


while (current != nullptr && current->data != oldData) {
current = current->next;
}

if (current == nullptr) {
cout << "El dato " << oldData << " no se encontró en la lista." << endl;
return;
}

current->data = newData;
cout << "Dato modificado correctamente." << endl;
}

void Lista_c_de::search(int data) {


if (head == nullptr) {
cout << "La lista está vacía." << endl;
return;
}

Node* current = head;


while (current != nullptr && current->data != data) {
current = current->next;
}

if (current == nullptr) {
cout << "El dato " << data << " no se encontró en la lista." << endl;
return;
}

cout << "Dato " << data << " encontrado en la lista." << endl;
}

Manual 66
Algoritmos y Estructura de Datos

void Lista_c_de::display() {
if (head == nullptr) {
cout << "La lista está vacía." << endl;
return;
}

Node* current = head;


cout << "Elementos de la lista: ";
while (current != nullptr) {
cout << current->data << " ";
current = current->next;
}
cout << endl;
}

int main() {
Lista_c_de L;
int data, s, change;
do {
cout << "\tMenu\n";
cout << "Option: ";
cout << "1) Ingresar un nuevo dato a la lista\n2) Eliminar un dato de la lista\n3) Modi-
ficar un dato de la lista\n4) Buscar un dato de la lista\n5) Imprimir la lista\n6) Salir\n";
cin >> s;
switch (s) {
case 1: {
cout << "Ingrese el dato a agregar: ";
cin >> data;
L.insert(data);
break;
}
case 2: {
cout << "Ingrese el dato a eliminar: ";
cin >> data;
L.remove(data);
break;
}
case 3: {
cout << "Ingrese el dato que quiere cambiar: ";
cin >> change;
cout << "\nIngrese el nuevo dato: ";
cin >> data;
L.modify(change, data);
break;
}
case 4: {
cout << "Ingrese el dato a buscar: ";
cin >> data;
L.search(data);
break;
}
case 5: {
L.display();
break;
}
case 6: {
cout << "Adioh";
break;
}
}

Manual 67
Algoritmos y Estructura de Datos

} while (s != 6);
return 0;
}

a complejidad Big O del programa es la siguiente:


1. La función insert tiene una complejidad O(1) en el mejor caso y O(n) en el peor caso. En el
mejor caso, cuando la lista está vacía, se realiza una operación de asignación constante. En el
peor caso, cuando se inserta al final de la lista, se recorre la lista para encontrar el último nodo
y luego se realiza una operación de asignación constante.
2. La función remove tiene una complejidad O(n), donde n es el número de nodos en la lista.
Esto se debe a que se busca el nodo con el dato dado recorriendo la lista, y luego se realizan
operaciones de asignación constantes para eliminar el nodo.
3. La función modify tiene una complejidad O(n), donde n es el número de nodos en la lista.
Esto se debe a que se busca el nodo con el dato dado recorriendo la lista, y luego se realiza
una operación de asignación constante para modificar el dato del nodo.
4. La función search tiene una complejidad O(n), donde n es el número de nodos en la lista. Esto
se debe a que se busca el nodo con el dato dado recorriendo la lista.
5. La función display tiene una complejidad O(n), donde n es el número de nodos en la lista.
Esto se debe a que se recorre la lista para imprimir todos los datos de los nodos.
En el programa principal, el bucle principal tiene una complejidad O(n), donde n es el número de
veces que el usuario interactúa con el menú. En cada iteración, se llama a una función que tiene una
complejidad determinada.
En resumen, la complejidad total del programa depende del número de nodos en la lista y de las
operaciones realizadas en cada función. En general, la complejidad es O(n) debido a los bucles que
recorren la lista.

3.0 ESTRUCTURAS DE DATOS NO LINEALES


3.1 ÁRBOLES
Un árbol consta de un conjunto finito de elementos, denominados nodos y un conjunto finito de líneas
dirigidas, denominadas ramas, que conectan a los nodos.

Manual 68
Algoritmos y Estructura de Datos

Nodo: Para árboles binarios, es decir que el nodo raíz, sólo tenga dos hijos.
PROPIEDADES DE UN ÁRBOL
Altura de un nodo: Se calcula de abajo hacia arriba del árbol
Profundidad de un nodo: Se calcula de arriba hacia abajo.
Nodos hermanos: Deben estar al mismo nivel y deben tener en común el mismo padre
Orden: Será la máxima cantidad de hijos que podrá tener cada nodo. Si te dicen que el árbol es de
orden dos, un nodo puede tener 0,1 ó 2 hijos

3.2 ÁRBOLES BINARIOS


Un árbol binario es un árbol de orden 2. Se conoce el nodo de la izquierda como hijo izquierdo y el
dono de la derecha como hijo derecho. Puede tener 0, 1 ó máximo 2 hijos.
• Un árbol binario es una estructura recursiva. Un árbol binario se divide en tres subconjuntos
disjuntos.

TIPOS DE ÁRBOLES BINARIOS

Manual 69
Algoritmos y Estructura de Datos

Árbol lleno: Es aquel en que todos sus nodos, excepto el del último nivel tiene dos hijos. Además en
el último nivel todos están exactos.

Árbol Completo: Hay una diferencia en el número de niveles de la parte izquierda y la parte derecha.
Además, otra característica es que el subárbol izquierdo casi siempre tiene más nodos que el subárbol
derecho. Podría ser que también en el subárbol derecho algún nodo sólo tenga un solo hijo.

Árbol Degenerado: Rompe con todos los paradigmas del árbol y si te fijas bien puede ser una lista
enlazada.

Manual 70
Algoritmos y Estructura de Datos

3.2.1 BÚSQUEDA BINARIA


#include <iostream>
#include <conio.h> // Biblioteca para la función getch() en Windows
#include <stdlib.h> // Biblioteca para la función system()

using namespace std;

struct Node{
int data;
Node *right;
Node *left;
Node *nothing;
};

// Prototipos de funciones
Node *createnode(int, Node *);
void insertnode(Node *&, int, Node *);
void menu();
void showtree(Node *, int);
bool search(Node *, int);
void preSort(Node *);
void inSort(Node *);
void pozole(Node *);
void deleteNode(Node *, int);
void deletee(Node *);
Node *min(Node *);
Node *tree = NULL;

int main(){
menu();
return 0;
}

// Crea un nuevo nodo con los datos proporcionados y retorna el puntero al nodo
creado

Manual 71
Algoritmos y Estructura de Datos

Node *createnode(int n, Node *nothing){


Node *nuevo_Node = new Node();

nuevo_Node->data = n;
nuevo_Node->right = NULL;
nuevo_Node->left = NULL;
nuevo_Node->nothing = nothing;

return nuevo_Node;
}

// Inserta un nodo con el dato dado en el árbol


void insertnode(Node *&tree, int n, Node *nothing){
if(tree == NULL){
Node *nuevo_Node = createnode(n, nothing);
tree = nuevo_Node;
}else{
int valorRaiz = tree->data;

if(n < valorRaiz){


insertnode(tree->left, n, tree);
}else{
insertnode(tree->right, n, tree);
}
}
}

// Función que muestra el menú y realiza las operaciones seleccionadas por el usuario
void menu(){
int data, opcion, contador=0;
do{
cout<<"\t: MENU: "<<endl;
cout<<"1) Insertar nuevo Node\n2) Mostrar el tree completo\n3) Buscar un ele-
mento del arbol\n4) Pre Ordenar el arbol\n5) In Ordenar el arbol\n6) Pos Ordenar el
arbol\n";
cout<<"Opcion: ";
cin>>opcion;

switch(opcion){
case 1:{
cout<<"\nDigite un numero: ";
cin>>data;
insertnode(tree, data, NULL);
cout<<"\n";
system("pause"); // Pausa la ejecución hasta que el usuario presione una tecla
break;
}
case 2:{
cout<<"\nMostrando el tree completo:\n\n";
showtree(tree, contador);
cout<<"\n";
system("pause");
break;
}
case 3:{
cout<<"\nDigite el elemento a buscar: ";
cin>>data;
if(search(tree, data) == true){
cout<<"\nElemento "<<data<<" ha sido encontrado :D";
}else{

Manual 72
Algoritmos y Estructura de Datos

cout<<"\nElemento no encontrado D:";


}
system("pause");
break;
}
case 4:{
cout<<"\nRecorrido en Pre Orden\n";
preSort(tree);
cout<<"\n\n";
system("pause");
break;
}
case 5:{
cout<<"\nRecorrido en In Orden del arbol\n";
inSort(tree);
cout<<"\n\n";
system("pause");
break;
}
case 6:{
cout<<"\nRecorrido en Pos Orden del arbol\n";
pozole(tree);
cout<<"\n\n";
system("pause");
break;
}
}
system("cls"); // Limpia la pantalla de la consola
}while(opcion != 5); // El bucle se repite hasta que se seleccione la opción 5 (salir)
}

// Muestra el árbol completo en la consola, utilizando espacios para representar la es-


tructura jerárquica
void showtree(Node *tree, int cont){
if(tree == NULL){
return;
}else{
showtree(tree->right, cont+1);
for(int i=0; i<cont; i++){
cout<<" ";
}
cout<<tree->data<<endl;
showtree(tree->left, cont+1);
}
}

// Busca un elemento en el árbol y devuelve verdadero si se encuentra, falso en caso


contrario
bool search(Node *tree, int n){
if(tree == NULL){
return false;
}else if(tree->data == n){
return true;
}else if(n < tree->data){
return search(tree->left, n);
}else{
return search(tree->right, n);
}
}

Manual 73
Algoritmos y Estructura de Datos

// Realiza el recorrido en preorden del árbol (Raíz-Izquierda-Derecha) e imprime los


datos de cada nodo
void preSort(Node *tree){
if(tree == NULL){
return;
}else{
cout<<tree->data<<" - ";
preSort(tree->left);
preSort(tree->right);
}
}

// Realiza el recorrido en inorden del árbol (Izquierda-Raíz-Derecha) e imprime los da-


tos de cada nodo
void inSort(Node *tree){
if(tree == NULL){
return;
}
inSort(tree->left);
cout<<tree->data<<" - ";
inSort(tree->right);
}

// Realiza el recorrido en posorden del árbol (Izquierda-Derecha-Raíz) e imprime los


datos de cada nodo
void pozole(Node *tree){
if(tree == NULL){
return;
}
preSort(tree->left);
preSort(tree->right);
cout<<tree->data<<" - ";
}

// Elimina un nodo con el valor dado del árbol


void deleteNode(Node *, int n){
if(tree == NULL){
return;
}else if(n < tree->data){
deleteNode(tree->left, n); // Busca por la izquierda
}else if(n > tree->data){
deleteNode(tree->right, n); // Busca por la derecha
}else{ // Ya encontró el valor
deletee(tree);
}
}

// Elimina un nodo del árbol y realiza los ajustes necesarios en la estructura del árbol
void deletee(Node *supr){
if(supr->left && supr->right){ // El nodo tiene hijos izquierdos y derechos
Node *less = min(supr->right); // Busca el nodo más pequeño en el subárbol derecho
supr->data = less->data; // Reemplaza los datos del nodo actual con los datos del
nodo más pequeño
deletee(less);
}
}

// Busca y retorna el nodo más pequeño en el subárbol dado


Node *min(Node *tree){
if(tree == NULL){

Manual 74
Algoritmos y Estructura de Datos

return NULL;
}
if(tree->left){ // Si tiene hijo izquierdo
return min(tree->left); // Buscar el nodo más izquierdo posible
}else{ // Nodo sin hijo izquierdo
return tree; // Retorna el mismo nodo
}
}

La complejidad Big O del programa se puede analizar por bloques de código:


1. En la función insertnode:
• El peor caso ocurre cuando el árbol está completamente desbalanceado, lo que resulta
en un árbol lineal. En este caso, la inserción tiene una complejidad de O(n), donde n
es el número de nodos en el árbol.
2. En la función showtree:
• El recorrido del árbol para mostrarlo completo se realiza mediante un recorrido en
orden (inorden), lo que implica visitar todos los nodos una vez. Por lo tanto, la
complejidad es O(n), donde n es el número de nodos en el árbol.
3. En la función search:
• La búsqueda en un árbol binario de búsqueda tiene una complejidad en el peor caso
de O(h), donde h es la altura del árbol. En el peor escenario, cuando el árbol está
desbalanceado, la altura puede ser n (número de nodos en el árbol). Sin embargo, en
un árbol balanceado, la altura es log(n), lo que resulta en una complejidad de
O(log(n)).
4. En las funciones de recorrido (preSort, inSort, pozole):
• En cada función, se visita cada nodo una vez, por lo que la complejidad es O(n), donde
n es el número de nodos en el árbol.
5. En las funciones deleteNode y deletee:
• La complejidad de estas funciones depende de la implementación específica del
algoritmo de eliminación en el árbol. En el peor caso, cuando el árbol está
desbalanceado y se elimina un nodo, la complejidad puede ser O(n), donde n es el
número de nodos en el árbol. Sin embargo, en un árbol balanceado, la complejidad
puede ser O(log(n)).
En general, la complejidad Big O del programa se puede considerar como O(n), donde n es el número
de nodos en el árbol. Sin embargo, en escenarios específicos, la complejidad puede variar según la
estructura y el equilibrio del árbol.
3.2.2 ÁRBOL ROJO NEGRO
Los árboles rojo-negro son un subtipo de árbol binario de búsqueda en el que cada nodo tiene un
atributo de color cuyo valor es rojo o negro.

Manual 75
Algoritmos y Estructura de Datos

PROPIEDADES:
• Un nodo es Rojo o Negro.
• La raíz siempre es negra.
• Un nodo Rojo siempre tendrá hijos negros (Un apuntador Null se tomará en consideración
para hacer referencia a un nodo Negro).
• El número de nodos negros es el mismo en cualquier camino que vaya de la raíz a la hoja.
EJEMPLO

Las rotaciones son necesarias para que cumpla las propiedades de un árbol rojo-negro, ya que en los
casos como inserción y eliminación, al final hay ocasiones que se necesita restructurar el árbol, por
lo cual existen la rotación:
• La Rotación izquierda.
• La Rotación derecha.
Es la misma rotación que ocupan los árboles binarios de búsqueda.
#include <stdio.h>
#include <stdlib.h>
#include <iostream>

using namespace std;

// Definición de los colores de los nodos


typedef enum Color {
ROJO,
NEGRO
} Color;

// Definición de la estructura de un nodo del árbol


typedef struct Nodo {
int dato;

Manual 76
Algoritmos y Estructura de Datos

Color color;
struct Nodo* izquierdo;
struct Nodo* derecho;
struct Nodo* padre;
} Nodo;

// Prototipos de funciones
Nodo* crearNodo(int dato);
Nodo* insertar(Nodo* raiz, int dato);
void arreglarInsercion(Nodo** raiz, Nodo* nodo);
void rotacionIzquierda(Nodo** raiz, Nodo* nodo);
void rotacionDerecha(Nodo** raiz, Nodo* nodo);
void imprimirArbol(Nodo* raiz, int cont);

// Función para crear un nuevo nodo


Nodo* crearNodo(int dato) {
Nodo* nodo = (Nodo*)malloc(sizeof(Nodo));
nodo->dato = dato;
nodo->color = ROJO;
nodo->izquierdo = NULL;
nodo->derecho = NULL;
nodo->padre = NULL;
return nodo;
}

// Función para insertar un nodo en el árbol


Nodo* insertar(Nodo* raiz, int dato) {
// Crear un nuevo nodo
Nodo* nodo = crearNodo(dato);

// Insertar el nodo como en un árbol binario de búsqueda


Nodo* padre = NULL;
Nodo* actual = raiz;

while (actual != NULL) {


padre = actual;
if (nodo->dato < actual->dato)
actual = actual->izquierdo;
else
actual = actual->derecho;
}

nodo->padre = padre;

if (padre == NULL) {
raiz = nodo;
} else if (nodo->dato < padre->dato) {
padre->izquierdo = nodo;
} else {
padre->derecho = nodo;
}

// Arreglar el árbol después de la inserción


arreglarInsercion(&raiz, nodo);

// Asegurar que la raíz sea negra


while (raiz->padre != NULL)
raiz = raiz->padre;
raiz->color = NEGRO;

Manual 77
Algoritmos y Estructura de Datos

return raiz;
}

// Función para arreglar el árbol después de una inserción


void arreglarInsercion(Nodo** raiz, Nodo* nodo) {
while (nodo->padre != NULL && nodo->padre->color == ROJO) {
Nodo* abuelo = nodo->padre->padre;

if (nodo->padre == abuelo->izquierdo) {
Nodo* tio = abuelo->derecho;

if (tio != NULL && tio->color == ROJO) {


nodo->padre->color = NEGRO;
tio->color = NEGRO;
abuelo->color = ROJO;
nodo = abuelo;
} else {
if (nodo == nodo->padre->derecho) {
nodo = nodo->padre;
rotacionIzquierda(raiz, nodo);
}

nodo->padre->color = NEGRO;
abuelo->color;
nodo->padre->color = NEGRO;
abuelo->color = ROJO;
rotacionDerecha(raiz, abuelo);
}
} else {
Nodo* tio = abuelo->izquierdo;

if (tio != NULL && tio->color == ROJO) {


nodo->padre->color = NEGRO;
tio->color = NEGRO;
abuelo->color = ROJO;
nodo = abuelo;
} else {
if (nodo == nodo->padre->izquierdo) {
nodo = nodo->padre;
rotacionDerecha(raiz, nodo);
}

nodo->padre->color = NEGRO;
abuelo->color = ROJO;
rotacionIzquierda(raiz, abuelo);
}
}
}
}

// Función para realizar una rotación a la izquierda


void rotacionIzquierda(Nodo** raiz, Nodo* nodo) {
Nodo* derecha = nodo->derecho;
nodo->derecho = derecha->izquierdo;

if (derecha->izquierdo != NULL)
derecha->izquierdo->padre = nodo;

derecha->padre = nodo->padre;

Manual 78
Algoritmos y Estructura de Datos

if (nodo->padre == NULL)
(*raiz) = derecha;
else if (nodo == nodo->padre->izquierdo)
nodo->padre->izquierdo = derecha;
else
nodo->padre->derecho = derecha;

derecha->izquierdo = nodo;
nodo->padre = derecha;
}

// Función para realizar una rotación a la derecha


void rotacionDerecha(Nodo** raiz, Nodo* nodo) {
Nodo* izquierda = nodo->izquierdo;
nodo->izquierdo = izquierda->derecho;

if (izquierda->derecho != NULL)
izquierda->derecho->padre = nodo;

izquierda->padre = nodo->padre;

if (nodo->padre == NULL)
(*raiz) = izquierda;
else if (nodo == nodo->padre->izquierdo)
nodo->padre->izquierdo = izquierda;
else
nodo->padre->derecho = izquierda;

izquierda->derecho = nodo;
nodo->padre = izquierda;
}

// Función para imprimir el árbol en orden


void imprimirArbol(Nodo* raiz, int cont) {
if (raiz == NULL){
return;
}else{
imprimirArbol(raiz->derecho,cont+1);
for(int i=0;i<cont;i++){
printf(" ");
}
printf("%d",raiz->dato);
printf("\n");
imprimirArbol(raiz->izquierdo,cont+1);
}
}

// Función principal
int main() {
Nodo* raiz = NULL;
int cont=0;
// Insertar nodos en el árbol
raiz = insertar(raiz, 14);
raiz = insertar(raiz, 4);
raiz = insertar(raiz, 15);
raiz = insertar(raiz, 16);
raiz = insertar(raiz, 2);
raiz = insertar(raiz, 8);
raiz = insertar(raiz, 9);
// Imprimir el árbol en orden

Manual 79
Algoritmos y Estructura de Datos

printf("Árbol: \n");
imprimirArbol(raiz,cont);

return 0;
}

La complejidad Big O de este código depende de las operaciones realizadas en las funciones
principales: insertar y arreglarInsercion. Analicemos cada una de ellas:
1. insertar: Esta función realiza una inserción en un árbol binario de búsqueda. La complejidad
de la inserción en un árbol binario de búsqueda promedio es O(log n), donde n es el número
de nodos en el árbol. Sin embargo, en el peor caso, si el árbol está completamente
desbalanceado, la complejidad de la inserción puede ser O(n), donde n es el número de nodos
en el árbol. Esto ocurre si los nodos se insertan en orden ascendente o descendente.
2. arreglarInsercion: Esta función realiza ajustes en el árbol rojinegro después de una inserción.
La cantidad de ajustes necesarios depende de la altura del árbol y de la posición del nodo
insertado. En el peor caso, cuando se realiza una rotación en cada nivel del árbol hasta la raíz,
la complejidad es O(log n), donde n es el número de nodos en el árbol. En promedio, la
complejidad es menor debido a las características del árbol rojinegro, pero aún se considera
O(log n).
Por lo tanto, la complejidad Big O general del código es O(log n) en el caso promedio y O(n) en el
peor caso, donde n es el número de nodos en el árbol.

Manual 80

También podría gustarte