Você está na página 1de 9

TEMA 23: DISEÑO DE ALGORITMOS: TÉCNICAS DESCRIPTIVAS.

1. INTRODUCCIÓN: La razón principal para utilizar un ordenador es para resolver problemas (en el sentido más general de la palabra),
o en otras palabras, procesar información para obtener un resultado a partir de unos datos de entrada.
Los ordenadores resuelven los problemas mediante la utilización de programas escritos por los programadores. Los programas
de ordenador no son entonces más que métodos para resolver problemas. Por ello, para escribir un programa, lo primero es que el
programador sepa resolver el problema que estamos tratando.

El procesamiento de la información se realizará mediante la utilización de un método para resolver el problema que
denominaremos algoritmo.

2. ALGORITMOS: Algoritmo: descripción precisa de una sucesión ordenada de instrucciones que permiten resolver un problema en un
número finito de pasos. Esta instrucciones o pasos deben ser: simples (directamente ejecutables), no ambiguas y en un orden
establecido. Los Algoritmos son independientes tanto del lenguaje de programación como del ordenador que los ejecuta.
Los algoritmos son independientes tanto del lenguaje de programación en que se expresan como de la computadora que los
ejecuta. Un lenguaje de programación es tan sólo un medio para expresar un algoritmo y una computadora es tan sólo un procesador
para ejecutarlo. Tanto el lenguaje de programación como la computadora son los medios para obtener un fin: conseguir que el
algoritmo se ejecute y efectúe el proceso correspondiente.
La resolución de un problema exige el diseño de un algoritmo que resuelva el problema propuesto.
2.1. ETAPAS:
1-Análisis del problema
a.- Acotar y especificar el problema con total precisión (obtener el
máximo de información acerca de lo que debemos resolver y las
soluciones a determinar.) El problema ha de ser comprendido
perfectamente antes de realizar el algoritmo.
b.- Definir los datos iniciales o de partida (que datos necesitamos
proporcionar del problema para resolverlo.)
c.- Definir que datos o resultados debe proporcionar el algoritmo.
2-Diseño del algoritmo
No hay un método general para encontrar el algoritmo para un
problema. Es una cuestión de experiencia e ingenio. Sin embargo si
que hay métodos sobre cómo proceder para realizar el algoritmo. Uno
de los más importantes es el diseño descendente (TOP-DOWN).
Consiste en descomponer un problema en subproblemas más
sencillos. Este proceso se puede repetir sucesivamente sobre cada
subproblema.
Es el más adecuado para la programación estructurada y modular.
3-Implementación del algoritmo
Consiste en la codificación del algoritmo en un programa. Esta codificación se deberá realizar utilizando un determinado lenguaje de
programación.
4- Prueba y depuración
Una vez realizado el programa se deberá ejecutar para comprobar que realmente hace lo que se pretendía, es decir, que no existan
errores de codificación ni errores en el algoritmo.
2.2. CARACTERÍSTICAS: Las características fundamentales que debe seguir un algoritmo son:
 Preciso. Debe indicar el orden de realización de cada paso.
 Definido. Si se sigue un algoritmo dos veces, se debe obtener el mismo resultado cada vez.
 Finito. Si se sigue un algoritmo, se debe terminar en algún momento; o sea, debe tener un número finito de pasos.
 Interacciona con el entorno. Es decir, tiene como mínimo una salida y puede tener entradas.
La definición de un algoritmo debe describir tres partes: Entrada, Proceso y Salida.
2.3. ELEMENTOS BÁSICOS:
2.3.1. Datos: Un dato es la expresión general que describe los objetos con los cuales opera un algoritmo. Los datos podrán ser
de los siguientes tipos:
 Entero. Subconjunto finito de los números enteros, cuyo rango o tamaño dependerá del lenguaje en el que
posteriormente codifiquemos el algoritmo y de la computadora utilizada.
 Real. Subconjunto de los números reales limitado no sólo en cuanto al tamaño, sino también en cuanto a la precisión.
 Lógico. Conjunto formado por los valores Verdadero y Falso.
 Carácter. Conjunto finito y ordenado de los caracteres que la computadora reconoce.
 Cadena. Los datos de este tipo contendrán una serie finita de caracteres, que podrán ser directamente traídos o
