Algoritmos y Estructura de Datos
Algoritmos y Estructura de Datos
Algoritmos y Estructura de Datos
Algoritmos y
Estructura
de Datos
2023
Cuadernillo
Manual 1
Algoritmos y Estructura de Datos
Contenido
INTRODUCCIÓN ............................................................................................................................... 5
1.1.4 PSEUDOCÓDIGO.............................................................................................................. 6
Manual 2
Algoritmos y Estructura de Datos
2.2 COLAS..................................................................................................................................... 45
Manual 3
Algoritmos y Estructura de Datos
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
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
Manual 8
Algoritmos y Estructura de Datos
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
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.
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.
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
Manual 12
Algoritmos y Estructura de Datos
}
}
// 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>
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
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
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.
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.
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
}
}
Manual 16
Algoritmos y Estructura de Datos
j = medio + 1;
// Copia los elementos del arreglo auxiliar de vuelta al arreglo original x[]
for (k = izquierda; k <= derecha; k++) {
x[k] = b[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;
}
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
}
}
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)
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
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
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;
}
}
}
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
}
Manual 24
Algoritmos y Estructura de Datos
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
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
}
}
}
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)
Manual 27
Algoritmos y Estructura de Datos
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
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;
}
}
}
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
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
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();
Manual 34
Algoritmos y Estructura de Datos
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;
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;
Manual 36
Algoritmos y Estructura de Datos
printf("%d\n", best_path[0]);
}
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();
Manual 37
Algoritmos y Estructura de Datos
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;
}
Push Pop
(Meter, poner o apilar) (Quitar, sacar o desapilar)
Se inserta un elemento a la pila Se elimina un elemento a la pila
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
return 0;
}
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
Manual 41
Algoritmos y Estructura de Datos
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>
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;
}
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
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.
Manual 46
Algoritmos y Estructura de Datos
void Colauwu::dinamic() {
Nodo *start = NULL; // Inicio de la cola
Nodo *end = NULL; // Fin de la cola
int dato;
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
}
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;
}
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
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
void Colauwu::dinamic() {
Nodo *start = NULL; // Inicio de la cola
Nodo *end = NULL; // Fin de la cola
int dato;
Manual 51
Algoritmos y Estructura de Datos
Manual 52
Algoritmos y Estructura de Datos
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
#include <iostream>
#include <conio.h>
class Lista{
private:
public:
struct Node{
int value;
Node *next;
};
// FUNCIONES PROTOTIPO
Manual 54
Algoritmos y Estructura de Datos
else{
aux2->next=new_node;
}
Manual 55
Algoritmos y Estructura de Datos
void Lista::assess(Node*list){
Node *current =new Node();
Node *aux =new Node();
current=list;
int main()
{
Lista L;
Lista::Node *list=NULL;
int value, op=0;
do{
system("CLS");
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;
}
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>
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
};
Manual 58
Algoritmos y Estructura de Datos
}
Node* lastNode=*headRef;
while(lastNode->next!=NULL){
lastNode=lastNode->next;
}
lastNode->next=newNode;
newNode->prev=lastNode;
}
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;
}
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>
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 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
Manual 64
Algoritmos y Estructura de Datos
#include <iostream>
class Lista_c_de {
private:
public:
struct Node {
int data;
Node* prev;
Node* next;
};
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;
}
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;
}
if (current == nullptr) {
cout << "El dato " << oldData << " no se encontró en la lista." << endl;
return;
}
current->data = newData;
cout << "Dato modificado correctamente." << endl;
}
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;
}
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;
}
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
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
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
nuevo_Node->data = n;
nuevo_Node->right = NULL;
nuevo_Node->left = NULL;
nuevo_Node->nothing = nothing;
return nuevo_Node;
}
// 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
Manual 73
Algoritmos y Estructura de Datos
// 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);
}
}
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
}
}
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>
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);
nodo->padre = padre;
if (padre == NULL) {
raiz = nodo;
} else if (nodo->dato < padre->dato) {
padre->izquierdo = nodo;
} else {
padre->derecho = nodo;
}
Manual 77
Algoritmos y Estructura de Datos
return raiz;
}
if (nodo->padre == abuelo->izquierdo) {
Nodo* tio = abuelo->derecho;
nodo->padre->color = NEGRO;
abuelo->color;
nodo->padre->color = NEGRO;
abuelo->color = ROJO;
rotacionDerecha(raiz, abuelo);
}
} else {
Nodo* tio = abuelo->izquierdo;
nodo->padre->color = NEGRO;
abuelo->color = ROJO;
rotacionIzquierda(raiz, abuelo);
}
}
}
}
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;
}
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 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