Guía 3

Anuncio
Programación III. Guía 3.
1
Facultad:
Ingeniería
Escuela:
Computación
Asignatura: Programación III
Tema: Teoría de la Complejidad
Objetivos Específicos

Aplicar las reglas teóricas para el cálculo de eficiencia de algoritmos.

Comparar tiempos de ejecución de distintos algoritmos.
Materiales y Equipo
 Guía Número 3.
 Computadora con programa Microsoft Visual C#.
Introducción Teórica
Tiempo de Ejecución
Una medida que suele ser útil conocer es el tiempo de ejecución de un programa en
función de N, lo que denominaremos T(N). Esta función se puede medir físicamente
(ejecutando el programa, reloj en mano), o calcularse sobre el código contando
instrucciones a ejecutar y multiplicando por el tiempo requerido por cada instrucción.
Así, un trozo sencillo de programa como:
S1;
for(int i= 0; i<N; i++)
{
S2;
}
Requiere: T(N) = t1 +t2*N
2
Programación IV. Guía 2.
Siendo t1 el tiempo que lleve ejecutar la serie “S1” de sentencias, y t2 el que lleve la
serie “S2”.
Prácticamente
todos
los
programas
reales
incluyen
alguna
sentencia
condicional,
haciendo que las sentencias efectivamente ejecutadas dependan de los datos concretos
que se le presenten. Esto hace que más que un valor T(N) debamos hablar de un rango
de valores
Tmin(N) < = T(N) < = Tmax(N)
Los extremos son habitualmente conocidos como “caso peor” y “caso mejor”. Entre ambos
se hallara algún “caso promedio” o más frecuente.
Cualquier fórmula T(N) incluye referencias al parámetro N y a una serie de constantes
“Ti” que dependen de factores externos al algoritmo como pueden ser la calidad del
código generado por el compilador y la velocidad de ejecución de instrucciones del
ordenador que lo ejecuta. Dado que es fácil cambiar de compilador y que la potencia de
los ordenadores crece a un ritmo vertiginoso (en la actualidad, se duplica anualmente),
intentaremos analizar los algoritmos con algún nivel de independencia de estos factores;
es decir, buscaremos estimaciones generales ampliamente válidas.
Con frecuencia nos encontraremos con que no es necesario conocer el comportamiento
exacto, sino que basta conocer una cota superior, es decir, alguna función que se
comporte “aun peor”. La definición matemática de estos conjuntos debe ser muy
cuidadosa para involucrar ambos aspectos: identificación de una familia y posible
utilización como cota superior de otras funciones menos malas:
Matemáticamente se dice que el conjunto O(f(n)) es el de las funciones de orden de f(n), que se
define como:
O(f(n)) ={g:INTEGER-> REAL + tales que
Existen las constantes k y N0 tales que
Para todo N>N0, g(N) <=K*F(N) }
Si lo interpretamos en palabras, significa que O(f(n)) está formado por aquellas funciones g(n)
que crecen a un ritmo menor o igual que el de f(n). De las funciones “g” que forman este
Programación III. Guía 3.
3
conjunto O(f(n)) se dice que “están dominadas asintóticamente” por “f” en el sentido de que
para N suficientemente grande, y salvo una constante multiplicativa “K”,f(n) es una cota
superior de g(n).
Órdenes de Complejidad:
La familia O(f(n)) define un Orden de Complejidad. Elegiremos como representante de
este Orden de Complejidad a la función f(n) más sencilla perteneciente a esta familia.
O (1)
Orden constante
O (log n)
Orden logarítmico
O (n)
Orden lineal
O (n2)
Orden cuadrático
O (n3)
Orden cúbico
O (na)
Orden polinómico
O (2n)
Orden Exponencial
O (n!)
Orden factorial
Menor complejidad
Mayor complejidad
Se identifica una Jerarquía de Ordenes de Complejidad que coincide con el orden de la
tabla mostrada; jerarquía en el sentido de que cada orden de complejidad inferior tiene a
las superiores como subconjuntos.
Si un algoritmo A se puede demostrar de un cierto orden O(1), es cierto que también
pertenece a todos los órdenes superiores( la relación de orden cota superior es transitiva)
pero en la práctica lo útil es encontrar la “menor cota superior”, es decir el menor orden de
complejidad que lo cubra.
Reglas Prácticas:
Aunque no existe una receta que siempre funcione para calcular la complejidad de un
algoritmo, si es posible tratar sistemáticamente una gran cantidad de ellos, basándonos
en que suelen estar bien estructurados y siguen pautas uniformes. Los algoritmos bien
estructurados combinan las sentencias de alguna de las formas siguientes:
4
Programación IV. Guía 2.
Secuencia (;)
Decisión (if)
Bucles
Llamadas a procedimientos
Sentencias sencillas:
Nos referimos a las sentencias de asignación, entrada/salida, etc. siempre y cuando no
trabajen sobre variables estructuradas cuyo tamaño esté relacionado con el tamaño N del
problema. La inmensa mayoría de las sentencias de un algoritmo requieren un tiempo
constante de ejecución, siendo su complejidad O(1).
Secuencia (;)
La complejidad de una serie de elementos de un programa es del orden de la suma de las
complejidades individuales, aplicándose las operaciones arriba expuestas.
Decisión (IF..THEN..ELSE..END):
La condición suele ser de O(1), complejidad a sumar con la peor posible, bien en la rama
THEN, o bien en la rama ELSE. En decisiones múltiples (ELSE IF, SWITCH CASE), se
tomara la peor de las ramas.
Bucles (WHILE, REPEAT, FOR):
En los bucles con contador explícito, podemos distinguir dos casos, que el tamaño N
forme parte de los límites o que no. Si el bucle se realiza un número fijo de veces,
independiente de N, entonces la repetición sólo introduce una constante multiplicativa que
puede absorberse.
1) Ej.- for (int i= 0; i < K; i++) { algo_de_O(1) } => K*O(1) = O(1)
Si el tamaño N aparece como límite de iteraciones.
2) Ej.- for (int i= 0; i < N; i++) { algo_de_O(1) } => N * O(1) = O(n)
3) Ej.- for (int i= 0; i < N; i++)
{
for (int j= 0; j < N; j++)
{
algo_de_O(1)
}
Programación III. Guía 3.
5
}
Tendremos N * N * O(1) = O(n2)
El bucle exterior se realiza N veces, mientras que el interior se realiza 1, 2, 3, … N veces
respectivamente. En total, 1 + 2 + 3 + … + N = N*(1+N)/2 => O(n2).
A veces aparecen bucles multiplicativos, donde la evolución de la variable de control no es
lineal (como en los casos anteriores)
c= 1;
while (c < N)
{
algo_de_O(1)
c= 2*c;
}
El valor inicial de “c” es 1, siendo “2k” al cabo de “k” iteraciones. El número de
iteraciones es tal que 2k >= N => k= es (log2 (N)) [el entero inmediato superior] y, por
tanto, la complejidad del bucle es O(log n).
c= N;
while (c > 1)
{
algo_de_O(1)
c= c / 2;
}
Un razonamiento análogo nos lleva a log2(N) iteraciones y, por tanto, a un orden O(log n)
de complejidad.
for (int i= 0; i < N; i++)
{
c= i;
while (c > 0)
{
algo_de_O(1)
6
Programación IV. Guía 2.
c= c/2;
}
}
Tenemos un bucle interno de orden O(log n) que se ejecuta N veces, luego el conjunto es de
orden O(n log n)
Procedimiento
El entorno que se ocupará en C#, para esta guía será en modo Gráfico, por lo cual se seguirán
Bibliografía
los siguientes pasos:
1.Guía
Inicio ->
1 Todos los programas -> Microsoft Visual Studio 2012/2013 (dependiendo de su
versión disponible) -> Microsoft Visual Studio 2012/2013.
2. Archivo -> Nuevo -> Proyecto -> En la ventana emergente de Nuevo proyecto, seleccionar en
Tipo de proyecto:(Visual C# -> Windows), luego en Plantillas: Aplicación de Windows Form.
Nombre: Según se le indique en el ejemplo.
Realice el siguiente ejemplo interpretando el algoritmo de ordenamiento, en sintaxis de C#
Ejemplo 1. Método de ordenamiento Burbuja.
El Ordenamiento de Burbuja (BubbleSort en inglés) es un sencillo algoritmo de ordenamiento.
Funciona revisando cada elemento de la lista que va a ser ordenada con el siguiente,
intercambiándolos de posición si están en el orden equivocado.
Es necesario revisar varias veces toda la lista hasta que no se necesiten más intercambios, lo
cual significa que la lista está ordenada.
Algoritmo.
Para i = 0
condicion i < (Numero de elementos de a) paso i+1 Hacer
Para j = 0 condición j < (Numero de elementos de a) paso j+1 Hacer
Si a[J] > a[J+1] entonces Hacer
aux = a[J]
a[J] = a[J+1]
a[J+1] = aux
Fin del ciclo Para
Fin del ciclo Para
Programación III. Guía 3.
7
Código en C#. (Guardar con el nombre Ordenamientos)
Lo primero que debe hacerse es renombrar el formulario con el nombre de la práctica. Luego
se insertan los objetos necesarios, de tal manera que el formulario luzca similar al mostrado en
la figura siguiente:
Se han agregado al formulario un (1) GroupBox, tres (3) label, un (1) NumericUpDown, un (1)
comboBox, dos (2) Button, un (1) TextBox y un (1) ListBox.
Para el botón para reordenar se ha modificado su propiedad Name en cmdOrdenar
Para el botón para agregar se ha modificado su propiedad Name en cmdAgregar
Para el comboBox se ha modificado su propiedad Name en cmbMetodo
Para el ListBox se ha modificado la propiedad Name en lstDatos
Para NumericUpDown se ha modificado la propiedad Name en nudDato
Para el TextBox se ha modificado la propiedad Name en txtOrdenados
Para el label de información se ha modificado la propiedad Name por lblInfo
Notas importantes:
Para el ListBox y la presentación de sus números debe ir en formato númerico con 0 decimales,
esto está en las propiedades de la herramienta FormatString.
El listBox deberá soportar multicolumna para que puedan escribirse más datos, en propiedades
buscamos MultiColumn y lo cambiamos a True
Para el ComboBox en las propiedades buscaremos Items y ahí escribimos los métodos que
deseamos en este caso Burbuja.
8
Programación IV. Guía 2.
En el código lo primero es agregar la librería de Diagnostic-> using System.Diagnostics; para
poder utilizar el StopWatch. Una vez agregados todos los componentes pasamos a la parte de
la codificación, dando click sobre el Form1.
Una vez ahí inicializamos el Form1, para con un método seleccionado por defecto para ser
cargado
public Form1()
{
InitializeComponent();
this.cmbMetodo.SelectedIndex = 0;
}
//selecciona por defecto un método
Posteriormente ingresamos el código que corresponde al método de ordenamiento “Burbuja”:
//método de ordenamiento de "Burbuja"
int[] burbuja(int[] datos)
{
for (int i = 0; i < datos.Length; i++)
{
for (int j = 0; j < datos.Length - 1; j++)
{
if (datos[j] > datos[j + 1])
{
int aux = datos[j];
datos[j] = datos[j + 1];
datos[j + 1] = aux;
}
}
}
return datos;
}
Ahora vamos a codificar el botón agregar con dos actividades que deseamos que realice al
hacer clic (NO OLVIDE QUE PARA QUE SE ACTIVEN LOS EVENTOS DEBE DE DAR CLICK
EN LAS HERRAMIENTAS, SINO TENDRÁ PROBLEMAS CON EL CÓDIGO)
//agregamos los datos digitados o seleccionados en el ListBox
private void cmdAgregar_Click(object sender, EventArgs e)
{
lstDatos.Items.Add(nudDato.Value);
nudDato.Value = 0;
nudDato.Focus(); //fija las entradas a este control
foreach (var it in nudDato.Controls)
de NumericUpDown
{
//para obtener la coleccion dentro
Programación III. Guía 3.
9
if (it is TextBox)
((TextBox)it).SelectAll();
}
}
Ahora vamos a codificar el botón de ordenar
private void cmdOrdenar_Click(object sender, EventArgs e)
{
txtOrdenados.ResetText(); //lo resetea a sus valores por defecto
//CONVIRTIENDO DATOS A UN ARREGLO DE INT
int[] datos = new int[lstDatos.Items.Count];
for (int i = 0; i < lstDatos.Items.Count; i++)
datos[i] = Convert.ToInt32(lstDatos.Items[i]);
Stopwatch tiempo = new Stopwatch(); //Provee la posibilidad de medir el
tiempo, creamos una instancia que usaremos
tiempo.Start(); //inicio de conteo de tiempo de proceso
if (cmbMetodo.SelectedIndex == 0)
datos = burbuja(datos); //si el método es burbuja que ordene por ese
algoritmo
/*else
datos = inserctionSort(datos);*/
bool primero = true; //bandera para verificar si es el primer número
foreach (int num in datos)
{
if (primero)
txtOrdenados.Text += num.ToString(); //si es el primero que
escriba el número
else
txtOrdenados.Text += ", " + num.ToString(); //si no es el primero
que agregue una , antes del número
primero = false;
}
tiempo.Stop();
//finalización del tiempo del proceso
lblInfo.Text = string.Format("Ordenamiento ejecutado en {0} ms.",
tiempo.ElapsedMilliseconds); /*enviamos al label el nombre del método
y el tiempo que demoró en milisegundos*/
}
Programación IV. Guía 2.
10
Análisis de resultados
Método de ordenamiento InsertionSort.
Este es uno de los métodos más sencillos. Consiste en tomar uno por uno los elementos de un
arreglo y recorrerlo hacia su posición con respecto a los anteriormente ordenados. Así empieza
con el segundo elemento y lo ordena con respecto al primero. Luego sigue con el tercero y lo
coloca en su posición ordenada con respecto a los dos anteriores, así sucesivamente hasta
recorrer todas las posiciones del arreglo.
Este método se emplea con frecuencia para ordenar cartas en juegos de mano: se considera un
elemento a la vez insertándolo en su lugar correspondiente entre aquellos que ya han sido
considerados (manteniéndolos ordenados). El elemento se inserta moviendo los elementos
superiores una posición a la derecha y ocupando la posición vacante.
El algoritmo es el siguiente:
static void Ordenamiento_Insercion(int[ ] array)
{
for (int i = 0; i < n; i++)
{ int temp = array[i];
int j = i - 1;
while ((j >= 0) && (array[j] > temp))
{ array[j + 1] = array[j];
j--;
}
array[j + 1] = temp;
}
}
Ejercicio No. 1
Modifique el programa del ejemplo No. 1, de tal manera que pueda incorporarse otro método de
ordenamiento el InsertionSort. Agregar para poder seleccionarlo en el ComboBox de métodos,
que envíe el tiempo que demora en realizar el ordenamiento este nuevo método.
Ejercicio No. 2
Modifique el programa que viene trabajando de forma que agregue dos botones, uno para
poder eliminar un número en el ordenamiento a realizar y otro botón para poder reiniciar con un
nuevo arreglo de números.
Programación III. Guía 3.
11
Investigación Complementaria
Para la siguiente semana:
Guía 3
1. Calcule el grado de complejidad (notación O) y el tiempo, para los algoritmos utilizados
Guía 4
en la práctica (Inserción y de burbuja y todo el programa).
2. Analice si esos grados de complejidad corresponden a los tiempos presentados
fía
3. Analice por qué los tiempos no siempre son iguales a pesar de ingresar los mismos
números al arreglo
Descargar