enviados a/desde consola.
Al establecer un tipo para un dato hay que considerar las operaciones que vamos a realizar con él y los instrumentos
disponibles. Los datos pueden venir expresados como constantes, variables o expresiones.
2.3.2. Constantes: Son datos cuyo valor no cambia durante todo el desarrollo del algoritmo. Las constantes podrán ser literales o
con nombres. Las constantes simbólicas o con nombre se identifican por su nombre y el valor asignado. Una constante
literal es un valor de cualquier tipo que se utiliza como tal. Tendremos pues constantes enteras, reales, lógicas, carácter y
cadena.
2.3.3. Variables: Es un objeto cuyo valor puede cambiar durante el desarrollo del algoritmo. Se identifica por su nombre y por su
tipo, que podrá ser cualquiera, y es el que determina el conjunto de valores que podrá tomar la variable. En los algoritmos
se deben declarar las variables. Cuando se traduce el algoritmo a un lenguaje de programación y se ejecuta el programa
resultante, la declaración de cada una de las variables originará que se reserve un determinado espacio en memoria
etiquetado con el correspondiente identificador.
2.3.4. Expresiones: Una expresión es una combinación de operadores y operandos. Los operandos podrán ser constantes,
variables u otras expresiones y los operadores de cadena, aritméticos, relacionales o lógicos. Las expresiones se clasifican,
según el resultado que producen, en:
 Aritméticas. Los operandos que intervienen en ellas son numéricos (enteros o reales), el resultado es también de tipo
numérico y se construyen mediante los operadores aritméticos. Los operadores aritméticos utilizados son los
siguientes:
o Multiplicación *
o División real /
o Exponenciación ^
o Suma +
o Resta –
o Resto de la división entera mod
o División entera div
 Cadena. Los operandos son de tipo cadena y producen resultados también de dicho tipo. Se construyen mediante el
operador concatenación, representado por el operador ampersand (&) o con el mismo símbolo utilizado en l as
operaciones aritméticas para la suma.
 Lógicas. Su resultado podrá ser verdadero o falso. Se construye mediante los operadores relacionales y lógicos. Los
operadores de relación son:
o Igual =
o Distinto <>
o Menor que <
o Mayor que >
o Menor o igual <=
o Mayor o igual >=
Actúan sobre operandos del mismo tipo y siempre devuelve un valor lógico. Los operadores lógicos básicos son:
o Negación lógica (no)
o Multiplicación lógica (y)
o Suma lógica (o)
Actúan sobre operandos de tipo lógico y devuelven resultados de l mismo tipo, determinados por las tablas de verdad
correspondientes a cada uno de ellos.

3. ESTRUCTURA DE LOS ALGORITMOS: Son las que controlan la ejecución de un algoritmo. Son de tres tipos:
3.1. Estructuras secuenciales: Se caracterizan porque una acción se ejecuta detrás de otra.
Acción 1
Acción 2
:
Acción n
3.2. Estructuras selectivas: Se ejecutan unas acciones u otras según se cumpla o no una determinada condición; pueden ser
simples, dobles o múltiples.
3.2.1. Simples: Se evalúa la condición y si ésta da como resultado verdad se ejecuta una determinada acción o grupo de
acciones; en caso contrario se saltan dicho grupo de acciones.
si < condición > entonces
Acción
fin -si
3.2.2. Dobles: Se evalúa la condición y si ésta da como resultado verdad se ejecuta una determinada acción o grupo de acciones
y si el resultado es falso se ejecuta otra acción o grupo de acciones.
si < condición > entonces
acción 1
si-no
acción 2
fin -si
3.2.3. Múltiples: Se ejecutarán unas acciones u otras según el resultado que se obtenga de evaluar una expresión. Se considera
que dicho resultado ha de ser de un tipo ordinal, es decir de un tipo de datos en el que cada uno de los elementos que
constituyen el tipo, excepto el primero y el último tiene un único predecesor y un único sucesor.
Cada grupo de acciones se encontrará ligado con: un valor, varios valores separados por comas, un rango, expresado
como valor_inicial..valor_final o una mezcla de valores y rangos.
Se ejecutará únicamente las acciones del primer grupo que, entre los valores a los que está ligado, cuente con el obtenido
al evaluar la expresión. Cuando el valor obtenido al evaluar la expresión no esté presente en ninguna lista de valores se
ejecutarían las acciones establecidas en la cláusula si_no, si existiese dicha cláusula.
según-sea < expresión > hacer
< lista1 >: < acciones1 >
< lista2 >: < acciones2 >
.....................................
[si-no
<accionesN>]
fin-según
3.3. Estructuras repetitivas: Las acciones del cuerpo del bucle se repiten mientras o hasta que se cumpla una determinada
condición. Es frecuente el uso de variables para controlar un bucle. Podemos considerar tres tipos básicos de estructuras
repetitivas: mientras, hasta, desde.
3.3.1. Mientras (While): Las acciones del bucle se realizan mientras la condición sea cierta. Se pregunta por la condición al
principio, por tanto dichas acciones se podrán ejecutar de 0 a N veces.
Mientras <expresión_lógica> hacer
< acciones >
fin_mientras
3.3.2. Hasta (Repeat): Las acciones del interior del bucle se ejecutaran una vez y continúan repitiendo se hasta que la condición
sea verdadera, por tanto dichas acciones se podrán ejecutar de 1 a N veces.
Repetir
< acciones >
hasta_que < expresión_lógica >

