Está en la página 1de 14

ESTRUCTURAS DE DATOS I

CLASE DEL 7 DE AGOSTO DE 2023


Tipos de datos. Pueden ser simples o compuestos (o estructurados).
Simples: numéricos (enteros, reales (o de coma flotante: de precisión simple o doble)), lógicos (o booleanos),
alfanuméricos (caracteres, cadenas).
Tipos de datos compuestos.
Pueden ser homogéneos o heterogéneos.
Homogéneos: arreglos (cada elemento de un arreglo es del mismo tipo).
Heterogéneos: registros (se componen de campos, que pueden ser de distinto tipo).
Pueden ser estáticos o dinámicos. Dependiendo del tipo de memoria que utilicen.
Partes de la memoria de un programa:
Datos (memoria estática): pila (stack): tiempo de compilación.
int c[500];
Código: para ejecutar un programa, el código también se guarda en la RAM.
Datos (memoria dinámica): montículo (heap): tiempo de ejecución.
int *p = new int[n];
Pueden ser lineales o no lineales.
Lineales: listas, pilas y colas.
No lineales: árboles y grafos.
Abstracción: capacidad de manejar objetos y situaciones concentrándonos solo en la esencia de los mismos.
Tipo Abstracto de Datos (TAD): constituyen una forma de generalizar y encapsular los aspectos más importantes de la
información.

Pila: estructura lineal de tipo LIFO (Last Input First Output: el último en entrar es el primero en salir).
Pila: capacidad, tope, elementos; ver si está vacía, ver si está llena, apilar, desapilar, ver el elemento que está en el
tope.
Ejemplo de aplicación de las pilas: evaluación o verificación de expresiones posfijas.
Cuando en la expresión encuentro una variable o una constante numérica, la coloco en la pila.
Cuando encuentro un operador, ya que estamos hablando de operadores binarios,
retiro los dos últimos elementos de la pila para hacer la operación
(el primer elemento retirado sería el segundo operando)
Si hay menos de dos elementos en la pila, la expresión es inválida.
El resultado de la operación se coloca en la pila.
Al final, la pila debe estar vacía. Si no lo está, la expresión es inválida.
“a b c + d e - * f / +”:
“a b c + - d - * e f +”: expresión inválida
“a b c + - d e f / +”: expresión inválida

CLASE DEL 11 DE SEPTIEMBRE DE 2023


Estructuras de datos lineales: listas, pilas y colas.
Memoria dinámica y apuntadores, paso por referencia.
Listas enlazadas.
Conceptos de Programación Orientada a Objetos:
Encapsupación (atributos y métodos), ocultación de información, paso de mensajes (uso de objetos), constructores y
destructores.
Plantillas
Métodos de ordenamiento: ordenamiento por inserción.
PENDIENTE:
Listas: doblemente enlazadas, circulares, multilistas.
Recursividad.
Complejidad algorítmica.
Técnica de diseño de algoritmos.
Manejo de excepciones.
RESOLUCIÓN DEL PARCIAL
// PRUEBA
bool verificarSignosDeAgrupacion(Cola<char> *formulaPtr) {
while(!formulaPtr->estaVacia()) {
char caracter = formulaPtr->desencolar();

}
}

// VERSIÓN 1
bool verificarSignosDeAgrupacion(Cola<char> *formulaPtr) {
Pila<char> pila;

while(!formulaPtr->estaVacia()) {
char caracter = formulaPtr->desencolar();

if(caracter == ‘(’ || caracter == ‘[’ || caracter == ‘{’) {


pila.apilar(caracter);
}
else if(caracter == ‘)’ || caracter == ‘]’ || caracter == ‘}’) {
if(pila.estaVacia()) {
return false;
}

char signoDeApertura = pila.desapilar();

if((caracter == ‘)’ && signoDeApertura != ‘(’) ||


(caracter == ‘]’ && signoDeApertura != ‘[’) || (caracter == ‘}’ && signoDeApertura != ‘{’)) {
return false;
}
}
}

return pila.estaVacia();
}

// VERSIÓN 1
bool sonSignosDeAgrupacionCorrespondientes(char apertura, char cierre) {
if((apertura == ‘(’ && cierre == ‘)’) || (apertura == ‘[’ && cierre == ‘]’) || (apertura == ‘{’ && cierre == ‘}’)) {
return true;
}
else {
return false;
}
}

