Algoritmo para o problema de paridade de prefixo


8

O problema de paridade do prefixo pode ser definido da seguinte maneira. Você recebe uma sequência de comprimento e, inicialmente, cada caractere é . Então você deseja criar uma estrutura de dados que possa suportar atualizações como a seguir.Sn0 0

  1. Para um dado mudança para um ou outro ouEuS[Eu]0 01
  2. para um determinado encontrar a paridade de .EuS[1]+S[2]+...+S[Eu]

Do alto da minha cabeça, há uma solução que pode suportar esse tipo de consulta em tempo , enquanto utiliza apenas espaço linear e tempo de pré-processamento linear para construir a estrutura de dados. A idéia é construir uma árvore de pesquisa binária completa no topo da string, onde as folhas correspondem a caracteres individuais de e em cada nó interno armazenamos a soma de todos os caracteres que são folhas na subárvore definida por esse nó. Dessa forma, podemos suportar trivialmente as duas atualizações em .O(registron)SO(registron)

No entanto, encontrei um documento comprovando um limite inferior para esse problema, afirmando que você não pode fazer melhor que para as atualizações, e também encontrei o seguinte artigo http://link.springer.com/chapter/10.1007%2F3-540-51542-9_5 e um link direto para o pdf , fornecendo um algoritmo para atingir esse limite, sendo, portanto, ideal.O(registronregistroregistron)

Eu gostaria de entender esse algoritmo, no entanto, a explicação é como 1 página e faltam muitos detalhes.

Então, eu queria saber se existe alguma outra fonte sobre esse problema, porque acho muito difícil encontrar alguma, ou essa é a única fonte disponível?

Agradeço antecipadamente

Respostas:


9

Fiz uma rápida leitura do artigo que você vinculou. Com base nas idéias dadas nesse documento, aqui está uma estrutura de dados simples que obtém umaO(registronregistroregistron) tempo limite em cada operação.

Você mencionou na sua pergunta que pode usar árvores aumentadas e equilibradas para acelerar isso. Em particular, se você tiver uma árvore binária e aumentar cada nó com a paridade de sua subárvore esquerda, poderá fazer atualizações e pesquisas no tempoO(registron)cada. Isso é rápido, mas não rápido o suficiente.

Agora, considere a seguinte generalização da sua ideia. Suponha que, em vez de usar uma árvore binária, usamos uma árvore de múltiplas vias com fator de ramificaçãok. Aumentamos cada chave em cada nó com a paridade de todas as subárvores anteriores (isso generaliza a idéia de armazenar a paridade da subárvore esquerda). Agora, vamos pensar em como faríamos uma pesquisa ou atualização nesta árvore. Para fazer uma pesquisa, usamos uma versão ligeiramente modificada do algoritmo de pesquisa em árvore binária de antes: caminhe do topo da árvore para baixo, a cada passo acumulando a paridade da subárvore apenas à esquerda de cada nó. A altura da árvore neste caso seráO(registrokn) e nós fazemos O(1) trabalhar por nó, portanto, o custo de fazer uma pesquisa será O(registrokn).

No entanto, com essa configuração, o custo de fazer uma atualização aumenta. Em particular, se mudarmos a paridade de um elemento, precisamos subir da parte inferior da árvore para o topo, alterando a paridade armazenada de cada chave em cada nó no caminho para cima. temk chaves por nó e O(registrokn) nós no caminho para cima a partir das folhas, portanto, o custo de executar uma operação como essa será O(kregistrokn)=O(kregistrokregistron), que é muito lento. Se pudéssemos de alguma forma eliminar esse extrak prazo, então estaríamos no negócio.

O insight do artigo é o seguinte. Se você pensa sobre o nosso problema inicial, tivemos uma variedade de tamanhosne queria poder calcular paridades de prefixo. Agora temos umkárvore -ary onde, em cada nó, precisamos resolver o problema de paridade de prefixo em matrizes de tamanho kcada, pois cada nó armazena em cache informações sobre as camadas abaixo dele. Na estrutura de dados acima, resolvemos o problema de paridade de prefixo em cada nó apenas armazenando uma matriz das paridades de prefixo, o que significa que, se precisarmos realizar uma atualização, o custo seráO(k). O insight do artigo é que, usando uma estrutura de dados mais inteligente em cada nó, é possível executar essas atualizações de maneira significativamente mais eficiente.

