Está en la página 1de 22

NOTAS DEL CURSO:

ESTRUCTURA DE DATOS Y ALGORITMOS

Ramiro Torres

Semestre 2021-B
2
Contents

3 Conceptos Básicos de C++ 7


3.1 Punteros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
3.1.1 Operadores de dirección (&) e indirección (*) . . . . . . . . . . . . . . . . 7
3.2 Arreglos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
3.2.1 Arreglos Estáticos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
3.2.2 Arreglos Dinámicos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
3.3 Biblioteca de clases del contenedor (STL) . . . . . . . . . . . . . . . . . . . . . . 10
3.3.1 Vectores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
3.4 Clases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
3.5 Herencia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
3.6 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18

3
4 CONTENTS
Bibliografı́a

1. Introduction to Algorithms. Cormen, Leiserson, Rivest & Stein. MIT Press.


2. Data structure and algoritms in C++. Adam Drozdek.
3. Algorithms. S. Dasgupta, C. H. Papadimitriou, and U. V. Vazirani.
4. Combinatorial Optimization. Cook, Cunningham, Pulleyblank & Schrijver Wiley-Interscience.
5. The C++ Programming Language. Stroustrup. Addison-Wesley.
6. The C++ Standard Library: A Tutorial and Reference. Nicolai M. Jousuttis.Addison-
Wesley.
7. Aprenda C++ como si estuviera en primero. Garcı́a, Rodrı́guez, Sarriegui, Brazález &
González. Universidad de Navarra.

5
6 CONTENTS
Chapter 3

Conceptos Básicos de C++

3.1 Punteros
El valor de cada variable está almacenado en un lugar determinado de la memoria, caracterizado
por una dirección (que se suele expresar con un número hexadecimal). Gracias a los nombres de
las variables, el programador no se preocupe de la dirección de memoria donde están almacenados
sus datos. Sin embargo, en ciertas ocasiones es más útil trabajar con las direcciones que con los
propios nombres de las variables para incrementar la velocidad de los programas. El manejo de
direcciones se lo realiza mediante los punteros o apuntadores.
Ası́, un puntero es una variable que puede contener unicamente la dirección de otra variable
y se dice que un puntero apunta a una variable si su contenido es la dirección de dicha variable.
Un puntero se debe declarar de acuerdo con el tipo del dato al que apunta bajo la siguiente
sintaxis:

tipo_dato * nombre_puntero;

Ejemplo 1 Un puntero a una variable de tipo int se declara del siguiente modo:

int *direc;

es decir, el puntero direc podrá contener la dirección de cualquier variable entera.

3.1.1 Operadores de dirección (&) e indirección (*)


Un puntero es una verdadera variable, y por tanto puede cambiar de valor al que apunta. Para
ello se usan dos tipos de operadores:

• &: Extrae la dirección del objeto al que se aplica el operador

• *: Accede al valor depositado en la zona de memoria al puntero al que se aplica

El operador & no se puede aplicar en los siguientes casos:

• Las constantes y las expresiones no tienen dirección.

• No es posible cambiar la dirección de una variable.

• Los valores posibles para un puntero son las direcciones posibles de memoria.

7
8 CHAPTER 3. CONCEPTOS BÁSICOS DE C++

• Un puntero puede tener valor 0 (equivalente a la constante simbólica predefinida NULL)

Ası́, las siguientes sentencias son ilegales:

p = &34; // las constantes no tienen dirección


p = &(i+1); // las expresiones no tienen dirección
&i = p; // las direcciones no se pueden cambiar
p = 17654; // habrı́a que escribir p = (int *)17654;

Ejemplo 2 Se dispone de las siguientes declaraciones y sentencias,

int i, j, *p; // p es un puntero a int


p = &i; // p contiene la dirección de i
*p = 10; // i toma el valor 10
p = &j; // p contiene ahora la dirección de j
*p = -2; // j toma el valor -2

