30
UNIDAD I “ANÁLISIS DE LOS ALGORITMOS” [FECHA] [NOMBRE DE LA COMPAÑÍA] [Dirección de la compañía]

Análisis de Algoritmos

Embed Size (px)

DESCRIPTION

ESTRUCTURA DE DATOS

Citation preview

Page 1: Análisis de Algoritmos

UNIDAD I

“ANÁLISIS DE LOS ALGORITMOS”

[FECHA][Nombre de la compañía]

[Dirección de la compañía]

Page 2: Análisis de Algoritmos

1

B

INSTITUTO TECNOLÓGICO DE VERACRUZ

INGENIERÍA EN SISTEMAS COMPUTACIONES

“UNIDAD I: ANÁLISIS DE LOS ALGORITMOS”

Proyecto de Investigación

PARA ACREDITAR LA MATERIA

ESTRUCTURA DE DATOS

HORARIO: 12-13 HRS.

PRESENTA LA ALUMNA:CASTILLO HUERTA BRENDA KAREN

NO. CONTROL: E14021194

PROFESOR DE LA MATERIA:

ING. BALLESTEROS BARRADAS LUIS BERNARDO

VERACRUZ, VER. Septiembre 2015

Page 3: Análisis de Algoritmos

2

ÍNDICE GENERALPÁG.

ÍNDICE Tabla/Figura……....……………..………………………………………………

2

Introducción…………………………………..………………………… 3

1.1 Concepto de complejidad de algoritmos…………………………………….. 5

1.1.1 Tiempo de ejecución……………………………………………….. 8

1.1.2 Aritmética de la notación 0…………………………………………. 9

1.1.3 Regla para la notación O…………………………………………… 9

1.2 Complejidad en el tiempo…………………………………………………. 12

1.3 Complejidad en el espacio………………………………………………….. 14

1.4 Evaluación y selección de algoritmos eficientes…………………………… 18

Conclusión…………………………………………………………………. 19

Bibliografía………………………………………………………………… 20

Page 4: Análisis de Algoritmos

3

INTRODUCCIÓN

En el desarrollo de programas, existe una fase previa a la escritura del programa, esta es el diseño del algoritmo que conducirá a la solución del problema, en esta fase también deberá considerarse la estructura de datos que se va a utilizar. El término estructura de datos se refiere a la forma en que la información está organizada dentro de un programa. La correcta organización de datos puede conducir a algoritmos más simples y más eficientes. Estructura de datos: Conjunto de variables agrupadas y organizadas de cierta forma para representar un comportamiento.

De acuerdo a John Bentley, de 1945 a 1985 la velocidad del hardware de supercómputo se incrementó en un factor de 6x105 mientras que las mejoras de los algoritmos para problemas, por ejemplo los cálculos en tres dimensiones han sido reducidos en un factor de N4/60. Para N=64 fue cerca de 3x105. De esta forma vemos que las mejoras algorítmicas han tenido tanto impacto en los cálculos científicos como los avances logrados en hardware.

En el período 1980-1994 la computadora aumenta su velocidad en un orden de 1.5 cada década, en cambio, los avances logrados en el campo de los algoritmos, permiten la solución de problemas de tamaño 200 y hasta 820 veces mayor, respectivamente el equivalente a 15 y 20 años de avance en el hardware.

Desde el punto de vista de la duración de su impacto, los nuevos microprocesadores se vuelven obsoletos en 2 a 4 años, mientras que los avances en los algoritmos permanecen por décadas.

Algoritmo se define como un conjunto de instrucciones que la computadora debe seguir para resolver un problema. La palabra algoritmo se deriva de la traducción al latín del nombre Muhammad Musa Alkhawarizmi, un matemático y astrónomo árabe que en el siglo IX escribió un tratado sobre manipulación de números y ecuaciones. Y sus características son:

Son independientes del lenguaje de programación a utilizar. Sencillo, los pasos deben ser claros y bien definidos. Precisos, indican claramente el orden de realización paso a paso. Definidos, cada vez que se ejecutan con las mismas entradas se obtiene el mismo

resultado. Finitos, tienen un número de pasos finito.

En una primera impresión, podría considerarse que los algoritmos y estructuras de datos no tienen una aplicación práctica. Sin embargo, su conocimiento y correcta aplicación permiten desarrollar programas con mejor aprovechamiento de los recursos del sistema, más rápidos, eficientes, robustos y con una mayor tolerancia a errores.

