Please enable JavaScript.
Coggle requires JavaScript to display documents.
Verificación, Diseño y Análisis de Algoritmos - Coggle Diagram
Verificación, Diseño y Análisis de Algoritmos
- I. Verificación Formal de Programas (Fundamentos)
- Demostrar matemáticamente la corrección de un programa con respecto a una especificación formal.
- Garantizar que las salidas del programa son las esperadas para todas las entradas válidas.
- Es especialmente útil en aplicaciones críticas, donde los errores pueden tener costos incalculables (aviación, finanzas, sistemas médicos, etc.).
- Se presenta como una alternativa a las pruebas (testing), ya que estas no pueden garantizar al 100% la ausencia de errores, solo evidenciar su presencia.
- Figura Clave: Sir Charles Antony Richard Hoare (Tony Hoare)
- Autor del artículo seminal "An axiomatic basis for computer programming" (1969), que sentó las bases de la verificación formal.
- Creador de la Lógica de Hoare.
- Otras contribuciones importantes a la computación incluyen los algoritmos Quicksort y Quickselect.
- Fue galardonado con el Premio A. M. Turing en 1980.
- Fundamento Principal: Lógica de Hoare
- Elemento Central: Triplas de Hoare (Ternas de Hoare)
-
- Q (Precondición): Un predicado que define los estados iniciales que se consideran válidos antes de ejecutar el programa S. Restringe los valores iniciales de las variables.
- S (Programa/Comando): El código o la secuencia de instrucciones que se va a verificar. Transforma un estado inicial en un estado final.
- R (Poscondición): Un predicado que define los estados finales aceptados una vez que S ha terminado. Restringe los valores finales de las variables y los relaciona con los valores iniciales.
- Validez de una Tripla: Una tripla es válida si al ejecutarse S en un estado que satisface Q, el programa termina y el estado final resultante satisface R. No es válida si el programa no termina o si el estado final no cumple R.
- Especificación de un Programa: El par de predicados (Q, R) establece QUÉ debe hacer el programa (el contrato), no el CÓMO lo hace.
- Conceptos Relacionados con Estados y Variables
- Estado del Programa: El conjunto de valores de todas las variables en un momento dado.
- Variables Semánticas: Variables que no son parte del programa y no son modificadas por él. Se usan en la poscondición (R) para referirse a los valores INICIALES de las variables de entrada. Por convención, se usan mayúsculas.
- Propiedades de las Triplas de Hoare
- Ley del Milagro Excluido: {Q} S {falso} es válida si y solo si [Q ≡ falso].
- Fortalecimiento de la Precondición: Si [P ⇒ Q] es válido y {Q} S {R} es válida, entonces {P} S {R} también es válida.
- Debilitamiento de la Poscondición: Si [R ⇒ P] es válido y {Q} S {R} es válida, entonces {Q} S {P} también es válida.
- Conjunción de Poscondiciones: Si {Q} S {R} y {Q} S {P} son válidas, entonces {Q} S {R ∧ P} es válida.
- Disyunción de Precondiciones: Si {Q} S {R} y {P} S {R} son válidas, entonces {Q ∨ P} S {R} es válida.
- II. Técnicas de Verificación Formal
- A. Verificación de Instrucciones Simples y Secuenciales (Cálculo de Precondición más Débil - wp)
- Definición de wp(S,R): Es el predicado más débil (el más general) que, de ser cierto antes de ejecutar S, garantiza que S terminará en un estado que satisface R.
- Regla de Demostración: Para probar {Q} S {R}, basta con demostrar la implicación [Q ⇒ wp(S, R)].
- Reglas de Cálculo por Tipo de Instrucción
- Instrucción Vacía (
<vacío>): wp(<vacío>, R) ≡ R.
- Asignación (
x := E): wp(x := E, R) ≡ R(x:=E) (R con x reemplazada por E).
- Composición Secuencial (
S1; S2): wp(S1; S2, R) ≡ wp(S1, wp(S2, R)).
- Instrucción Condicional (
if C then A else B): wp(if C then A else B, R) ≡ ((C ⇒ wp(A,R)) ∧ (¬C ⇒ wp(B,R))).
- B. Verificación de Programas Iterativos (Ciclos
while)
- Desafío: Calcular la
wp de un ciclo es impráctico, pues conduce a una relación de recurrencia compleja.
- Solución Principal: Teorema de Invarianza
- Invariante de Ciclo (P): Un predicado que se satisface justo antes de la primera iteración y se mantiene satisfecho después de cada iteración. Debe contener información relevante del estado.
- Cota de Ciclo (t): Una función con valor entero que disminuye con cada iteración y está acotada inferiormente (ej.
t ≥ 0). Sirve para garantizar la terminación del ciclo.
- Obligaciones de Prueba (Reglas del Teorema)
- Inicialización: El invariante P se cumple al inicio del ciclo:
{Q} <inicialización> {P}.
- Mantenimiento: El invariante P se mantiene después de cada iteración:
{P ∧ <condición>} <instrucciones> {P}.
- Corrección Final: Al finalizar el ciclo, el invariante y la negación de la condición implican la poscondición:
[(P ∧ ¬<condición>) ⇒ R].
- Terminación (Progreso): La cota del ciclo disminuye en cada iteración:
{P ∧ <condición> ∧ t=T} <instrucciones> {t < T}.
- Terminación (Acotamiento): La cota está acotada inferiormente:
[P ∧ <condición> ⇒ t ≥ Tmin].
- III. Paradigmas de Diseño de Algoritmos y su Verificación
- A. Dividir y Conquistar (Divide and Conquer)
- Concepto: Técnica que divide un problema en uno o más subproblemas de idéntica naturaleza y menor tamaño. Las soluciones se combinan para resolver el problema original. Es un enfoque inherentemente recursivo.
- División: El problema se divide en subproblemas. El número de subproblemas puede ser mayor a dos, como en el algoritmo de Strassen que lo divide en siete.
- Conquista: Los subproblemas se resuelven de forma independiente, usualmente con llamadas recursivas hasta alcanzar un caso base trivial.
- Combinación: Las soluciones de los subproblemas se unen para componer la solución del problema original. Esta suele ser la fase más importante del algoritmo.
- Análisis de Complejidad (Teorema Maestro)
- Ecuación de Recurrencia: El análisis de eficiencia de un algoritmo D&C resulta en una ecuación de la forma general
T(n) = aT(n/b) + f(n).
-
a: número de subproblemas.
-
n/b: tamaño de cada subproblema.
-
f(n): costo de las fases de división y combinación.
- Casos del Teorema: Para una recurrencia de la forma
T(n) = aT(n/b) + Θ(n^c).
- Si
c < log_b(a), entonces T(n) = Θ(n^(log_b(a))).
- Si
c = log_b(a), entonces T(n) = Θ(n^c * log n).
- Si
c > log_b(a), entonces T(n) = Θ(n^c).
- Enfoque de Verificación: La corrección se demuestra formalmente mediante inducción matemática sobre el tamaño del problema, debido a su naturaleza recursiva.
- Concepto: Técnica que permite evaluar eficientemente ecuaciones de recurrencia, especialmente cuando la recursión directa produce algoritmos ineficientes (generalmente exponenciales) por repetir cálculos.
- Enfoque "Hacia Arriba" (Bottom-up)
- Se resuelven primero los problemas más pequeños (casos base).
- A partir de ellos, se construyen las soluciones a subproblemas cada vez más grandes hasta obtener la solución original.
- Uso de Estructura de Datos (Memorización): Se utiliza una tabla o arreglo para mantener los valores ya calculados y así evitar realizarlos de nuevo.
- Evaluación de menor a mayor: Se evalúan los subproblemas en un orden que garantiza que las soluciones necesarias ya han sido calculadas.
- Enfoque de Verificación: La estructura típicamente iterativa de estos algoritmos los hace candidatos ideales para la verificación formal mediante el Teorema de Invarianza.
- IV. Casos de Estudio y Aplicación
- Algoritmos de Ordenamiento
- Ordenamiento por Burbuja y Selección: Algoritmos sencillos pero de complejidad cuadrática, cuya utilidad práctica es muy limitada. Su verificación formal es un buen ejercicio introductorio.
- Ordenamiento por Mezcla (Merge Sort):
- Ejemplo canónico de Dividir y Conquistar.
- Divide el arreglo en dos mitades, las ordena recursivamente y las combina.
- Su ecuación de recurrencia es
T(n) = 2T(n/2) + Θ(n), lo que resulta en una complejidad de Θ(n log n).
- Algoritmos de Programación Dinámica
- La implementación recursiva directa
fib(n-1) + fib(n-2) es exponencial.
- La solución con PD usa un arreglo
F para almacenar resultados intermedios, logrando una complejidad lineal Θ(n).
- Calcula el mínimo de cambios (inserción, eliminación, sustitución) para transformar una cadena en otra.
- La solución con PD usa una matriz
(m+1)x(n+1) para almacenar las distancias de los prefijos de las cadenas.
-