3.2 Arreglos
3.2.1 Arreglos Estáticos
Un array es un modo de manejar una gran cantidad de datos del mismo tipo bajo un mismo
nombre o identificador. Por ejemplo, mediante la sentencia:

double a[5];

se reserva espacio para 5 variables de tipo double. Las 5 variables se llaman a y se accede al valor
de cada una de ellas por medio de un subı́ndice. Un subı́ndice es una expresión entera escrita a
continuación del nombre entre corchetes [...]. La forma general de la declaración de un vector es
la siguiente:

tipo nombre[n];

Los elementos se numeran desde 0 hasta (n − 1). El tamaño de un vector puede definirse con
cualquier expresión constante entera.

Ejemplo 3
double a[10];
a[5] = 0.8;
a[9] = 30. * a[5];
a[0] = 3. * a[9] - a[5]/a[9];
a[3] = (a[0] + a[9])/a[3];

Las matrices se declaran de forma análoga, con corchetes independientes para cada subı́ndice.
La forma general de la declaración es:

tipo nombre[numero_filas][numero_columnas];

donde tanto las filas como las columnas se numeran también a partir de 0. La forma de ac-
ceder a los elementos de la matriz es utilizando su nombre, seguido de las expresiones enteras
correspondientes a los dos subı́ndices, entre corchetes.
3.2. ARREGLOS 9

3.2.2 Arreglos Dinámicos


Con los operadores new y delete el programador tiene entera libertad para decidir sobre la
creación o destrucción de sus variables cuando considere adecuado, permitiendo de este modo el
manejo dinámico de la memoria.
Una variable creada con el operador new dentro de cualquier bloque, perdura hasta que es
explı́citamente borrada con el operador delete. Se puede utilizar el operador new para crear
variables de cualquier tipo.
tipo_dato *nombre;
x=new tipo_dato;
Cuando una variable ya no es necesaria se destruye con el operador delete para poder utilizar la
memoria que estaba ocupando, mediante una instrucción del tipo:
delete nombre;

Ejemplo 4
double *x;
x=new double;
cin>>*x;
cout<<*x;
delete x;

Además, estos operadores pueden ser usados para la creación de arreglos. Se puede usar la
siguiente sintaxis:
tipo_dato *nombre;
x=new tipo_dato[n];
y se lo puede eliminar mediante el operador delete
delete[] nombre;
El siguiente ejemplo reserva memoria dinámicamente para un vector y una matriz de números
reales:
Ejemplo 5 #include <iostream>
int main()
{

//VECTORES
int n,;
double *vect;
// se reserva memoria para el vector
vect = new double[n];

// se inicializa todo el vector


for(i=0; i<n; i++)
vect[i]=i*i;
// se imprime la matriz
for(i=0; i<n; i++){
cout << vect[i]<< "\t";
10 CHAPTER 3. CONCEPTOS BÁSICOS DE C++

std::cout << "escritura completa";


}

//MATRICES

int nfil=4, ncol=3, i, j;


double **mat;
// se reserva memoria para el vector de punteros
mat = new double*[nfil];
// se reserva memoria para cada fila
for (i=0; i<nfil; i++)
mat[i] = new double[ncol];
// se inicializa toda la matriz
for(i=0; i<nfil; i++)
for(j=0; j<ncol; j++)
mat[i][j]=i+j;
// se imprime la matriz
for(i=0; i<nfil; i++){
for(j=0; j<ncol; j++)
std::cout << mat[i][j] << "\t";
std::cout << "\n";
}
// se libera memoria
for(i=0; i<nfil; i++)
// se borran las filas de la matriz
delete [] mat[i];
// se borra el vector de punteros
delete [] mat;
}

3.3 Biblioteca de clases del contenedor (STL)