Page 5: Análisis de Algoritmos

4

Los algoritmos tienen innumerables aplicaciones, entre estas podemos mencionar algoritmos para la ruta más corta entre dos estaciones del metro, algoritmos para procesamiento de cadenas en analizadores léxicos, algoritmos criptográficos, algoritmos para compresión de datos, algoritmo genéticos, etc.

En esta investigación se tratarán temas para aprender a realizar de una manera más eficiente un algoritmo, de tal manera que, se pueda escribir en el menor número de líneas posibles para así facilitar su lectura y su escritura; y con ello se pueda ahorrar tiempo de ejecución y memoria. Se pueden aprender estructuras, las cuales nos ayudarán a realizar algoritmos más eficaces y fáciles de realizar.

También se expondrá cómo seleccionar algoritmos de tal forma que éste sea sencillo de entender, calcular, codificar y depurar, así mismo, que utilice eficientemente los recursos de la computadora y se ejecute con la mayor rapidez posible con un eficaz uso de memoria dinámica y estática.

Page 6: Análisis de Algoritmos

5

1.1. CONCEPTO DE COMPLEJIDAD DE ALGORITMOS

La mayoría de los problemas que se plantean en la actualidad se pueden resolver con algoritmos que difieren en su eficiencia. Dicha diferencia puede ser irrelevante cuando el número de datos es pequeño pero cuando la cantidad de datos es mayor la diferencia crece. Ejemplo: Suma de 4 y 10 primero números naturales.

1+2+3+4  = 10

 

1+2+3+4+5+6+7+8+9+10 = 55

3                3

tiempo

9                3

4*(4+1)/2 = 10

 

10*(10+1)/2 = 55

La complejidad de un algoritmo o complejidad computacional, estudia los recursos y esfuerzos requeridos durante el cálculo para resolver un problema los cuales se dividen en: tiempo de ejecución y espacio en memoria. El factor tiempo, por lo general es más importante que el factor espacio, pero existen algoritmos que ofrecen el peor de los casos en un menor tiempo que el mejor de los casos, lo cual no es la mejor de las soluciones.

El factor tiempo de ejecución de un algoritmo depende de la cantidad de datos que se quieren procesar, una forma de apreciar esto es con las cuatro curvas que se presentan en las gráficas de la figura 1.1.

Como se puede apreciar en las gráficas, entre mayor se al número de datos mayor tiempo se aplica en las gráficas a), b) y c), lo cual no ocurre con la gráfica d), por lo tanto podemos deducir que una función que se acerque más al eje de las x es más constante y eficiente en el manejo de grandes cantidades de datos.

Una vez que dispongamos de un algoritmo que funciona correctamente, es necesario definir criterios para medir su rendimiento o comportamiento.

Estos criterios se centran principalmente en su simplicidad y en el uso eficiente de los recursos. A menudo se piensa que un algoritmo sencillo no es muy eficiente. Sin embargo, la sencillez es una característica muy interesante a la hora de diseñar un algoritmo, pues

Page 7: Análisis de Algoritmos

6

facilita su verificación, el estudio de su eficiencia y su mantenimiento. De ahí que muchas veces prime la simplicidad y legibilidad del código frente a alternativas más crípticas y eficientes del algoritmo. Este hecho se pondrá de manifiesto en varios de los ejemplos mostrados a lo largo de este libro, en donde profundizaremos más en este compromiso. Respecto al uso eficiente de los recursos, éste suele medirse en función de dos parámetros: el espacio, es decir, memoria que utiliza, y el tiempo, lo que tarda en ejecutarse.

Ambos representan los costes que supone encontrar la solución al problema planteado mediante un algoritmo. Dichos parámetros van a servir además para comparar algoritmos entre sí, permitiendo determinar el más adecuado de entre varios que solucionan un mismo problema.

En este capítulo nos centraremos solamente en la eficiencia temporal. El tiempo de ejecución de un algoritmo va a depender de diversos factores como son: los datos de entrada que le suministremos, la calidad del código generado por el compilador para crear el programa objeto, la naturaleza y rapidez de las instrucciones máquina del procesador concreto que ejecute el programa, y la complejidad intrínseca del algoritmo.