3.3.3. Desde (For ó Para): Se utiliza cuando se conoce, con anterioridad a que empiece a ejecutarse el bucle, el número de
veces que se va a iterar. La estructura desde empieza con un valor inicial de la variable índice y las acciones especificadas
se ejecutan a menos que el valor inicial sea mayor que el valor final. La variable índice se incrementa en uno, o en el valor
que especifiquemos, y si este nuevo valor no excede al final se ejecutan de nuevo las acciones.
desde v ¬ vi hasta vf [incremento | decremento <valor>] hacer
< acciones >
fin_desde

4. TÉCNICAS DESCRIPTIVAS: Existen numerosas formas de representar algoritmos, a continuación vemos las más importantes.
4.1. Métodos textuales
4.1.1. Especificación de la interfaz -función: Poco detallado. Se indican las entradas, las salidas y la función que se espera que
realice. La función se especifica como una sentencia simple que establece la relación entre las entradas y las salidas. Se
tienen que definir completamente los requerimientos del sistema, pero se puede prescindir de los detalles de
implementación.
MÓDULO: nombre
ENTRADAS: Parámetros de entrada
SALIDAS: Parámetros de salida
FUNCIÓN: Funcionalidad
Detalles de la funcionalidad: Especificación detallada de los pasos del algoritmo.
4.1.2. Pseudocódigo: Más detallado. Especifica más “como hacerlo” que “que hacer”. Es un lenguaje informal para especificar
proceso en el análisis estructurado. Tienes las siguientes características:
 El pseudocódigo es una herramienta para el diseñador y el programador, más que para el analista, y por tanto las
sentencias son generalmente más precisas y parecidas al código en el que será finalmente implementado.
 Sintaxis fija de palabras clave, que permite representar todos los constructores estructurados, declarar datos y establecer
características de modularidad.
 Sintaxis libre en lenguaje natural para describir características de procesamiento.
 Facilidad para declarar datos, incluso estructuras de datos sencillas (escalares, vectores) y complejas (listas y árboles)
 Mecanismos de definición de subalgoritmos y de invocación, soportando los diferentes modos de descripción de
interfaces.
Pensando en el código del programa, la sintaxis utilizada puede acercarse todo lo que se desee al lenguaje de
programación. El pseudocódigo es una herramienta muy buena para el seguimiento de la lógica de un algoritmo y para transformar
con facilidad los algoritmos a programas escritos en un lenguaje de programación específico. La notación utilizada es:
El comienzo y fin del algoritmo: o Decisión Múltiple
algoritmo nombre según-sea < expresión > hacer
inicio < lista1 >: < acciones1 >
acciones... < lista2 >: < acciones2 >
fin .....................................
Las acciones simples con: [si-no
o Asignación <nombre_variable>¬<expresión> <accionesN>]
o Entrada leer (<lista_de_variables>) fin-según
o Salida escribir (<lista_de_variables>) o Mientras (While)
Las distintas estructuras se representarán de la siguiente forma: Mientras <expresión_lógica> hacer
o Decisión Simples < acciones >
si < condición > entonces fin_mientras
Acción o Hasta (Repeat)
fin-si Repetir
o Decisión Dobles < acciones >
si < condición > entonces hasta_que < expresión_lógica >
acción 1 o Desde (For)
si-no desde v vi hasta vf [incremento | decremento <valor>] hacer
acción 2 < acciones >
fin-si fin_desde