La librerı́a estándar de plantillas, o STL (”Standar Template Library”), es un gran conjunto de
estructuras de datos y algoritmos que conforman una parte sustancial de la Librerı́a Estándar
C++. Mas precisamente, las librerı́as STL están construidas casi exclusivamente sobre dos
elementos ya existentes en el lenguaje: las clases y las plantillas (”Templates”). Una estructura
de datos se dice que es contenedora si puede contener instancias de otras estructuras de datos.

3.3.1 Vectores
Un vector contiene elementos almacenados como un arreglo. Se debe incluir la libreria:

#include < vector >


Este dispone de las siguientes caracterı́sticas
• Constructor : Crea un vector e inicializa las casillas en algún valor.
vector < int > v;
vector < int > v(n);
vector < int > v(n, valor);
3.4. CLASES 11

• Disponen de las siguientes funciones miembro:

1. resize(n): cambia el tamaño de un vector.


2. resize(n,val):cambia el tamaño del vector a n inicializado en val
v.resize(10);
v.resize(10,5);(crea un vector de 10 casillas e inicializa en 5)
3. Operators: comparación(==,!=), asignación(=) , acceso a los elementos [*]. Por
ejemplo: v[1]=4;
4. at: retorna el elemento ubicado en una casilla especifica:
v.at(5);
5. back():retorna el último elemento del vector.
6. front(): retorna la referencia al primer elemento del vector.
7. pop_back():
remueve el último elemento del vector
8. push_back():
adhiere un elemento al final del vector
9. clear():remueve todos los elementos del vector.
10. empty(): verdadero si el vector no tiene elementos.
11. size(): retorna el número de casillas del vector.
12. begin():retorna el primer elemento del vector.
13. end(): devuelve un iterador justo después del último elemento del vector
vector<int>::const_iterator it;
for(it = v1.begin(); it != v1.end(); ++it )
14. erase(punti,puntf ):remueve elementos del vector(usando punteros)
x.erase(x.begin()+9,x.end());
15. insert(punt,val): inserta elementos en el vector. Se necesita ubicaciones y no valores.
v2.insert( v2.end(), v1.begin(), v1.end() ); o v2.insert( v2.end(), 2);
16. swap(vector):cambia el contenido de un vector a otro.
x.swap(y);(x ahora esta vacio)

3.4 Clases
La Programación Orientada a Objetos permite realizar grandes programas mediante la unión de
elementos más simples que pueden ser diseñados y comprobados de manera independiente del
programa que van a usarlos. A estas piezas, módulos o componentes, que interactúan entre sı́
cuando se ejecuta un programa se les denomina objetos. Estos objetos contienen tanto datos
como las funciones que actúan sobre esos datos. Algunos de estos elementos representan entidades
del mundo real (matrices, personas, cuentas de banco, elementos mecánicos o eléctricos, ...) y
otros pueden ser componentes del ordenador.
La construcción de éstos objetos se realiza mediante la definición de clases. Una clase contiene
una completa y detallada descripción de la información y las funciones que contendrá cada objeto
de esa clase. En C++ las clases son verdaderos tipos de datos definidos por el usuario y pueden
ser utilizados de igual manera que los tipos de datos propios del C++, tales como int o float.
12 CHAPTER 3. CONCEPTOS BÁSICOS DE C++

Para proteger a las variables de modificaciones no deseadas se introduce el concepto de encap-


sulación, ocultamiento o abstracción de datos. Ası́, los miembros de una clase se pueden dividir
en públicos y privados:

• públicos: son aquellos a los que se puede acceder libremente desde fuera de la clase.

• privados: solamente pueden ser accedidos por los métodos de la propia clase.

Además, dentro de la clase es posible definir:

• Constructores y destructores de los objetos

• Funciones a través de las cuales se puede manipular datos de tipo privados.

• Definir operaciones sobre la nueva clase

En C++, una clase pueden ser definida bajo la siguiente sintaxis:

class Nombre_clase
{
private:
Declaración de variables;
public:
Declaración de variables;
Constructores;
Operadores;
Funciones;
};