Hay dos estudios posibles sobre el tiempo:

1. Uno que proporciona una medida teórica (a priori), que consiste en obtener una función que acote (por arriba o por abajo) el tiempo de ejecución del algoritmo para unos valores de entrada dados.

2. Y otro que ofrece una medida real (a posteriori), consistente en medir el tiempo de ejecución del algoritmo para unos valores de entrada dados y en un ordenador concreto.

Ambas medidas son importantes puesto que, si bien la primera nos ofrece estimaciones del comportamiento de los algoritmos de forma independiente del ordenador en donde serán implementados y sin necesidad de ejecutarlos, la segunda representa las medidas reales del comportamiento del algoritmo. Estas medidas son funciones temporales de los datos de entrada.

La resolución práctica de un problema exige por una parte un algoritmo o método de resolución y por otra un programa o codificación de aquel en un ordenador real.

Ambos componentes tienen su importancia, pero la del algoritmo es absolutamente esencial, mientras que la codificación puede muchas veces pasar a nivel de anécdota.

A efectos prácticos o ingenieriles, nos deben preocupar los recursos físicos necesarios para que un programa se ejecute.

Aunque puede haber muchos parámetros, los más usuales son el tiempo de ejecución y la cantidad de memoria (espacio).

Ocurre con frecuencia que ambos parámetros están fijados por otras razones y se plantea la pregunta inversa: ¿cuál es el tamaño del mayor problema que puedo resolver en T segundos y/o con M bytes de memoria?

Page 8: Análisis de Algoritmos

7

En lo que sigue nos centramos casi siempre en el parámetro tiempo de ejecución, si bien las ideas desarrolladas son fácilmente aplicables a otro tipo de recursos.

Para cada problema determinaremos una medida N de su tamaño (por número de datos) e intentaremos hallar respuestas en función de dicho N.

El concepto exacto que mide N depende de la naturaleza del problema.

Así, para un vector se suele utilizar como N su longitud; para una matriz, el número de elementos que la componen; para un grafo, puede ser el número de nodos (a veces es más importante considerar el número de arcos, dependiendo del tipo de problema a resolver), en un archivo se suele usar el número de registros, etc.

Es imposible dar una regla general, pues cada problema tiene su propia lógica de coste.

El algoritmo en función del tamaño de la entrada.

Cada algoritmo guarda una estrecha relación con una estructura de datos. Por ello, no siempre es posible utilizar el algoritmo más eficiente, puesto que la elección de las estructuras de datos depende de varias cuestiones, incluida la de qué tipo de datos administramos y la frecuencia con que se realizan diferentes operaciones sobre ellos. Así, deberemos encontrar una situación compromiso entre tiempo y compromiso utilizados. En general, si aumentamos el espacio necesario para almacenar los datos, conseguiremos un mejor rendimiento en el tiempo y viceversa.

La eficiencia de un algoritmo puede ser cuantificada con las siguientes medidas de complejidad:

1. Complejidad Temporal o Tiempo de ejecución. Tiempo de cómputo necesario para ejecutar algún programa.

2. Complejidad Espacial. Memoria que utiliza un programa para su ejecución, La eficiencia en memoria de un algoritmo indica la cantidad de espacio requerido para ejecutar el algoritmo; es decir, el espacio en memoria que ocupan todas las variables propias al algoritmo. Para calcular la memoria estática de un algoritmo se suma la memoria que ocupan las variables declaradas en el algoritmo. Para el caso de la memoria dinámica, el cálculo no es tan simple ya que, este depende de cada ejecución del algoritmo.

Este análisis se basa en las Complejidades Temporales, con este fin, para cada problema determinaremos una medida N, que llamaremos tamaño de la entrada o número de datos a procesar por el programa, intentaremos hallar respuestas en función de dicha N.

El concepto exacto que cuantifica N dependerá de la naturaleza del problema, si hablamos de un array se puede ver a N como el rango del array, para una matriz, el número de elementos que la componen; para un grafo, podría ser el número de nodos o arcos que lo arman, no se puede establecer una regla para N, pues cada problema acarrea su propia lógica y complejidad.

Page 9: Análisis de Algoritmos

8

1.1.1. 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

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.

Page 10: Análisis de Algoritmos

9