4.2. Métodos gráficos: Los métodos gráficos que se utilizan para representar un algoritmo son:
4.2.1. Diagrama de flujo. Es la técnica más antigua y a la vez la más utilizada. Utiliza entre otros los siguientes símbolos:
Líneas de flujo () representa el flujo secuencial de la lógica del programa.
Rectángulo ( ) representa acciones a realizar.
Rombo () es una caja de decisión que representa respuestas sí/no o bien diferentes alternativas 1,2,3,4, ..., etc.
4.2.2. Jackson: Se basa en que la estructura de un programa está en función de los datos que manipula. La representación de
un programa es en forma de árbol.
4.2.3. Warnier: Esta basado en la teoría de conjuntos. Los datos de E/S que utiliza el programa deben estar estructurados en
forma de conjuntos.
4.2.4. Chapin: Representa diagramas en forma de bloques compactos, consta de definición detallada de E/S incluyendo los
archivos lógicos utilizados. La simbología utilizada se basa en rectángulos donde un programa se representa por un solo
diagrama en el que se incluyen todas las operaciones
Vamos a ver como se representas las estructuras con los diferentes métodos gráficos:
Otros métodos gráficos:

Diagrama de Flujo de Datos (dfd): Realizan una


representación gráfica de los algoritmos resaltando el flujo de
control del algoritmo.
El flujo de control de un algoritmo es el orden en el cual se
deben ejecutar las sentencias.

Diagrama de Cajas o Nassi-Schneiderman (N-S): Diagrama


en el que se omiten las flechas de unión y las cajas son
contiguas, lo que permite resaltar la estructura y favorece la
modularidad.

4.3. Otros métodos


4.3.1. Tablas de decisión: En muchas aplicaciones software, puede ser necesario que un módulo evalúe una compleja
combinación de condiciones y, de acuerdo con ellas, seleccione acciones apropiadas. Una tabla de decisión constituye una
notación que traduce las acciones y las condiciones a una forma tabular.
Para construir una tabla de decisión listamos las acciones involucradas en el procedimiento y las condiciones,
asociando conjuntos específicos de condiciones con acciones, y definimos reglas indicando que acciones ocurren para un
conjunto de condiciones.
Ej. Tabla de decisión que expresa los medicamentos asociados a un tratamiento médico, considerando algunas
características del paciente.

4.3.2. Árboles de decisión: La expresividad de un árbol de decisión es similar a la de una tabla de decisión, pero es menos
exhaustivo respecto a la combinación de condiciones que se desea explorar. Justamente en el caso que el número de
acciones sea reducido y no sean posibles todas las combinaciones de condiciones es preferible usar un árbol de decisión.
Ej. Sistema de venta por catálogo, donde el procedimiento para efectuar pedidos considera unas determinadas
situaciones y acciones asociadas, esta información se refleja en el siguiente árbol de decisión.

5. ANÁLISIS DE ALGORITMOS: COSTES: El análisis de algoritmos proporciona los métodos necesarios para poder comparar
distintos algoritmos que resuelven un mismo problema (a tener en cuenta en la etapa del diseño del algoritmo).
Además, para que la comparación tenga sentido, los algoritmos deben analizarse respecto al mismo modelo de computación.Esta
comparación se hace atendiendo a criterios de eficiencia computacional. La eficiencia algorítmica hace referencia al consumo de
recursos por parte de los algoritmos, principalmente, tiempo y espacio.
Estos conceptos de «tiempo» y «espacio» algorítmicos son abstractos, a diferencia del tiempo y el espacio reales consumidos por
la realización concreta de un algoritmo como programa.
Complejidad de un algoritmo: es la medida de los recursos que un algoritmo necesita para su ejecución (se puede expresar mediante
parámetros que miden de alguna manera la cantidad de recursos necesarios para ejecutar el algoritmo). Complejidad temporal:
Tiempo que un algoritmo necesita para su ejecución. Complejidad espacial: Recursos espaciales (de almacén) que un algoritmo
consume o necesita para su ejecución.
El tiempo de ejecución de un algoritmo depende de:
Factores externos:
- La máquina en la que se va a ejecutar
- El compilador
- La experiencia del programador
- Los datos de entrada suministrados en cada ejecución
Factores Internos:
- El número de instrucciones asociadas al algoritmo