Ejemplo 6 Crear una clase para almacenar los datos personales de un estudiante:

class alumno
{
private:
string Nombre;
string Apellido;
int edad;
int semestre;
double nota;
public:
//CONSTRUCTRES
alumno()
{
Nombre="S/N";
Apellido="S/N";
edad=-1;
semestre=0;
nota=0.0;
}
//OPERADORES
friend ostream& operator<<(ostream& os,alumno& al);
3.4. CLASES 13

friend istream& operator>>(istream& is,alumno& al);

//FUNCIONES
string Escribir_nombre()
{return Nombre;}
int Escribir_edad();
void Modificar_nota(double n)
{Nota=n;}

};

int alumno::Escribir_edad()
{
return edad;
}

A continuación presentamos una breve descripción de las principales componentes de una


clase en el lenguaje C++:

Constructores y destructores
Los constructores y destructores deben ser declaradas en la sección pública de la clase, por lo
que se podrán utilizar desde fuera de la misma sin ningún tipo de restricción. Los constructores
deben llevar el mismo nombre de la clase y no tienen valor de retorno, ni siquiera void.
Nombre_Clase(Lista de argumentos);
Los constructores se llaman de modo automático cada vez que se crea un objeto (en el caso
anterior, cada vez que se cree un objeto alumno) y su misión es que todas las variables miembro
estén siempre correctamente inicializadas.
Los destructores son funciones miembro especiales que sirven para eliminar un objeto. El de-
structor realizará procesos necesarios cuando un objeto termine su ámbito temporal, por ejemplo
liberando la memoria dinámica utilizada por dicho objeto o liberando recursos usados. La sintaxis
es la siguiente:

~ Nombre_Clase();

Cuando se define un destructor para una clase, éste es llamado automáticamente cuando se aban-
dona el ámbito en el que fue definido. Esto es ası́ salvo cuando el objeto fue creado dinámicamente
con el operador new, ya que en ese caso es necesario eliminarlo explı́citamente usando el operador
delete.

Funciones miembro
Estas funciones o métodos públicos constituyen la interfaz de la clase que garantiza el buen uso
de los objetos, manteniendo la coherencia de la información. Esto serı́a imposible si se accediera
libre e independientemente a cada variable miembro. Al usuario le es suficiente con saber cómo
comunicarse con un objeto, pero no tiene por qué conocer el funcionamiento interno del mismo.
Las funciones miembro permiten acceder a las variables privadas. Las funciones miembro
deben ser declaradas al interior de la clase y permiten el acceso a las variables privadas o pub-
licas directamente, sin necesidad de re-declararlas o pasarlas como argumento. A las funciones
miembro se accede mediante el operador punto (.) o flecha (− >) dependiendo del caso.
14 CHAPTER 3. CONCEPTOS BÁSICOS DE C++

Nombre_Clase.Nombre_Función();

Funciones amigas
Una función amiga de una clase es una función que no pertenece a la clase, pero que tiene permiso
para acceder a sus variables y funciones miembro privadas por medio de los operadores punto (.)
o flecha (− >), sin tener que recurrir a las funciones miembro públicas de la clase. Si una clase
se declara como amiga(friend) de otra, todas sus funciones miembro son amigas de esta segunda
clase. El carácter de amiga puede restringirse a funciones concretas, que pueden ser miembro de
alguna clase o pueden ser funciones generales que no pertenecen a ninguna clase.
Para declarar una función o una clase como amiga de otra clase, es necesario anteponer friend
a la declaración de la clase o función y dentro de la clase que debe autorizar el acceso a sus datos
privados. Esto se puede hacer de forma indiferente en la zona de los datos o en la de los datos
privados. Un ejemplo de declaración de una clase friend podrı́a ser el que sigue:

class Nombre
{
friend class Amiga;
friend tipo_retorno Funcion(argumentos);
};