1.1.2. ARITMÉTICA DE LA NOTACIÓN O

La notación asintótica “O” (grande) se utiliza para hacer referencia a la velocidad de crecimiento de los valores de una función, es decir, su utilidad radica en encontrar un límite superior del tiempo de ejecución de un algoritmo buscando el peor caso.

La definición de esta notación es la siguiente:

f(n) y g(n) funciones que representan enteros no negativos a números reales. Se dice que f(n) es O(g(n)) si y solo si hay una constante realc>0 y un entero constante n0>=1 tal que

f(n)<=cg(n) para todo entero n>= n0.

Esta definición se ilustra en la figura 1.2.

Nota: el orden de magnitud de una función es el orden del término de la función más grande respecto de n.

La notación asintótica “O” grande se utiliza para especificar una cota inferior para la velocidad de crecimiento de T(n), y significa que existe una constante c tal que T(n) es mayor o igual a c(g(n)) para un número infinito de valores n.

1.1.3. Regla para la notación O

Definición (O)T(n) es O(f(n)) si existen las constantes positivas c y n0 tales que n >= n0 se verifica T(n)<= cf(n).

Esta definición afirma que existe un punto inicial n0 tal que para todos los valores de n después de ese punto; el tiempo de ejecución T(n) esta acotado por algún múltiplo de f(n).

La expresión matemática de lo anterior es T(n) = O(f(n)) y el índice de crecimiento de T(n) es <= al crecimiento de f(n).

T(n) Tiempo de ejecución del algoritmo.

F(n) Tiempo al introducir los datos al algoritmo.

Page 11: Análisis de Algoritmos

10

Dada una función f, queremos estudiar aquellas funciones g que a lo sumo crecen tan deprisa como f. Al conjunto de tales funciones se le llama cota superior de f y lo denominamos O(f). Conociendo la cota superior de un algoritmo podemos asegurar que, en ningún caso, el tiempo empleado será de un orden superior al de la cota.

Definición 1.1 Sea f: N→[0,∞). Se define el conjunto de funciones de orden O (Omicron) de f como: O(f) = {g: N→[0,∞) ∃c∈R, c>0, ∃n0∈N • g(n) ≤ cf(n) ∀n ≥ n0}.

Diremos que una función t: N →[0,∞) es de orden O de f si t ∈O(f). Intuitivamente, t ∈ O(f) indica que t está acotada superiormente por algún múltiplo de f.

Normalmente estaremos interesados en la menor función f tal que t pertenezca a O(f). En el ejemplo del algoritmo Buscar analizado anteriormente obtenemos que su tiempo de ejecución en el mejor caso es O(1), mientras que sus tiempos de ejecución para los casos peor y medio son O(n).

Propiedades de O Veamos las propiedades de la cota superior. La demostración de todas ellas se obtiene aplicando la definición:

1.1. 1. Para cualquier función f se tiene que f ∈O(f).

2. f ∈O(g) ⇒ O(f) ⊂ O(g).

3. O(f) = O(g) ⇔ f ∈O(g) y g ∈O(f).

4. Si f ∈O(g) y g ∈O(h) ⇒ f ∈O(h).

5. Si f ∈O(g) y f ∈O(h) ⇒ f ∈O(min(g,h)).

6. Regla de la suma: Si f1 ∈O(g) y f2 ∈O(h) ⇒ f1 + f2 ∈O(max(g,h)).

7. Regla del producto: Si f1 ∈O(g) y f2 ∈O(h) ⇒ f1•f2 ∈O(g•h).

8. Si existe ( ) ( ) g n f n lim n→∞ = k, dependiendo de los valores que tome k obtenemos:

a) Si k ≠0 y k < ∞ entonces O(f) = O(g).

b) Si k = 0 entonces f ∈O(g), es decir, O(f) ⊂ O(g), pero sin embargo se verifica que g ∉O(f).

Obsérvese la importancia que tiene el que exista tal límite, pues si no existiese (o fuera infinito) no podría realizarse tal afirmación, como veremos en la resolución de los problemas de este capítulo. De las propiedades anteriores se deduce que la relación ~O, definida por f ~O g si y sólo si O(f) = O(g), es una relación de equivalencia. Siempre escogeremos el representante más sencillo para cada clase; así los órdenes de complejidad constante serán expresados por O(1), los lineales por O(n), etc.