Para estudiar el tiempo de ejecución o coste podemos hacer dos tipos de aproximación:
 Aproximación empírica o a posteriori, basada en la ejecución del algoritmo en un ordenador.
- Se obtiene una medida real del comportamiento del algoritmo en el entorno de aplicación.
- El resultado depende de los factores externos e internos
 Aproximación teórica o a priori, basada en un cálculo matemático que nos permite estudiar el comportamiento del algoritmo
sin necesidad de ejecutarlo realmente.
- El resultado depende sólo de los factores internos.
- Estima el comportamiento del algoritmo de forma independiente de los factores externos.
- No es necesario implementar y ejecutar los algoritmos.
- No obtiene una medida real del comportamiento del algoritmo en el entorno de aplicación
La complejidad temporal viene definida por el número de pasos necesarios para ejecutar el algoritmo y es una función del tamaño del
problema (Valor o conjunto de valores asociados a la entrada del problema que representa una medida de su tamaño respecto de
otras entradas posibles) y de la instancia del problema. Como lo que queremos es comparar algoritmos no necesitamos obtener
cantidades exactas para el coste, sino que nos basta con obtener su orden y magnitud. Para ello, estudiaremos la complejidad
asintótica y obtendremos:
- Una cota superior del coste estudiando el caso más desfavorable.
- Una cota inferior del coste estudiando el caso más favorable.
- Comportamiento medio, es una estimación del tiempo de cálculo medio que requiere el algoritmo. Es muy complicado de
obtener.
Para formalizar esto de la complejidad asintótica utilizaremos una notación matemática que se llama notación asintótica, la cual nos
permite estimar el rendimiento de un algoritmo con cierta aproximación.
Notación asintótica Big-Omicron: O Se utiliza para representar la cota superior. Sea f la función que representa el coste de un
algoritmo, decimos que f es como mucho del orden de una función g o que f crece más lentamente que g o que g es una cota superior
de f si y solo si:


Es decir, que existe alguna constante positiva a partir de la cual f(n) es estrictamente
menor que c·g(n).
Definimos pues el conjunto O(g(n)) = { funciones que son como mucho del orden de
g(n)}
Ejemplos
nn  Tomando c=3 i n0=0 tenemos: f(n)=3n  3n = c·g(n) n0
3n esta acotada superiormente por 3n
nn^)  Tomando c=3 i n0=0 tenemos: f(n)=3n  c·g(n) =3n2 n0
3n esta acotada superiormente por 3n 2

Existen otras dos notaciones Bigy Big- para expresar la cot a inferior y exacta de los algoritmos, aunque Big-Omicron O es la
notación más común, puesto que sirve para expresar cotas máximas. Podemos establecer cierta ordenación de las diferentes
funciones de coste de manera que dada una función cualquiera, podamos acotar-la superiormente por alguna de las funciones de la
serie. Así podemos establecer la siguiente relación entre los diferentes tipos de funciones:
O (1) < O (log(n))< O(n) < O(n·log(n)) < O(n^2) < O(n^3) < ···< O(a^n) < O(n!) < O(n^n)

Obtención práctica del coste de un algoritmo: En la práctica, para obtener el coste de


un algoritmo procederemos de la siguiente forma:
1. Determinar la talla del problema (de qué depende el tamaño del problema).
2. Si el coste del algoritmo varía en función de las instancias del problema, identificamos
el caso mejor y el caso peor (puede que no exista).
3. Aplicar un método de obtención de cotas de complejidad.