// VERSIÓN 2
bool sonSignosDeAgrupacionCorrespondientes(char apertura, char cierre) {
return (apertura == ‘(’ && cierre == ‘)’) || (apertura == ‘[’ && cierre == ‘]’) || (apertura == ‘{’ && cierre == ‘}’)
}

// VERSIÓN 2, CON ERRORES


bool verificarSignosDeAgrupacion(Cola<char> *formulaPtr) {
bool formulaBalanceada = true;

Pila<char> pila;

while(formulaBalanceada && !formulaPtr->estaVacia()) {


char caracter = formulaPtr->desencolar();
if(caracter == ‘(’ || caracter == ‘[’ || caracter == ‘{’) {
pila.apilar(caracter);
}
else if(caracter == ‘)’ || caracter == ‘]’ || caracter == ‘}’) {
if(pila.estaVacia()) {
formulaBalanceada = false;
}

if(!sonSignosDeAgrupacionCorrespondientes(pila.desapilar(), caracter)) {
formulaBalanceada = false;
}
}
}

return formulaBalanceada && pila.estaVacia();


}

// VERSIÓN 2
bool sonSignosDeAgrupacionCorrespondientes(char apertura, char cierre) {
return (apertura == ‘(’ && cierre == ‘)’) || (apertura == ‘[’ && cierre == ‘]’) || (apertura == ‘{’ && cierre == ‘}’)
}

// VERSIÓN 3
bool verificarSignosDeAgrupacion(Cola<char> *formulaPtr) {
bool formulaBalanceada = true;

Pila<char> pila();

while(formulaBalanceada && !formulaPtr->estaVacia()) {


char caracter = formulaPtr->desencolar();

if(caracter == ‘(’ || caracter == ‘[’ || caracter == ‘{’) {


pila.apilar(caracter);
}
else if(caracter == ‘)’ || caracter == ‘]’ || caracter == ‘}’) {
if(pila.estaVacia()) {
formulaBalanceada = false;
}
else if(!sonSignosDeAgrupacionCorrespondientes(pila.desapilar(), caracter)) {
formulaBalanceada = false;
}
}
}

return formulaBalanceada && pila.estaVacia();


}

// VERSIÓN 4 (OK)
bool verificarSignosDeAgrupacion(Cola<char> *formulaPtr) {
bool formulaBalanceada = true;

Pila<char> pila();

while(formulaBalanceada && !formulaPtr->estaVacia()) {


char caracter = formulaPtr->desencolar();

if(caracter == ‘(’ || caracter == ‘[’ || caracter == ‘{’) {


pila.apilar(caracter);
}
else if(caracter == ‘)’ || caracter == ‘]’ || caracter == ‘}’) {
if(pila.estaVacia() || !sonSignosDeAgrupacionCorrespondientes(pila.desapilar(), caracter)) {
formulaBalanceada = false;
}
}
}

return formulaBalanceada && pila.estaVacia();


}
// VERSIÓN 5 (NO FUNCIONA)
bool verificarSignosDeAgrupacion(Cola<char> *formulaPtr) {
bool formulaBalanceada = true;

Pila<char> pila();

while(!formulaPtr->estaVacia()) {
char caracter = formulaPtr->desencolar();

if(caracter == ‘(’ || caracter == ‘[’ || caracter == ‘{’) {


pila.apilar(caracter);
}
else if(caracter == ‘)’ || caracter == ‘]’ || caracter == ‘}’) {
formulaBalanceada = pila.estaVacia() || !sonSignosDeAgrupacionCorrespondientes(pila.desapilar(), caracter);
}
}

return formulaBalanceada && pila.estaVacia();


}

ERRORES COMUNES
Usar variables sin declararlas e inicializarlas.
Usar variables fuera de alcance.
CLASE DEL 13 DE SEPTIEMBRE DE 2023
RECURSIVIDAD

Factorial de un número
5 !=5× 4 × 3 ×2 ×1=120
5 !=5× ( 4 × 3 ×2 ×1 )=5 × 4 !=120

Definición no recursiva (en programación, sería iterativa):