El tiempo de ejecución es proporcional, esto es, multiplica por una constante a alguno de los tiempos de ejecución anteriormente propuestos, además de la suma de algunos términos más pequeños. Así, un algoritmo cuyo tiempo de ejecución sea T = 3N2 + 6N se puede

Page 12: Análisis de Algoritmos

11

considerar proporcional a N2. En este caso se diría que el algoritmo es del orden de N2, y se escribe O(N2).

Los grafos definidos por matriz de adyacencia ocupan un espacio O(N2), siendo N el número de vértices de éste.

La notación O-grande ignora los factores constantes, es decir, ignora si se hace una mejor o peor implementación del algoritmo, además de ser independiente de los datos de entrada del algoritmo. Es decir, la utilidad de aplicar esta notación a un algoritmo es encontrar un límite superior del tiempo de ejecución.

A veces ocurre que no hay que prestar demasiada atención a esto. Por ejemplo, el tiempo de ejecución del algoritmo Quick Sort es de O(N2). Sin embargo, en la práctica este caso no se da casi nunca y la mayoría de los casos son proporcionales a N•logN.

Page 13: Análisis de Algoritmos

12

1.2. COMPLEJIDAD EN EL TIEMPO

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

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.

Si hacemos un análisis de forma directa al programa para determinar el tiempo de ejecución del mismo, debemos definir el conjunto de operaciones primitivas, que son independientes del lenguaje de programación que se use. Algunas de las funciones primitivas son las siguientes:

Asignación de un valor a una variable. Llamada a un método. Ejecución de una operación aritmética. Comparar dos números. Poner índices a un arreglo. Seguir una referencia de objeto. Retorno de un método.

Page 14: Análisis de Algoritmos

13

En forma específica, una operación primitiva corresponde a una instrucción en el lenguaje de bajo nivel, cuyo tiempo de ejecución depende del ambiente de hardware y software, pero es constante. Ejemplo. Método que retorna el número mayor de un arreglo de n elementos.

public int Mayor()

{

int may=arr[0];

for(ind=0; ind<arr.length; ind++)

if(arr[ind]>may)

may=arr[ind];

return may;

}

Para este ejemplo se pueden encontrar dos fórmulas que determinen el tiempo de ejecución, la primera representa el peor de los casos y la segunda el mejor de los casos. Para ser creación se sigue el programa:

La inicialización de la variable may=arr[0], corresponde a dos unidades de tiempo.La inicialización del ciclo for agrega otra unidad de tiempo.La condición del ciclo for se ejecuta desde 1 hasta el tamaño del arreglo lo cual agrega el número de unidades del tamaño del arreglo.El cuerpo del ciclo for se ejecuta el tamaño del arreglo - 1 veces, para este caso el número de operaciones del cuerpo del ciclo pueden ser 6 o 4 (condición del if dos, asignación a may dos e incremento y asignación dos) en el peor o mejor de los casos respectivamente. Por consiguiente el cuerpo del ciclo contribuye con 4(tamaño del arreglo - 1) o 6(tamaño del arreglo - 1) unidades de tiempo.Y el retorno de may aporta una unidad de tiempo.

Con todo lo anterior se logra obtener las siguientes formulas (tamaño del arreglo o arr.length se cambian por n):

T(n) = 2+1+n+6(n-1)+1 = 7n-2 Peor de los casos.

T(n) = 2+1+n+4(n-1)+1 = 5n Mejor de los casos.

Page 15: Análisis de Algoritmos

14

1.3. COMPLEJIDAD EN ESPACIO.

La complejidad de espacio, se refiere a la memoria que utiliza un programa para su ejecución; es decir el espacio de memoria que ocupan todas las variables propias del programa. Dicha memoria se divide en Memoria estática y Memoria dinámica.

Para calcular la memoria estática, se suman la cantidad de memoria que ocupa cada una de las variables declaradas en el programa.

Tomando en cuenta los tipos de datos primitivos del lenguaje de programación java podemos determinar el espacio que requiere cada una de las variables de un programa, de acuerdo a lo siguiente:

Tipo de dato primitivo Tamaño en bits Tamaño en Bytesbyte

char

short

int

float

long

double

8

16

16

32

32

64

64

1

2

2

4

4

8

8