La complejidad temporal se calculará contando el número de pasos de programa de cada instrucción en función de la talla del
problema. Consideremos la complejidad de cada una de las estructuras algorítmicas en términos de pasos de programa.
Operaciones básicas: Se consideran como pasos de programa las asignaciones, comparaciones, operaciones aritméticas,
lecturas, escrituras, etc. El coste de un paso de programa será O (1).
Secuencia de instrucciones: S=S1 ; S2; coste(S)= coste(S1)+coste(S2)
Para tallas grande s, se puede dar el caso de que una de las instrucciones tenca coste despreciable frente a otras, de manera
que el coste de la secuencia se puede considerar del orden de la de mayor magnitud.
Ejemplo: Dadas S1 i S2 tales que coste(S1)=2n+3 y coste(S2)=n^2  coste(S1+S2) O (n^2)
Decisiones si..si-no....fin-si: si <B> entonces S1 si -no S2 fin –si coste(“Si”)=coste(B) +coste(S1 ó S2)
B Cierta coste(“Si”)=coste(B;S1)
B Falsa coste(“No”)=coste(B;S2)
Estructura repetitiva Mientras: Mientras <B> hacer <S >fin_mientras
Sea n el número de iteraciones que se ejecutan la ejecución de la instrucción anterior será equivalente en coste a la
ejecución de:

A efectos prácticos:
Si se realizan n iteraciones, el coste será como mucho de orden O (n). Para el caso de los bucles anidados, los costes crecerán
exponencialmente según el nivel de anidamiento. Así, generalmente, para un nivel de anidamiento i, el coste será de orden O (ni).
Si en cada iteración la talla n se reduce en un factor c, hasta llegar a una talla n>0 para la cual se da la condición de salida del bucle:
Tras la 1a iteración: la talla pasa a n/c
Tras la 2a iteración: la talla pasa a n/c 2
···
Tras la k-ésima iteración: la talla pasa a n/c k=n0
Por lo tanto, el número de iteraciones será:

6. DISEÑO DE ALGORITMOS: Existen diferentes técnicas de diseño de algoritmos para resolver un problema. A continuación
presentamos las más importantes:
6.1. RECURSIVIDAD: Un objeto es recursivo si su definición requiere la definición previa del objeto en un caso más sencillo.
Ejemplo: Números Naturales N (a) 0 es un nº natural. (b) el sucesor de un nº natural es también un nº natural.
0 Naturales n Naturales  n+1 Naturales
Una función es recursiva si su resolución requiere la solución previa de la función para casos más sencillos.
Ejemplo: (a) 0!=1 (b) n!=n (n-1)!

Un algoritmo A que resuelve un problema P es recursivo si está basado directa o indirectamente en sí mismo.

con: I, I' del mismo tipo


I' c I
Cuando I' es lo más pequeña posible, solución directa

Los algoritmos recursivos son especialmente apropiados si el propio problema o el propio cálculo a realizar o la propia
estructura con la que trabaja el problema aceptan una definición recursiva. Sin embargo, la existencia de tales definiciones no
garantiza que la mejor forma de resolver un problema pase por utilizar un algoritmo recursivo.
Siempre que diseñemos un algoritmo recursivo habrá que asegurarse no sólo de que el número de llamadas es finito (la
recursión acaba) sino también de que es pequeño, ya que hace falta espacio de memoria (pila de recursión) para almacenar en
cada llamada los objetos locales, los parámetros de la llamada y el estado del proceso en curso para recuperarlo cuando acabe
la llamada actual y haya que reanudar la antigua.

TIPOS DE RECURSIÓN:
1.- RECURSIÓN LINEAL: Si cada llamada recursiva genera, como mucho otra llamada recursiva
- FINAL: si la llamada recursiva es la última operación que se efectúa, devolviéndose como resultado lo que se haya
obtenido de la llamada recursiva sin modificación alguna.
- NO FINAL: El resultado obtenido de la llamada recursiva se combina para dar lugar al resultado de la función que
realiza la llamada.

2.- RECURSIÓN MÚLTIPLE: si alguna llamada puede generar más de una llamada adicional.

3.- RECURSIÓN ANIDADA: Hay recursión anidada cuando uno de los argumentos de la función recursiva es el resultado de la
llamada recursiva.
ETAPAS DEL DISEÑO RECURSIVO: Un diseño recursivo constará de las siguientes etapas:
1.- Definición del problema.
2.- Análisis de casos. Identificación de la función limitadora.
3.-Trascripción algorítmica y verificación de cada caso.
4.-Validación de la inducción: la función limitadora decrece estrictamente en las llamadas.