Em particular, o artigo apresenta as seguintes informações. Vamos supor queké "pequeno", para alguma definição de pequeno que escolheremos mais adiante. Se você deseja resolver o problema de paridade de prefixo em uma matriz de tamanhok, então existem apenas 2k diferentes matrizes de bits possíveis de comprimento k. Além disso, existem apenask possíveis consultas de pesquisa que você pode fazer em um tamanho pequeno k. Como resultado, o número de combinações possíveis de uma matriz e uma consulta ék2k. Se escolhermoskpara ser pequeno o suficiente, podemos tornar essa quantidade tão pequena que se torna possível pré-calcular o resultado de toda matriz possível e de toda consulta possível. Se fizermos isso, podemos atualizar nossa estrutura de dados da seguinte maneira. Em cada nó dok, em vez de cada chave armazenar a paridade de sua subárvore esquerda, armazenamos uma matriz de kbits, um para cada chave no nó. Quando queremos encontrar a paridade de todos os nós à esquerda doEufilho, apenas fazemos uma pesquisa em uma tabela indexada por aqueles k bits (tratados como um número inteiro) e o índice Eu. Desde que possamos calcular esta tabela com rapidez suficiente, isso significa que fazer uma consulta de paridade de prefixo ainda levará tempoO(registrokn), mas agora as atualizações levam tempo O(registrokn) também porque o custo de uma consulta de paridade de prefixo em um determinado nó será O(1).

Os autores do artigo notaram que, se você escolher k=lgn2, o número de possíveis consultas que podem ser feitas é lgn22lgn2=lgn2n=o(n). Além disso, o custo de executar qualquer operação na árvore resultante seráO(registrokn)=O(registronregistrolgn2)=O(registronregistroregistron). O problema é que agora você precisa fazero(n)pré-computação no início da configuração da estrutura de dados. Os autores fornecem uma maneira de amortizar esse custo usando uma estrutura de dados diferente para as consultas iniciais até que seja feito trabalho suficiente para justificar a execução do trabalho necessário para configurar a tabela, embora você possa argumentar que precisa gastarO(n) tempo construindo a árvore em primeiro lugar e isso não afetará o tempo de execução geral.

Então, em resumo, a ideia é a seguinte:

  • Em vez de usar uma árvore binária aumentada, use um valor aumentado karvore.
  • Observe que com pequenas k, tudo possível klistas de bits e consultas nessas listas podem ser pré-computadas.
  • Use essa estrutura de dados pré-computada em cada nó da árvore.
  • Escolher k=lgn2 para aumentar a altura da árvore e, portanto, o custo por operações, O(registronregistroregistron).
  • Evite o custo inicial de pré-computação usando uma estrutura de dados de substituição temporária em cada nó até que a pré-computação se torne válida.

Em suma, é uma estrutura de dados inteligente. Obrigado por fazer esta pergunta e vinculá-la - eu aprendi muito no processo!

Como adendo, muitas das técnicas incluídas nessa estrutura de dados são estratégias comuns para acelerar soluções aparentemente ótimas. A idéia de pré-computar todas as consultas possíveis em objetos de tamanho pequeno é frequentemente chamada de Método dos Quatro Russos e pode ser vista em outras estruturas de dados, como a estrutura de dados de Fischer-Heun para consultas mínimas de alcance ou o algoritmo decremental para conectividade em árvore. Da mesma forma, a técnica de usar árvores multivias balanceadas aumentadas com um fator de ramificação logarítmico aparece em outros contextos, como a estrutura determinística original dos dados para conectividade dinâmica de gráficos, em que essa abordagem é usada para acelerar consultas de conectividade deO(registron) para O(registronregistroregistron).

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.