Operadores
Los operadores de C++, al igual que las funciones, pueden ser sobrecargados. La sobrecarga de
operadores quiere decir que se pueden redefinir algunos de los operadores existentes en C++ para
que actúen de una manera determinada definida por el programador. Esto puede ser muy útil,
por ejemplo, para definir operaciones matemáticas con objetos tales como vectores, matrices,
grafos, etc. Ası́, sobrecargando adecuadamente los operadores suma (+) y asignación (=), se
puede llegar a sumar dos matrices con una sentencia tan simple como:

A = B + C;

Otra capacidad muy utilizada es la de sobrecargar los operadores de inserción y extracción en


los flujos de entrada y salida (>> y <<), de manera que puedan imprimir o leer estructuras o
clases complejas con una sentencia estándar.
Los operadores son análogos a las funciones amigas sustituyendo el nombre de la función
por la palabra operator seguida del carácter o caracteres del operador de C++ que se desea
sobrecargar. Al dar una nueva definición para los operadores de salida (<<), entrada(>>), suma
(+), resta (-), multiplicación (*), división (/), entre otros, acorde con la aritmética de la nueva
clase.
En la sobrecarga del operador de asignación (=) aparece el operador this. THIS trata de
un puntero que tiene asociado cada objeto y que apunta a si mismo. Ese puntero se usa para
acceder a sus miembros.
Por ejemplo, en el caso de la clase:

class complejo {
private:
// Datos miembro de la clase "pareja"
int a, b;
3.5. HERENCIA 15

public:
// Constructor
complejo(int a2, int b2);
// Funciones miembro
void Guarda(int a2, int b2);
friend complejo operator= (int a2, int b2);
};

void complejo::Guarda(int a2, int b2) {


this->a = a2;
this->b = b2;
}
complejo operator= (complejo x)
{
this->a = x.a;
this->b = x.b;
return *this;
}

3.5 Herencia
La herencia permite definir una clase modificando una o más clases ya existentes. Estas modifica-
ciones consisten habitualmente en añadir nuevas variables o funciones a una clase que previamente
construida. También se puede redefinir variables o funciones miembro ya existentes.

Figure 3.1: Herencia en C++

Las clases de las que se parte en el proceso de herencia se llaman clases base y la nueva
clases que se obtiene se llama clase derivada. Una clase derivada puede a su vez ser una clase
base de un nuevo proceso de derivación, iniciando de esta forma una jerarquización de las clases.
Un objeto de la clase derivada contiene todos los miembros de la clase base y todos los miembros
adicionales de la clase derivada.
La sintaxis general que permite declarar clases derivadas es la siguiente:

class clase_derivada:public / private base1, public / private base2


{
......
16 CHAPTER 3. CONCEPTOS BÁSICOS DE C++

};
Si no se especifica ninguno de los dos, por defecto se asume que es private. En el caso
de estructuras jerárquicas se debe permitir que las clases derivadas tengan acceso a las vari-
ables,funciones miembro de las clases base. Para ello, el acceso protected es definido adicional
a private y public. Los miembros definidos en protected permiten que los datos sean inaccesibles
desde el exterior de las clases, pero a la vez, sean accesibles desde las clases derivadas.
Al igual que en la sección anterior, al momento de declarar una clase derivada se debe definir
los respectivos constructores. El constructor de la clase base puede ser omitido en el caso de que
se tenga un constructor por defecto:
Clase_Derivada();
es decir, es llamado automáticamente. Por el contrario, en el caso en el que el constructor de la
clase base disponga de argumentos, al declarar el objeto de la clase derivada se ejecuta primero
el constructor de la clase base
Clase_Derivada(definicion argumentos):Clase_Base(llamado );

OBS 1 Clases y punteros: En C++ es posible trabajar con puntero que apuntan a clases.
Para hacer menos incómodo el trabajo con punteros a estructuras, C++ dispone del operador
flecha (− >) que se utiliza de esta forma:

ptr− > campo