El cálculo de la memoria dinámica, no es tan simple ya que depende de cada ejecución del programa o algoritmo y el tipo de estructuras dinámicas que se estén utilizando.

Otra consideración a tener en cuenta a la hora de tratar con la complejidad es que si estamos contando el tiempo que tarda un algoritmo en resolver un problema ¿En qué ordenador lo ejecutamos? Parece obvio que el mismo algoritmo ejecutado en un ordenador el doble de rápido que otro tardará la mitad en encontrar la solución. ¿Cuál debería ser entonces la unidad de medida de la complejidad? Ninguna unidad de tiempo nos vale: ni segundos ni milisegundos, porque el resultado variaría de un ordenador a otro.

Además el mismo algoritmo tardará más o menos en solucionar un problema de una talla u otra. Es decir, no puede tardarse lo mismo en ordenar un array de 100 valores que uno de 100000.

Page 16: Análisis de Algoritmos

15

Nos podemos permitir esa simplificación porque lo que realmente queremos saber es cómo crece el número de instrucciones necesarias para resolver el problema con respecto a la talla del problema. Eso es realmente la complejidad.

Por ejemplo, observa ésta función (da igual cuál sea su propósito)

En este algoritmo hay un par de bucles para que se ejecuten uno después del otro.

Observemos el primer bucle.

Se ejecuta n veces, y en su interior hay una instrucción (la de la línea 5).

Eso quiere decir que la línea 5 se ejecuta n veces.

Después se ejecuta el segundo bucle, que contiene en su interior dos instrucciones (las de las líneas 8 y 9).

Como ese segundo bucle se ejecuta también n veces y tiene dos instrucciones, se realizan 2n instrucciones.

Finalmente hay una instrucción en la línea 11 que se ejecuta una sola vez.

El número de instrucciones que se ejecutan en total es n + 2n + 1 es decir, 3n + 1

Todavía no hemos llegado al fondo de la cuestión, pero vamos encaminados. Podemos decir que la complejidad de ese algoritmo es 3n + 1, porque ese es el número de

- 1figura 4.1

Page 17: Análisis de Algoritmos

16

instrucciones que hay que realizar para solucionar el problema cuando la talla del problema es n.

La idea que subyace es que podemos saber cómo se comporta el algoritmo conforme la talla del problema va creciendo. En este caso, si se representa 3n + 1 con respecto a n nos daremos cuenta de que esta función es una recta. Para este algoritmo podemos suponer que cuando lo traslademos a un lenguaje de programación concreto sobre un ordenador concreto, si para un n = 100 tarda 7 unidades de tiempo en solucionarlo, con unas pocas operaciones podemos deducir cuántas unidades de tiempo tarda para un n = 1000. Por supuesto, no es un valor exacto, pero lo que nos importa es saber de qué manera aumenta el tiempo con respecto a la talla del problema.

La complejidad espacial es el estudio de la eficiencia de los algoritmos en lo relativo a su consumo de memoria. Un razonamiento similar al seguido con el coste temporal nos lleva a considerar únicamente la evolución del consumo de memoria con la talla del problema. En el estudio asintótico no nos preocupa que un programa utilice la mitad o el doble de memoria que otro, pero sí que utilice una cantidad de memoria que crece con el cuadrado de n cuando otro requiere solo una cantidad de memoria constante, por ejemplo. El concepto de ((paso)) utilizado en los análisis de coste temporal tiene su análogo en el estudio del coste espacial con el concepto de ((celda de memoria)).

No importa si una variable ocupa 2, 4 u 8 bytes. Solo importa si su tamaño es o no es función (y de que orden) de n, la talla del problema. Un análisis de la complejidad espacial, pues, obtiene cotas superiores e inferiores para el consumo de memoria. El estudio de los algoritmos no recursivos es relativamente sencillo.

Por ejemplo, si un algoritmo únicamente utiliza variables escalares, presenta un coste espacial constante. Si necesita vectores cuyo tamaño es proporcional a n, la talla del problema, el coste espacial es Θ(n). Los algoritmos recursivos deben tener en cuenta, además, el espacio de pila consumido durante el cálculo. Aunque una rutina recursiva solo utilice variables escalares, puede requerir espacio Θ(n) si efectúa del orden de n llamadas recursivas para solucionar un problema de talla n.

Page 18: Análisis de Algoritmos