6.2. DISEÑO CON ESQUEMAS: Una forma de resolver ciertos problemas en programación es atacando el problema directamente.
Es lo que se conoce como el método de fuerza bruta. Normalmente, se puede aplicar a problemas sencillos, y puede llegar a dar
como resultado algunos algoritmos útiles en ciertos casos.
Pero la adopción de este criterio, proporciona algoritmos poco generales y en general poco eficientes cuando el problema
empieza a tener cierta magnitud.
Como alternativa a esta aproximación, los esquemas algorítmicos intentan capturar ciertas estrategias generales que
pueden ser útiles en muchos problemas diferentes, y no presentan las dificultades de fuerza bruta, permiten la generalización y
reutilización de algoritmos.
Estrictamente podemos definir un esquema algorítmico como un algoritmo genérico que expresa la forma de resolver un
determinado conjunto de problemas que cumplen ciertas condiciones.
Para cada problema concreto y cada representación del problema, el esquema dará un algoritmo concreto diferente.
Las entradas y las salidas de un esquema se expresan mediante un tipo genérico de datos que pueden diferir notablemente
según la forma en que cada algoritmo particular (una vez concretado el problema) sea implementado. La manera de expresar
los esquemas será mediante pseudocódigo o en lenguaje natural, aunque a menudo se hará uso de funciones genéricas que
pueden representar cálculos arbitrariamente complejos según cada caso particular.

6.2.1. Divide y vencerás: Para calcular un algoritmo que tiene una entrada de tamaño n, Divide y Vencerás (DV) propone dividir
la entrada en k subconjuntos distintos, 1 k n, lo que nos conduce a k subproblemas. Hay que resolver esos subproblemas
y encontrar un método que combine las soluciones de forma que se obtenga una solución para todo el problema.
Existe un teorema llamado Teorema de Reducción que demuestra que los mejores resultados se obtienen
fragmentando en cada paso el problema en 2 o más partes de aproximada mente el mismo tamaño.
Podemos expresar la estrategia de diseño DV mediante el siguiente esquema:

pequeño: función que determina si un problema es suficientemente pequeño


trivial: función que devuelve la solución para un problema pequeño
descomponer: función que descompone el problema en otros de tamaño menor
combinar :función que obtiene el resultado final a partir de las soluciones de los subproblemas

Ventajas: Algoritmo eficiente, Ideal para la resolución de problemas complejos, Se adapta de forma natural a entornos
multiprocesador  paralelismo, Hace un uso eficiente de la memoria.
Inconvenientes: Lento por la repetición del proceso recursivo por las llamadas a los subproblemas.
6.2.2. Programación dinámica: Se trata de un cambio de filosofía a la hora de resolver problemas de naturaleza recursiva. Se
puede ver como una manera de conversión de algoritmos recursivos en iterativos.
Los algoritmos de programación dinámica hacen uso de memoria auxiliar explícitamente, para evitar repetir cálculos, y
además realizan los cálculos de manera ascendente, resuelven primero los problemas más sencillos y guardan el resultado
de manera conveniente, y después resuelven los más complejos.
Principio de optimalidad: No todos los problemas recursivos se pueden resolver con programación dinámica,
solamente aquellos que cumplen el principio de optimalidad: Dado un problema de optimización del cual la solución se
puede representar como una secuencia de decisiones, cumplen este principio si dada una secuencia correspondiente a la
solución optima, toda subsecuencia corresponde a solución optima del subproblema asociado. (Para llegar a una solución
óptima, los pasos intermedios tiene que ser óptimo; así demostraremos la corrección del algoritmo recursivo y también de
la programación dinámica).
6.2.3. Algoritmos voraces: Es quizás el método de diseño más sencillo que además se puede aplicar a una gran variedad de
problemas. La mayoría de estos problemas tienen n entradas y lo que se pretende es obtener un subconjunto que satisfaga
ciertas restricciones. A cualquier subconjunto que satisfaga las restricciones se le llama solución factible. Lo que debemos
encontrar es una solución factible que minimice o maximice una función objetivo dada. A una solución factible que hace esto
se le llama solución óptima. Normalmente hay una forma obvia de determinar una solución factible pero no precisamente
una solución óptima.
El método glotón (voraz o fagocitosis) sugiere un algoritmo que trabaje en etapas, considerando una entrada cada vez.
En cada etapa se toma una decisión mirando si una entrada en particular está o no en una solución óptima. Esto se hace
considerando las entradas en un orden que se determina mediante algún procedimiento de selección. Si la inclusión de la
siguiente entrada en la solución parcial óptima que se está construyendo da lugar a una solución no factible, entonces esta
entrada no se añade a la solución parcial. El procedimiento de selección está basado en alguna medida de optimización.
Esta medida puede ser o no la función objetivo. De hecho, para un mismo problema se pueden considera r diferentes
medidas de optimización aunque la mayoría de ellas dará lugar a algoritmos que generan soluciones no óptimas.
6.2.4. Vuelta atrás: El Backtracking es un método general de resolución de problemas aplicable cuando:
a) La solución se expresa mediante una tupla de valores (x 1, x2,..., xn) /x isi .xi representa una decisión del conjunto finito
de decisiones Si.
b) La solución no debe violar ciertas restricciones. Para ello debe ser posible determinar cuando una tupla es una solución
factible (no viola las restricciones).
c) Puesto que la solución la vamos a ir construyendo paso a paso, debe ser posible determinar cuando una tupla a medio
construir (x1, x2, ... ,xk) puede o no conducir a una solución factible.
Se pretende encontrar:
- una solución factible cualquiera o bien
- todas las soluciones factibles o bien
- la mejor solución factible según un cierto criterio de optimización solución óptima.
Backtracking organiza la búsqueda de las soluciones recorriendo un árbol. Cada nodo representa una tupla parcial, será un
estado de construcción de la solución. Los descendientes de un nodo representan las ampliaciones posibles de la tupla que
representan, las ampliacion es que conducen a tuplas no factibles no se realizan (mecanismo de poda).