que es equivalente a escribir


(∗ptr).campo

Veamos un ejemplo sencillo basado en la idea del punto anterior:


Ejemplo 7
#include <iostream>
#include <list>
#include <vector>
#include <cstdlib>

using namespace std;


const double INF=1e20;

class base
{
protected:
int a;
double b;
public:
base(int x,double y)
{
a=x;
b=y;
}
double Obt_b()
{
3.5. HERENCIA 17

return b;
}
};

class derivada_1: public base


{
protected:
double c;
string d;
public:
derivada_1(int z):base(z,7.8)
{
c=z;
d="sin datos";
}
double Obt_der_a()
{
return a;
}
};

class derivada_2:public derivada_1


{
protected:
int* e;
int n;
public:

derivada_2(int m):derivada_1(m)
{
n=m;
e=new int [n];
e[0]=a;
for(int i=1;i<n;i++)
{
e[i]=e[i-1]+b+c;
}
}

~ derivada_2()
{
cout<<"llamada al destructor"<<endl;
delete[] e;
}

friend ostream& operator<<(ostream& os, derivada_2 x)


{
os <<"a=" <<x.a<<endl
<<"b=" <<x.b<<endl
18 CHAPTER 3. CONCEPTOS BÁSICOS DE C++

<<"c=" <<x.c<<endl
<<x.d<<endl;
for(int i=0;i<x.n;i++)
{
cout<<x.e[i]<<" ; ";
}
os<<endl;
return os;
}
};
int main()
{
base x(5,7.5);
derivada_1 y(5);
cout<<"x.b="<<x.Obt_b()<<endl
<<"y.b="<<y.Obt_b()<<endl
<<"a="<<y.Obt_der_a()<<endl;
derivada_2 z(20);
cout<<z;
return 0;
}

3.6 Ejercicios
Ejercicio 3.1: Se dispone de un arreglo de n elementos enteros A que contiene valores
repetidos, esto es, cada elemento puede aparecer más de una vez en el arreglo. Escribir un
programa para remover todos los duplicados del arreglo.

Ejercicio 3.2: Se dispone del siguiente método iterativo

[f (xn )]2
xn+1 = xn −
f (xn + f (xn )) − f (xn )

donde x0 ∈ R es el valor inicial ingresado desde teclado y f (x) = ax2 + bx + c

• Escribir una clase que permita almacenar la función:


class polinomio2
{
private:
double a;
double b;
double c;
public:
......
};
• Escribir al menos un constructor y sobrecargar los operadores <<, >>
• Escribir una función miembro que reciba x ∈ R y retorne f (x). Usar double eval-
uar(double x);
3.6. EJERCICIOS 19

• Escribir una función miembro que reciba dos números x0 ∈ R y n ∈ Z + , y retorne xn


usando el método anterior.
Ejercicio 3.3: Realizar las siguientes tareas(usar stl):
• Escribir una función que reciba un número entero n ≥ 2 retorne un vector con la
descomposición en factores primos. Ejemplo: si n = 24, se debe retornar x = (2, 2, 2, 3)
• Escribir una función que reciba un vector x y un entero positivo p y retorne el número
de repeticiones de p en el vector.
• Escribir un programa que reciba un vector x y retorne un vector z ∈ Z m+1 donde
m = maxi=0,...,n−1 {xi }. La posición zi debe almacenar el número de veces que xi = i,
para todo i ∈ {0, 1, . . . , m}.
Ejemplo: z = (z0 = 0, z1 = 0, z2 = 3, z3 = 1)
Ejercicio 3.4: Una transformación afı́n de Rn → Rm es una función

T :Rn → Rm
x 7→ y = T (x) := Ax + b

donde A ∈ Rm×n y b ∈ Rm .

(a) Implementar una función que reciba A, x, b y calcule la transformación afı́n, de acuerdo
a la siguiente declaración:
double* afin(double** A, double* b, double * x);
(b) Probar la función anterior en un programa que lea un valor α ∈ R desde el teclado y
generar la matriz A y los vectores x, b.
     
