Sequências e cadeias de matrizes
LCS, distância de edição e parentização de matrizes, com tabelas e reconstrução.
ABCD e BACD partilham ACD como subsequência, conservando a ordem das letras e omitindo uma de cada palavra. Para encontrar uma subsequência comum de comprimento máximo, comparamos prefixos cada vez maiores e guardamos o melhor para cada par. A última letra de cada prefixo determina quais as células anteriores que precisamos de consultar.
A distância de edição usa o mesmo par de prefixos com outro objetivo: contar operações para transformar uma palavra na outra. A cadeia de matrizes usa estados por intervalos para escolher os parênteses sem mudar a ordem dos fatores. As três técnicas separam o que já está decidido do que ainda falta decidir.
Subsequência comum mais longa
Uma subsequência conserva a ordem, mas pode omitir elementos. ACD é subsequência de ABCD; não é um subvetor contíguo. A LCS de duas sequências é uma subsequência comum de comprimento máximo.
Define como o comprimento da LCS dos primeiros símbolos de e dos primeiros de . Os índices são comprimentos, pelo que e são os últimos símbolos desses prefixos.
Quando os últimos símbolos coincidem, existe uma LCS que os emparelha, porque uma LCS que usasse uma ocorrência anterior pode deslocar esse emparelhamento para os últimos sem perder a ordem. Quando diferem, não podem ser os dois o último símbolo comum, por isso pelo menos um fica de fora e as duas exclusões cobrem as alternativas.
Para e :
| vazio | B | A | C | D | |
|---|---|---|---|---|---|
| vazio | 0 | 0 | 0 | 0 | 0 |
| A | 0 | 0 | 1 | 1 | 1 |
| B | 0 | 1 | 1 | 1 | 1 |
| C | 0 | 1 | 1 | 2 | 2 |
| D | 0 | 1 | 1 | 2 | 3 |
O comprimento é 3.
Para reconstruir, regista em cada célula a decisão que atinge o máximo: diagonal quando , senão cima ou esquerda conforme o maior valor. Começa em , onde D coincide: segue a diagonal e guarda D. Em , C coincide e guarda C do mesmo modo. Em , e , um empate entre cima e esquerda: escolher cima encontra A e dá ACD, enquanto escolher esquerda encontra B e dá BCD. Inverte os símbolos guardados no fim.
O empate representa várias soluções ótimas, não um erro. As duas respostas conservam a ordem em ambas as sequências e atingem o mesmo comprimento.
O tempo e a tabela são . Duas linhas chegam para o comprimento, mas deixam de guardar o percurso completo de reconstrução. Uma string vazia tem LCS de comprimento zero com qualquer outra.
Distância de edição
A distância de Levenshtein é o mínimo número de inserções, remoções e substituições para transformar em . Cada operação custa 1; manter um símbolo igual custa 0. Outros custos definem outro problema.
Define para os primeiros símbolos de e os primeiros de . Temos , porque removemos todos, e , porque inserimos todos.
O símbolo entre parênteses retos vale 1 se a condição é verdadeira e 0 se é falsa. As três linhas são as possíveis últimas operações da transformação. Não acrescentes automaticamente 1 à diagonal quando os símbolos já são iguais.
Para canto e gato, a tabela é:
| vazio | g | a | t | o | |
|---|---|---|---|---|---|
| vazio | 0 | 1 | 2 | 3 | 4 |
| c | 1 | 1 | 2 | 3 | 4 |
| a | 2 | 2 | 1 | 2 | 3 |
| n | 3 | 3 | 2 | 2 | 3 |
| t | 4 | 4 | 3 | 2 | 3 |
| o | 5 | 5 | 4 | 3 | 2 |
A distância é 2. Uma transformação ótima substitui c por g e remove n. Guardar a operação escolhida permite reconstruir um alinhamento, incluindo inserções e remoções. O custo é tempo e espaço, ou espaço para apenas a distância.
#include <algorithm>
#include <iostream>
#include <string>
#include <vector>
int main() {
std::string a, b;
if (!std::getline(std::cin, a) || !std::getline(std::cin, b) ||
a.size() > 500 || b.size() > 500) {
std::cout << "entrada invalida\n";
return 0;
}
int n = static_cast<int>(a.size()), m = static_cast<int>(b.size());
std::vector<std::vector<int>> d(n + 1, std::vector<int>(m + 1));
for (int i = 0; i <= n; ++i) d[i][0] = i;
for (int j = 0; j <= m; ++j) d[0][j] = j;
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j)
d[i][j] = std::min({d[i - 1][j] + 1, d[i][j - 1] + 1,
d[i - 1][j - 1] + (a[i - 1] != b[j - 1])});
std::cout << "distancia " << d[n][m] << '\n';
}Executa o código para ver a saída e os gráficos.
Devolve distancia 2. Lê duas linhas, por isso uma linha vazia representa uma string vazia; mudar a entrada para \ngato deve dar 4. O exemplo limita cada linha a 500 bytes e trata bytes como símbolos. Para texto Unicode geral, um carácter pode ocupar vários bytes; seria necessário escolher e implementar uma unidade como pontos de código ou grafemas.
Distância de edição não é LCS com outra designação. Se só inserções e remoções fossem permitidas, o custo mínimo seria . Permitir substituição a custo 1 pode baixar esse valor: a para b custa 1, e não 2.
Cadeia de matrizes
Queremos calcular com o menor número de multiplicações escalares. A ordem das matrizes permanece; mudamos apenas os parênteses. A multiplicação é associativa, mas a matriz intermédia e o custo dependem da parentização.
Se tem dimensões , multiplicar uma matriz por outra custa multiplicações escalares no algoritmo usual. Define como o menor custo para a cadeia .
A última multiplicação divide a cadeia depois de algum . Se um dos lados não estivesse parentizado de forma ótima, substituí-lo por um melhor preservaria as dimensões e reduziria o total. Avaliar todos os encontra a melhor divisão.
Preenche por comprimento crescente do intervalo. Guardar , o vencedor, permite reconstruir recursivamente os parênteses.
Para , , :
- .
- .
- Dividir dá .
- Dividir dá .
Escolhemos , custo 330, e não , custo 960. A resposta da tabela é um plano de cálculo; a tabela não calcula os elementos do produto.
Há intervalos e até divisões por intervalo, portanto tempo e espaço. Com uma matriz o custo é zero. Dimensões incompatíveis tornam a cadeia inválida antes de procurar uma parentização.
Exercícios
Escolher os parênteses
A tem dimensão 8×2, B tem 2×10 e C tem 10×3. Quantas multiplicações escalares usa a melhor parentização?
Pista
Calcula os dois produtos possíveis primeiro e as dimensões que deixam.
Solução
A(BC) custa 60+48=108, com BC de dimensão 2×3. (AB)C custa 160+240=400, com AB de dimensão 8×10. A multiplicação BC produz uma matriz 2×3, e multiplicá-la por A mantém a dimensão intermédia pequena. A ordem A,B,C não muda, apenas a associação.
Erros frequentes
Somar apenas o custo da primeira multiplicação omite o produto final. Trocar A e B não é uma parentização.
Duas medidas diferentes para palavras
Para X=ab e Y=ac, calcula o comprimento da LCS e a distância de edição com substituição de custo 1. Explica por que não podes obter esta distância só pela fórmula de inserções e remoções.
Pista
A subsequência comum pode conservar a; b e c são diferentes.
Mais uma pista
Uma substituição de b por c resolve a transformação. Remover e inserir faria duas operações.
Solução
A LCS tem comprimento 1, por exemplo a. A distância de Levenshtein é 1 por substituir b por c. A fórmula |X|+|Y|-2LCS dá 2 e corresponde à variante que só admite inserções e remoções. Com substituição a custo 1, esse resultado não é a distância pedida.
Confere a tua resposta:
- Obtive LCS de comprimento 1.
- Obtive distância 1 por substituição.
- Expliquei por que a fórmula daria 2 noutra variante.
Erros frequentes
LCS mede uma subsequência, não um conjunto sem ordem. Aplicar uma fórmula de outro conjunto de operações muda o problema.