Algoritmos

Prova de correção por invariantes de laço

  • algoritmos
  • algoritmos iterativos
  • análise de algoritmos
Última atualização: 2022-02-13

Posts Relacionados

Uma invariante de laço é uma propriedade do algoritmo iterativo, em relação às suas variáveis, que deve se manter verdadeira antes, durante e depois da execução do laço. Uma vez que a invariante de laço mantenha-se verdadeira, podemos afirmar que, em relação à propriedade escolhida, o algoritmo está correto.

Sobre a invariante de laço, devemos demonstrar seu estado da em três momentos, para demonstrar a correção do algoritmo:

A prova de correção de algoritmos por invariantes de laço são semelhantes à prova por indução.

Para entender melhor como funcionam as invariantes de laço, três exemplos serão analisados.

Busca Linear

A busca linear, ou sequencial, é um algoritmo de busca exaustiva em listas. O algoritmo verifica cada elemento da lista até encontrar uma correspondência, ou até que não existam mais elementos na lista.

O pseudocódigo para a busca linear pode ser definido como:

linear_search(A, v):
   for i in 1..A.length:
       if A[i] == v:
          return i
   return nil

Nota: arrays possuem índices começando em 1.

O algoritmo possui um único laço, onde a iteração será executada para cada um dos elementos da lista.

A definição da invariante do laço para a busca linear é:

O valor v não está na lista A[1..i-1].

Como a invariante se mantém sempre verdadeira, o algoritmo está correto.

Busca Binária

Apesar de simples, e de encontrar um elemento independente da organização dos dados, a busca linear é ineficiente, uma vez que pode ser necessário analisar todos os elementos da lista.

Caso os elementos de uma lista estejam organizadas em ordem crescente em relação a uma chave k, a busca por elementos nessa lista pode ser mais eficiente se utilizarmos uma busca binária.

Na busca binária em uma lista ordenada de elementos A[1..n], comparamos uma chave k com um elemento da lista A[i], onde 1 <= i <= n. Se k <= A[i], como a lista está ordenada, sabemos que k está no conjunto A[1..i], caso contrário, sabemos que k está no conjunto A[i+1..n].

O pseudocódigo para a busca binária iterativa pode ser definido da seguinte forma:

binary_search(A, k):
    p = 1
    q = A.length
    while p != q:
        m = p + (q - p) / 2
        if k <= A[m]:
            q = m
        else:
            p = m + 1
    if k != A[q]:
        return nil
    return p

A invariante do laço pode ser definida como:

Se k está em A[1..n], então k está na lista A[p..q] que está contida em A[1..n].

Insertion Sort

Para que a busca binária possa ser utilizada é necessário que a lista de elementos esteja ordenada de acordo com uma chave. O problema da ordenação pode ser definido em função da sua entrada e saída esperada:

Entrada: lista de N elementos A = {a1, a2, …, an-1, an}
Saída: lista de N elementos A, tal que A = {a1 <= a2 <= … <= an-1 <= an} e aj pertence a A, para todo j pertencente a [1..n].

Podemos definir o pseudocódigo para o algoritmo Insertion Sort como:

insertion_sort(A):
    for j in 2 to A.length:
        k = A[j]
        i = j - 1
        while i > 0 and A[i] > k:
            A[i + 1] = A[i]
            i = i - 1
        A[i + 1] = k

Nota: arrays possuem índices começando em 1.

O algoritmo é formado por dois laços. O laço externo irá iterar sobre todos os elementos da lista, a partir do segundo elemento, e o laço interno ira iterar sobre todos os elementos anteriores ao i-ésimo elemento.

Para verificar a correção do algoritmo insertion sort, definimos a invariante de laço como:

A cada iteração de laço for, a subsequência A[1..j-1] consiste dos elementos originais em A[1..j-1], porém, ordenados.

E verificamos se a invariante é verdadeira:

Para uma prova de todo o algoritmo, devemos demostrar que o laço interno, o while, está correto:

Invariante do laço: os elementos A[i..j] são maiores ou iguais a k.

Uso de invariantes de laço

Podemos utilizar invariantes de laço para demonstrar se um algoritmo iterativo está correto ou não. Para isso definimos a invariante de laço e demonstramos que é verdadeira na inicialização, manutenção e finalização do laço. Se a propriedade avaliada pela invariante de laço for verdadeira nas três situações, dizemos que o programa está correto segundo a propriedade escolhida.

Referências

  1. Cormen et al. Introduction to Algorithms. MIT Press. 2014.
  2. Robert Sedwick; Kevin Wayne. Algorithms. Addisson-Wesley Professional. 2011.