17

1.4. EVALUACIÓN Y SELECCIÓN DE ALGORITMOS EFICIENTES

Una de las características primordiales en la selección de un algoritmo es que este sea sencillo de entender, calcular, codificar y depurar, así mismo que utilice eficientemente los recursos de la computadora y se ejecute con la mayor rapidez posible con un eficaz uso de memoria dinámica y estática.

También para seleccionar correctamente el mejor algoritmo es necesario realizar estas preguntas:

¿Qué grado de orden tendrá la información que vas a manejar?

Si la información va a estar casi ordenada y no quieres complicarte, un algoritmo sencillo como el ordenamiento burbuja será suficiente. Si por el contrario los datos van a estar muy desordenados, un algoritmo poderoso como Quicksort puede ser el más indicado. Y si no puedes hacer una presunción sobre el grado de orden de la información, lo mejor será elegir un algoritmo que se comporte de manera similar en cualquiera de estos dos casos extremos.

¿Qué cantidad de datos vas a manipular?

Si la cantidad es pequeña, no es necesario utilizar un algoritmo complejo, y es preferible uno de fácil implementación. Una cantidad muy grande puede hacer prohibitivo utilizar un algoritmo que requiera de mucha memoria adicional.

¿Qué tipo de datos quieres ordenar?

Algunos algoritmos sólo funcionan con un tipo específico de datos (enteros, enteros positivos, etc.) y otros son generales, es decir, aplicables a cualquier tipo de dato.

¿Qué tamaño tienen los registros de tu lista?

Algunos algoritmos realizan múltiples intercambios (burbuja, inserción). Si los registros son de gran tamaño estos intercambios son más lentos.

Page 19: Análisis de Algoritmos

18

CONCLUSION

Los dos factores principales estudiados:

El coste o complejidad espacial, es decir, la cantidad de memoria que consume.

El coste o complejidad temporal, o sea, el tiempo que necesita para resolver un problema.

Ambos determinan el coste o complejidad computacional. No siempre coincidirán consumo espacial optimo con mínimo coste temporal; es más, ambos factores entraran, normalmente, en competencia.

En ésta investigación se propuso una aproximación empírica consistente en implementar diferentes programas que resuelven un mismo problema (con los mismos o diferentes algoritmos) y medir y comparar los tiempos de ejecución.

En principio, resulta más determinante una correcta elección del algoritmo que los detalles de implementación o, incluso, que la elección de un lenguaje de programación frente a otro. La aproximación empírica supone un notable esfuerzo en tanto que obliga a implementar diferentes programas, prepararlos para hacer factible la medida de tiempos y realizar diferentes experimentos que nos permitan extraer conclusiones.

Se puede concluir que es muy importante tener en cuenta todos los puntos tratados anteriormente, ya que una vez que sean comprendidos se podrán realizar algoritmos de una forma más fácil para que a la hora de realizarlos se puede hacerlo de la mejor manera siendo más eficaces.

Así a la hora de programarlo se evite poner líneas de código de más y, así ahorrar memoria y tiempo de ejecución. Con esto se puede hacer que su lectura y escritura sea más fácil para el programador.

Page 20: Análisis de Algoritmos

19

BIBLIOGRAFÍA

Textos:

I. Análisis de Algoritmos, Amalia Duch. Barcelona, marzo de 2007.II. Programming Pearls, Jon Bentley ed. 2000.

III. Estructuras de Datos y Algoritmos con patrones de diseño orientadas a objetos en Java. Bruno R. Preiss, B.A.Sc., M.A.Sc. Ph.D., P. Eng. Departamento de Ingeniería Eléctrica y Computación. Universidad de Waterloo, Waterloo, Canadá.

IV. Redes neurales: Algoritmos, aplicaciones y técnicas de programación. James A., Freeman y traducción de Giner Bermejo, García Rafael.

V. Metodología de la Programación Diagramas de Flujo Algoritmos y Programación estructurada. Luis Joyanes Aguilar.

Páginas Web:

1) https://www.programacionfacil.com/estructura_de_datos/complejidad_algoritmos2) http://www.lcc.uma.es/3) http://www.biblioteca.co.cr/pdf/unidad12-4.pdf 4) http://www.brpreiss.com/books/opus5/html/

page438.html#SECTION0015200000000000000000