{
1 , n=0
n != n

∏ i ,n> 0
i=1
Definición recursiva:
n !=
{n× ( n−1
1 , n=0
) ! , n> 0

unsigned long int factorial(unsigned int numero) {


unsigned long int producto = 1;
for(int i = 1; i <= numero; i++) {
producto *= i;
}
return producto;
}

numero i i <= producto numero i i <= producto


numero numero
4 1 0 1
1 verdadero 1 1 falso
2 verdadero 2
3 verdadero 6
4 verdadero 24
5
n !=
{n× ( n−1
1 , n=0
) ! , n> 0

Una función recursiva se llama a sí misma.


Debe existir al menos un caso base: un momento en que la recursividad se detenga.

unsigned long int factorial(unsigned int numero) {


if(numero <= 1) {
return 1;
}

// pre: numero > 0.


return numero * factorial(numero – 1);
}

El código anterior es un ejemplo de aplicación de la técnica de diseño de algoritmos Divide y vencerás.


Específicamente, es una caso de simplificación, porque el problema se va reduciendo en otro más pequeño cada vez.

La recursividad funciona gracias a la pila de llamadas. Cada vez que se llama a la función recursiva, se coloca un nuevo
registro de aplicación en la pila de llamadas.

#include <iostream>
using namespace std;

int main() {
int n;
cin >> n;
cout << n << “! = ” << factorial(n) << endl;
return 0;
}

numero: 0 return 1
numero: 1 return 1*factorial(0) ⟹ return 1*1 ⟹ return 1
numero: 2 return 2*factorial(1) ⟹ return 2*1 ⟹ return 2
numero: 3 return 3*factorial(2) ⟹ return 3*2 ⟹ return 6
numero: 4 return 4*factorial(3) ⟹ return 4*6 ⟹ return 24
n:4 n! = 24

Fibonacci
Población de conejos suponiendo que los conejos nunca mueren, se reproducen a partir de los dos meses y siempre
tienen un par de conejos, macho y hembra.

fib ( n )=
{fib ( n−11 ,∧n=0∨ n=1
) + fib(n−2),∧n>1
unsigned long int fibonacci(unsigned int numero) {
if(numero <= 1) {
return 1;
}

return fibonacci(numero – 1) + fibonacci(numero - 2);


}

TAREAS

Construya una versión iterativa y eficiente del algoritmo de Fibonacci aplicando la técnica de diseño de algoritmos
Programación dinámica.

Construya una función recursiva que reciba como argumento un número entero no negativo y retorne la suma de las
cifras de dicho número.

CLASE DEL 20 DE SEPTIEMBRE DE 2023

unsigned long int fibonacci(unsigned int numero) {


if(numero <= 1) {
return 1;
}

return fibonacci(numero – 1) + fibonacci(numero - 2);


}

// VERSIÓN 1
// La complejidad espacial de este algoritmo es de orden lineal (o de orden n).
unsigned long int obtenerEnesimoTerminoFibonacci(unsigned int n) {
unsigned long *fib = new unsigned long[n + 1];

fib[0] = 1;
fib[1] = 1;

for(int i = 2; i <= n; i++) {


fib[i] = fib[i - 1] + fib[i - 2];
}

unsigned long f_n = fib[n];

delete [] fib;

return f_n;
}

En el main: cout << “fibonacci(5) = ” << obtenerEnesimoTerminoFibonacci(5) << endl;


// VERSIÓN 2
// La complejidad espacial de este algoritmo es de orden constante.
// TÉCNICA DE DISEÑO DE ALGORITMOS: Programación dinámica.
unsigned long int obtenerEnesimoTerminoFibonacci(unsigned int n) {
unsigned long f_0 = 1, f_1 = 1, f_2 = 1;

for(int i = 2; i <= n; i++) {


f_2 = f_1 + f_0;

f_0 = f_1;
f_1 = f_2;
}

return f_2;
}

Existen 10 clases de personas: las que saben binario y las que no.
Existen 3 clases de personas: las que saben contar y las que no.

// VERSIÓN 1
string convertirDecimal2cadenaBinaria(unsigned long int numero) {
if(numero < 2) {
return numero == 1 ? “1” : “0”;
}

return convertirDecimal2cadenaBinaria(numero/2) + (numero%2 != 0 ? “1” : “0”);


}

// VERSIÓN 2
string convertirDecimal2cadenaBinaria(unsigned long int numero) {
if(numero < 2) {
return (numero == 1) ? “1” : “0”;
}

string bitMenosSignificativo = (numero%2 != 0) ? “1” : “0”;


return convertirDecimal2cadenaBinaria(numero/2) + bitMenosSignificativo;
}

{
0 , n=0
1 ,n=1
binario ( n )= binario ()
n
2
+ 1 ,n> 1∧ 2∤ n

binario ()
n
2
+ 0 ,n> 1∧ 2∨n
TALLER
Construya una función recursiva que busque un valor en un vector y retorne la posición de la primera aparición de
dicho valor en el vector (-1 en caso de que no se encuentre). La función debe ser lo más eficiente posible.

En el main: cout << buscar(new int[] {-5, -2, 0, 4, 8, 12, 16, 21, 32, 40}, 10, 30);

// pre: para todo i < j: arreglo[i] <= arreglo[j]


int buscar(int arreglo[], int longitud, int valorBuscado) {
return buscar(arreglo, 0, longitud - 1, valorBuscado);
}

// FUNCIÓN RECURSIVA:
// pre: para todo i < j: arreglo[i] <= arreglo[j]
// pre: inicial <= final
// Esta función busca en el subarreglo que va desde inicial hasta final.
int buscar(int arreglo[], int inicial, int final, int valorBuscado) {

TAREA
Construya la versión iterativa de la función del taller.
// pre: para todo i < j: arreglo[i] <= arreglo[j]
int buscar(int arreglo[], int longitud, int valorBuscado) {

CLASE DEL 16 DE OCTUBRE DE 2023

Construya una clase en C++ que represente una lista de enteros sin datos repetidos y ordenada ascendentemente.
Implemente la clase como una lista simplemente enlazada.
TAREA: hacer funcionar el código.
TAREA: mejorar la clase para que funcione con cualquier tipo de dato. También incluya el manejo de excepciones.

class Nodo {
public:
int dato;
Nodo* siguiente;

Nodo(int dato) {
this.dato = dato;
this.siguiente = nullptr;
}
};

class ListaOrdenadaDeEnteros {
private:
Nodo *cabezaPtr;
bool permitirRepetidos;

public:
// TAREA: destructor, método eliminar, método mostrar, buscar.
ListaOrdenadaDeEnteros(bool permitirRepetidos);
~ListaOrdenadaDeEnteros();

void insertar(int dato);

};

ListaOrdenadaDeEnteros::ListaOrdenadaDeEnteros(bool permitirRepetidos) {
cabezaPtr = nullptr;
this.permitirRepetidos = permitirRepetidos;
}
// VERIFICAR QUE FUNCIONA PARA TODOS LOS CASOS: cuando la lista está vacía, cuando tiene un solo elemento,
cuando voy a insertar un elemento menor que el primero, cuando voy a insertar un elemento mayor que el último,
cuando voy a insertar un elemento que está entre el primero y el último, cuando voy a insertar elementos que ya
están en la lista y se permiten repetidos (igual al primero, igual al último, igual a un elemento intermedio) o cuando
no se permiten repetidos.
// TAREA: modificar el código para que los elementos repetidos queden en orden de inserción.
void ListaOrdenadaDeEnteros::insertar(int dato) {
// Creamos el nuevo nodo
Nodo *nuevoPtr = new Nodo(dato);

// Buscamos la posición donde va a quedar el nuevo nodo en la lista

Nodo *anteriorPtr = nullPtr, *actualPtr = cabezaPtr;

while(actualPtr != nullptr && actualPtr->dato < dato) {


anteriorPtr = actualPtr;
actualPtr = actualPtr->siguiente;
}

// Si no se permiten repetidos, validamos que eso no ocurra.


if(!permitirRepetidos && actualPtr != nullptr && actualPtr->dato == dato) {
// TAREA: lanzar una excepción y capturarla en el main.
}

// Realizamos la inserción

if(anteriorPtr == nullPtr) {
cabezaPtr = nuevoPtr;
nuevoPtr->siguiente = actualPtr;
}
else {
anteriorPtr->siguiente = nuevoPtr;
nuevoPtr->siguiente = actualPtr;
}
}

También podría gustarte