# Algoritmo probabilístico

Um algoritmo probabilístico é um algoritmo que utiliza a probabilidade como parte de sua lógica. Na prática, isso significa que a máquina que implementa o algoritmo deve acessar um gerador de números pseudo-aleatórios. O algoritmo utiliza bits aleatórios como um guia para o seu comportamento. Diferente dos algoritmos convencionais, um algoritmo probabilístico, dada uma mesma sequência de entrada, não necessariamente leva a um mesmo estado final.

## Definição

Para demonstrar os exemplos a seguir deve-se assumir um modelo. Um computador inicia seu trabalho sempre num estado inicial $Q_{0}$ e, dado uma seqüência de símbolos de entrada, esta máquina passará a outros estados. Numa máquina clássica não-probabilística (determinista), as transições dependem apenas da seqüência de símbolos, ou seja, dado um estado $Q_{n}$, a transição deste para um outro estado é sempre a mesma dado o recebimento do mesmo símbolo.
Em um algoritmo probabilístico, uma mesma seqüência de entrada não leva sempre a um mesmo estado final de computação. Isso acontece porque as transições entre estados dependem além do estado atual e do símbolo recebido, também de uma escolha aleatória. Imagine, num caso simplificado que, além de ler um símbolo para decidir o próximo passo de computação, a máquina ainda "lance uma moeda" para decidir se passa ou não ao próximo estado.

## Motivação

O estudo de algoritmos computacionais geralmente busca soluções baseadas nos resultados do pior caso. Isso significa que uma solução é classificada dado o seu desempenho na execução de uma tarefa no seu pior caso. Mas em vários outros problemas, o estudo do desempenho no caso médio já é o suficiente. Ou seja, quando um algoritmo geralmente resolve um problema melhor que qualquer outro. Estas soluções podem até mesmo ter uma probabilidade pequena de retornar respostas erradas. Para esses casos os algoritmos probabilísticos podem ser bastante úteis.
Para ilustrar esta motivação pode-se usar o exemplo de uma busca. Dado um vetor de tamanho $n$ preenchido uniformemente com os elementos $\lbrace a,b\rbrace$, o problema consiste em encontrar um $a$ dentro do mesmo. A forma mais óbvia de executar tal busca é verificar cada uma das posições do vetor. Usando este algoritmo verificaremos, no pior caso da entrada (vetor ordenado), $n/2$ posições. A verdade é que nenhum algoritmo determinístico termina esta tarefa mais rápido que isso para todos os casos de entrada.
Neste mesmo problema, pode-se fazer uso de um algoritmo probabilístico muito simples para melhorar este resultado. Caso pesquisemos aleatoriamente as posições do vetor, teremos uma alta probabilidade de encontrar rapidamente o valor desejado para qualquer que seja a entrada. Resta apenas uma pequena probabilidade de que a o fator aleatório demore para terminar a busca, mas isso independe da entrada.

## Programação

Uma máquina probabilística pode ser vista como uma particularidade das máquinas não determinísticas. O não-determinismo implica que a máquina pode seguir vários caminhos dados o par: estado $S_{n}$ e símbolo de entrada $X$. A diferença deste modelo para o da máquina probabilística é que este último escolhe aleatoriamente o caminho a seguir, enquanto aquele, ao menos teoricamente, busca o melhor caminho dentro de todas as possibilidades.
Para a implementação de algoritmos probabilísticos uma importante definição é a instrução de atribuição aleatória, $x:=random(S)$. Esta instrução diz respeito a escolha aleatória de um elemento do conjunto $S$ para a atribuição da variável $x$.

## Modelos
### Autômatos finitos probabilísticos

Não existe apenas uma representação para a teoria de autômato finito. Uma delas apresenta um modelo baseado em matrizes que é particularmente interessante, neste caso, pois a evolução da representação de autômatos finitos para autômatos finitos probabilísticos é muito clara. Seguem as três principais características:
* Para cada símbolo $x$ do alfabeto existe uma matriz de transição $M_{x}$ que expressa as probabilidades de transição entre estados dado a leitura do símbolo $x$;
* Os estados são representados através de vetores coluna;
* Pode-se calcular a aceitabilidade de uma palavra xyz com a seguinte fórmula: $P_{(xyz)}=q_{inicial}^{T}\cdot M_{x}\cdot M_{y}\cdot M_{z}\cdot q_{final}$.
Os autômatos finitos são um caso particular dos autômatos finitos probabilísticos para transições com probabilidade 0 (para transição não possível) ou 1 (para transição possível). Ou seja, a decisão é executada com 100% de certeza. Vejamos um exemplo:

#### No caso do autômato finito

Um autômato que reconhece as palavras binárias com o formato 00*11*00* pode ser escrito assim:
Os estados: $q_{0}=q_{inicial}={\begin{bmatrix}1\\0\\0\\0\end{bmatrix}};q_{1}={\begin{bmatrix}0\\1\\0\\0\end{bmatrix}};q_{2}={\begin{bmatrix}0\\0\\1\\0\end{bmatrix}};q_{3}=q_{final}={\begin{bmatrix}0\\0\\0\\1\end{bmatrix}}$
As matrizes de transição: $M_{0}={\begin{bmatrix}0&1&0&0\\0&1&0&0\\0&0&0&1\\0&0&0&1\\\end{bmatrix}};M_{1}={\begin{bmatrix}0&0&0&0\\0&0&1&0\\0&0&1&0\\0&0&0&0\\\end{bmatrix}}$
Um exemplo de aceitação e rejeição:
ACEITAÇÃO:
$P_{(010)}={\begin{pmatrix}1&0&0&0\end{pmatrix}}\cdot M_{0}M_{1}M_{0}\cdot {\begin{pmatrix}0\\0\\0\\1\end{pmatrix}}={\begin{pmatrix}0&1&0&0\end{pmatrix}}\cdot M_{1}M_{0}\cdot {\begin{pmatrix}0\\0\\0\\1\end{pmatrix}}={\begin{pmatrix}0&0&1&0\end{pmatrix}}\cdot M_{0}\cdot {\begin{pmatrix}0\\0\\0\\1\end{pmatrix}}={\begin{pmatrix}0&0&0&1\end{pmatrix}}{\begin{pmatrix}0\\0\\0\\1\end{pmatrix}}=1$
REJEIÇÃO:
$P_{(10)}={\begin{pmatrix}1&0&0&0\end{pmatrix}}\cdot M_{1}M_{0}\cdot {\begin{pmatrix}0\\0\\0\\1\end{pmatrix}}={\begin{pmatrix}0&0&0&0\end{pmatrix}}\cdot M_{0}\cdot {\begin{pmatrix}0\\0\\0\\1\end{pmatrix}}={\begin{pmatrix}0&0&0&0\end{pmatrix}}{\begin{pmatrix}0\\0\\0\\1\end{pmatrix}}=0$

#### No caso do autômato finito probabilístico

O mesmo modelo acima pode ser modificado para um autômato finito probabilístico mudando apenas as matrizes de possibilidades para que seja de probabilidades. Com isso ao invés de receber apenas os valores 0 e 1, podem existir valores no intervalo [0,1] desde que as linhas somem sempre 1, ou seja, as probabilidades de execução em um dado estado e recebido um símbolo é sempre 1.
Podemos modificar as matrizes de transição da seguinte forma:
$M_{0}={\begin{bmatrix}0.2&0.8&0&0\\0&1&0&0\\0&0&0.1&0.9\\0&0&0&1\\\end{bmatrix}};M_{1}={\begin{bmatrix}0&0&0&0\\0&0.15&0.85&0\\0&0&0.95&0.05\\0&0&0&0\\\end{bmatrix}}$
Os exemplo de aceitação e rejeição passam a não ser mais exatos, mas sim, a retornar uma probabilidade de aceitação:
PROBABILIDADE DE ACEITAÇÃO:
$P_{(010)}={\begin{pmatrix}1&0&0&0\end{pmatrix}}\cdot M_{0}M_{1}M_{0}\cdot {\begin{pmatrix}0\\0\\0\\1\end{pmatrix}}={\begin{pmatrix}0.2&0.8&0&0\end{pmatrix}}\cdot M_{1}M_{0}\cdot {\begin{pmatrix}0\\0\\0\\1\end{pmatrix}}={\begin{pmatrix}0&0.12&0.68&0\end{pmatrix}}\cdot M_{0}\cdot {\begin{pmatrix}0\\0\\0\\1\end{pmatrix}}={\begin{pmatrix}0&0.12&0.068&0.612\end{pmatrix}}{\begin{pmatrix}0\\0\\0\\1\end{pmatrix}}=0.612$
PROBABILIDADE DE ACEITAÇÃO:
$P_{(10)}={\begin{pmatrix}1&0&0&0\end{pmatrix}}\cdot M_{1}M_{0}\cdot {\begin{pmatrix}0\\0\\0\\1\end{pmatrix}}={\begin{pmatrix}0&0&0&0\end{pmatrix}}\cdot M_{0}\cdot {\begin{pmatrix}0\\0\\0\\1\end{pmatrix}}={\begin{pmatrix}0&0&0&0\end{pmatrix}}{\begin{pmatrix}0\\0\\0\\1\end{pmatrix}}=0$
E agora passamos a ter uma possibilidade de erro na aceitação da linguagem inicialmente descrita 00*11*00*