6.3. OTRAS TÉCNICAS:


 Algoritmos paralelos: permiten la división de un problema en subproblemas de forma que se puedan ejecutar de forma
simultánea en varios procesadores.
 Algoritmos probabilísticos: algunos de los pasos de este tipo de algoritmos están en función de valores pseudoaleatorios.
 Algoritmos determinísticos: sus pasos están perfectamente definidos y aportan una solución exacta.
 Algoritmos no determinísticos
 Metaheurísticas: encuentran soluciones aproximadas (no óptimas) a problemas basándose en un conocimiento anterior (a
veces llamado experiencia) de los mismos.
 Programación dinámica: intenta resolver problemas disminuyendo su coste computacional aumentando el coste espacial.
 Ramificación y acotación: se basa en la construcción de las soluciones al problema mediante un árbol implícito que se recorre
de forma controlada encontrando las mejores soluciones.

7. VERIFICACIÓN: Verificar un algoritmo consiste en demostrar que el algoritmo es correcto. Pero para saber si un algoritmo es
correcto tenemos que tener algo que nos diga que debería hacer el algoritmo, para poder comprobar si, en efecto, lo hace. Esto que
nos indica lo que hace el programa es lo que se llama "especificación".
Podríamos determinar si un algoritmo funciona o no probándolo para todos los casos posibles (validación por pruebas), pero esto
en la mayoría de las ocasiones es imposible (porque el conjunto de valores de prueba puede ser infinito), de modo que lo que se hace
es demostrarlo matemáticamente (verificación formal).
Las especificaciones de un programa se dividen en dos: precondiciones (Q) y post condiciones (R):
{Q} S {R} donde "S" es el segmento de código del programa.
La precondición (Q) nos dice bajo qué condiciones nuestro algoritmo va a funcionar, y la postcondición (R) nos dice qué relación
va a existir entre nuestros datos una vez que el algoritmo termine.
Ejemplo para el algoritmo que calcula la división entera, tendríamos:
{Q} = {b <> 0}
Algoritmo DivisionEntera(Entradas a, b : entero; Salidas q, r : entero)
{R} = {(a = b * q + r) AND (r < b) AND (r >= 0)}
Existen tres métodos para demostrar que un algoritmo es correcto:
 Método estático: Se consideran el algoritmo y las especificaciones lo que hace es comprobar que el comportamiento
lógico del algoritmo no las contradicen.
 Método dinámico: A partir de las especificaciones deduce el algoritmo.
 Verificación en paralelo: Consiste en escribir el algoritmo a partir de las especificaciones, comprobándolo fragmento a
fragmento. (Combinación del método dinámico y estático)

Você também pode gostar