cos α 0 − sin α 1 α
A :=  0 1 0  x :=  1  b :=  2α 
sin α 0 cos α α 3α

Ejercicio 3.5: Escribir una función que busque y elimine todas las filas duplicas en una
matriz, es decir, dada una matriz A ∈ Zm×n donde posiblemente para algún par de filas
i, j con i 6= j se tiene que ai1 = aj1 , . . . , ain = ajn , entonces se debe retornar una matriz
A′ ∈ Zp×n tal que p ≤ n donde se mantenga solo una de las filas repetidas. Usar la
declaración:

vector<vector<int> > EliminarDuplicados(vector<vector<int> > A);

Escribir una segunda función que reciba m, n, k y retorne una matriz A ∈ Z m×n , donde sus
elementos sean enteros aleatorios en el intervalo [0, k]. Aplicar la función EliminarDuplicados
a la matriz anterior.
Ejercicio 3.6: Dados n + 1 números naturales c1 , . . . , cn y K. La tarea consiste en
determinar si existe un subconjunto S ⊆ {1, . . . , n} tal que:
X
cj = K
j∈S

Por ejemplo, dado el conjunto {7, 3, 2, 5, 8} y K = 17, la respuesta es ”SI” al seleccionar


el primero, tercero y quinto elemento. Computacionalmente, el conjunto de números puede
20 CHAPTER 3. CONCEPTOS BÁSICOS DE C++

ser almacenado como un vector c ∈ Nn y la solución puede ser expresada como un vector
binario s ∈ {0, 1}n (si = 1 si el número natural ci es usado en la solución, y si = 0 caso
contrario). Entonces se debe cumplir que:

n
X
ci si = K
i=1
Podemos explorar el espacio de soluciones generando una familia F de subconjuntos {1, . . . , n}
y verificando si alguno de estos subconjuntos es una solución del problema.
Realizar las siguientes tareas:
(a) Definir la clase:
• class subconjunto
{
private:
vector<int> sol;
vector<int> c;
int K;
public:
subconjunto();
subconjunto(int n, int K0);
bool validar_sol(vector<vector<int> > As);
//sobrecargar <<,>>.
};
• subconjunto(): Constructor.
• subconjunto(int n, int K0): Constructor. Generar n números aleatorios
menores que K0 y almacenarlos en c.
• validar sol(vector < vector < int > > As): Cada fila de la matriz As rep-
resenta un vector solución s. Si alguna fila satisface la condición del problema,
entonces dicha fila debe ser almacenada en el vector sol de la clase y se retorna
true.
(b) Recibir una instancia del problema desde teclado o un archivo usando >>.
(c) Generar una matriz aleatoria As ∈ {0, 1}m×n y eliminar filas duplicadas.
(d) Usando la matriz anterior, verificar si en As existe una fila solución del problema.
(e) Presentar la instancia y la solución del problema( si existe) usando <<.
Ejercicio 3.7: La conjetura de Collatz fue enunciada por el matemático Lothar Collatz
en 1937 y a la fecha no se ha resuelto. Se la formula de la siguiente forma: Dada la función
f : N → N: (
n
, si n es par
f (n) = 2
3n + 1, si n es impar
La órbita de un número entero cualquiera se define como las imágenes sucesivas al it-
erar la función. Por ejemplo, si n=13: f (13) = 13 × 3 + 1 = 40, f (f (13)) = 40/2 =
20, f (f (f (13))) = 20/2 = 10, . . . Si observamos este ejemplo, la órbita de 13 es periódica,
es decir, se repite indefinidamente a partir de un momento dado: 13, 40, 20, 10, 5, 16, 8, 4, 2, 1.
La conjetura dice que siempre alcanzaremos el 1 (y por tanto el ciclo 4, 2, 1)
para cualquier número con el que comencemos. Realizar una verificación computa-
cional de la misma.
3.6. EJERCICIOS 21

• Escribir una función que reciba un entero positivo n y retorne una lista con su órbita
hasta alcanzar un 1, o cuando la órbita sea periódica.
• Escribir una segunda función que reciba un entero positivo positivo k y verifique que
se satisface la conjetura para los enteros 2, 3, . . . , k. La función debe retornar una
estructura vector < vector < int > > donde la casilla i del vector corresponde a la
órbita del número i + 2.
• Imprimir la estructura anterior en pantalla.

Ejercicio 3.8: Se dispone de una mochila con capacidad limitada de almacenamiento W y


un conjunto de n objetos. Cada objeto tiene peso wi y nos proporciona un beneficio ci . El
problema surge en seleccionar un subconjunto de objetos consecutivos para ingresarlos en
la mochila de forma que nuestro beneficio sea
Pkmáximo sin exceder
Pk la capacidad, es decir,
necesitamos encontrar enteros l ≤ k tal que i=l wi ≤ W y i=l ci sea maximizada. Para
almacenar la información se propone:

• La clase objeto debe ser representada como un par de números reales donde la primera
componente representa el peso y la segunda el beneficio.
• La clase mochila usará la siguiente declaración:

class mochila:private vector<objeto>


{
private:
double W; int l; int k;
public:
};

Completar las siguientes declaraciones:

(a) Definir los constructores de las clases.


(b) Sobrecargar los operadores <<, >>
(c) void resolver(); Encontrar los ı́ndices l, k para la solución del problema.
(d) Leer la información desde un archivo y retornar la solución en pantalla.

Ejercicio 3.9: Se dispone de 2n trabajadores y se desea dividirlos en 2 grupos de n traba-


jadores cada uno. Cada trabajador dispone de un valor numérico que indica el desempeño
del empleado en el último año(mayor valor, mejor desempeño). Se busca particionar a
los empleados de la forma más junta posible, es decir, que la suma de los valores de los
desempeños de los empleados asignados a cada grupo sean lo más equitativos posibles.
Presente un algoritmo para resolver dicho problema. Si se dispone de la siguiente clase
para almacenar la información de un empleado(id,valor):

class empleado: public pair<int,double>


{
.....
};

Y la nomina se almacena en:


22 CHAPTER 3. CONCEPTOS BÁSICOS DE C++

class nomina: public vector<empleado>


{
.....
};

Complete la clase tal que:


• Defina al menos un constructor y el destructor de la clase.
• Sobrecargar los operadores <<, >>. Leer la información de los empleados desde un
archivo.
• Crear los grupos de acuerdo al algoritmo propuesto. Considere la declaración:

void particionamiento(vector < int > &gr1, vector < int > &gr2);

donde gr1 y gr2 almacenan los id de los empleados en cada grupo.


• presentar la solución usando el operador sobrecargado <<.
Ejercicio 3.10: La conjetura de Goldbach es uno de los problemas abiertos más antiguos
en matemáticas. Su enunciado menciona lo siguiente:
Caso fuerte: Todo número par mayor que 2, puede ser escrito como la suma de
dos números primos.
Caso débil: Todo número impar mayor que 5, puede ser escrito como la suma
de tres números primos
Verificar computacionalmente la conjetura usando un conjunto finito de números primos.
• Definir la clase:
class goldbach:public vector<int>
{
private:
int n;
int p;
int q;
int r;
public:
.....
};
• goldbach(): Constructor de la clase.
• Sobrecarga del operador >>. Recibe un número entero positivo k. La función debe
encontrar y almacenar en el objeto todos los primos pi ≤ n = k.
• void conjetura(int m): Recibe un entero par 5 < m ≤ n y encuentra los primos p, q, r
que validan la conjetura.
• Sobrecarga del operador <<, retornando los valores p, q, r

También podría gustarte