### Máquinas de Turing probabilísticas

Uma Máquina de Turing Probabilística $M$ é um tipo de Máquina de Turing não-determinística que possui passos de transição chamados de lançamento-de-moeda, dando a máquina duas possibilidades a cada transição.
Se na computação de uma entrada $w$ é gerado o caminho de execução (ramificação) $b$, e neste, foram dados $k$ lançamentos de moeda, então a probabilidade do caminho é dada por: $P[b]=2^{-k}$. Já a probabilidade de aceitação da entrada $w$ é dada por: $P[Maceitaw]=\sum P[b]$, ou seja, a soma de todos os caminhos de execução que aceitam a palavra $w$. Adicionalmente, temos que: $P[Mrejeitaw]=1-P[Maceitaw]$.
A máquina $M$ continua reconhecendo linguagens apenas quando aceita todas as palavras da mesma e rejeita no caso contrário. Mas a uma Máquina de Turing Probabilística é permitido uma pequena probabilidade de erro: $0\leq \epsilon <0.5$. Desta forma, diz-se que $M$ reconhece uma linguagem $A$ com probabilidade de erro $\epsilon$ se:
${\begin{cases}w\in A\to P[Maceitaw]\geq 1-\epsilon \\w\notin A\to P[Mrejeitaw]\geq 1-\epsilon \end{cases}}$

## Ganhos (complexidade)
### BPP

Classe das linguagens que são reconhecidas por uma máquina de Turing probabilística (em tempo polinomial) com um erro no interval [0, 0.5). Este erro pode ser diminuído exponencialmente utilizando o lema da aplicaficação. Este lema diz que para toda máquina de Turing probabilística (em tempo polinomial = $polin(n)$) $M_{1}$ que opera com erro $\epsilon$, existe uma máquina equivalente $M_{2}$ que opera com uma probabilidade de erro de $2^{-polin(n)}$. Isto pode ser provado dado que $M_{2}$ pode simular a máquina $M_{1}$, executá-la um número polinomial de vezes e fazer uma escolha majoritária entre as respostas computadas. Existe um algoritmo probabilístico para teste de primalidade pertencente a BPP.

### RP

Classe das linguagens que são reconhecidas por uma máquina de Turing probabilística (em tempo polinomial) no qual as entradas pertencentes a linguagem são aceitas com probabilidade de no mínimo 0.5 e entradas não pertencentes a linguagem são rejeitadas com probabilidade 1. Este tipo de erro, denominado erro de um único lado, é muito comum nos algoritmos probabilísticos. Nesta classe também é possível a redução exponencial do erro cometido.

### ZPP

Engloba os problemas que possuem algoritmos que resolvem em tempo polinomial (no caso médio) e dão sempre uma resposta correta, mesmo que possam não parar em alguns casos.

## Aplicações
### Sistemas

* Redes Neurais
* Algoritmos Genéticos

### Problemas

* Aprendizagem computacional
* Reconhecimento de padrões