Remoção de duplicatas de forma eficiente e com pouca sobrecarga de memória


9

Desejo filtrar com eficiência uma lista de números inteiros para duplicatas, de maneira que apenas o conjunto resultante precise ser armazenado.

Uma maneira de isso ser visto:

  • nós temos um intervalo de números inteiros S={1,…,N} comN grande (por exemplo,240 )
  • temos uma função f:S→S com, supostamente, muitas colisões (as imagens são distribuídas uniformemente em S )
  • então precisamos armazenar , que é { f ( x ) | x ∈ S }f[S]{f(x)|x∈S}

Eu tenho uma estimativa bastante precisa (probabilística) do que é e, portanto, pode alocar estruturas de dados com antecedência (digamos | f [ S ] | ≈ 2 30 ).|f[S]||f[S]|≈230

Eu tive algumas idéias, mas não tenho certeza qual seria a melhor abordagem:

  • um bitset está fora de questão porque o conjunto de entradas não cabe na memória.
  • uma tabela de hash, mas (1) requer alguma sobrecarga de memória, digamos 150% de e (2) a tabela deve ser explorada quando criada, o que requer tempo adicional devido à sobrecarga da memória.|f[S]|
  • uma classificação "on the fly", de preferência com complexidade (classificação sem comparação). Com relação a isso, não tenho certeza de qual é a principal diferença entre a classificação de bucket e o flashsort .O(N)
  • uma matriz simples com uma árvore de pesquisa binária, mas isso requer tempo .O(Nlog⁡|f[S]|)
  • talvez o uso de filtros Bloom ou uma estrutura de dados semelhante possa ser útil no relaxamento (com falsos positivos) do problema.

Algumas perguntas sobre o stackoverflow parecem abordar esse tipo de coisa ( /programming/12240997/sorting-array-in-on-run-time , /programming/3951547/java -array-find-duplicates ), mas nenhum parece corresponder aos meus requisitos.


2
Você precisa enumerar f [S] (seja o que for) ou ser capaz de saber rapidamente se algum x está nele?
— Gilles 'SO- stop be evil'

@ Gilles: Eu acredito que, como nenhuma estrutura óbvia pode ser encontrada em f [S], as duas soluções são equivalentes.
— doc

Seus números não somam. A imagem esperada de uma função aleatória sobre um domínio de tamanho é aproximadamente ( 1 - 1 / e ) N . Outra questão é que a execução de 2 56 levará muito tempo, a menos que você tenha um supercomputador ou um cluster grande à sua disposição. N(1−1/e)N256
— Yuval Filmus

11
O tempo para a árvore de pesquisa binária seria , que pode ou não estar próximo de O ( N log N ) na prática, mas ainda é mais preciso. O(Nlog⁡|f[S]|)O(Nlog⁡N)
— Jmad

11
Com , um algoritmo de tempo linear também não será proibitivo? (Dos meus cálculos, mesmo se você considerar um elemento de S em 1 nanossegundo, você levaria bons 2 anos!). N∼256S
— Aryabhata

Respostas:


1

Por que não bin e chain?

A idéia é armazenar números inteiros positivos representáveis ​​por bits em uma matriz A de 2 k entradas representando intervalos de valores: entrada A [ y ] , y ≥ 0 , representa o intervalo [ 2 m y , 2 m ( y + 1 ) - 1 ] . Para qualquer 1 ≤ x < 2 n, podemos escrever x = 2 m yn=k+mA2kA[y]y≥0[2my,2m(y+1)−1]1≤x<2n onde y possui k bits e z possui m bits. Tente armazenar z (não x !) No local y :x=2my+zykzmzxy

  • Quando já, não faça nada: x é uma duplicata.A[y]=zx

  • Quando não for inicializado, armazene z em A [ y ] .A[y]zA[y]

  • Caso contrário, armazene um índice em uma matriz separada usada para encadear os (que colidiram em y ) nas listas vinculadas. Você terá que pesquisar linearmente a lista encabeçada por A [ y ] e, dependendo do que a pesquisa descobrir, potencialmente inserir z na lista.zyA[y]z

No final, é fácil recuperar fazendo um loop pelas entradas inicializadas de A e - apenas concatenando duas seqüências de bits - remontando cada z encontrado no local y (diretamente ou dentro de uma cadeia referenciada) no original valor x = 2 m y + z .f(S)Azyx=2my+z

Quando a distribuição é quase uniforme e excede N , não haverá muito encadeamento (isso pode ser avaliado da maneira usual) e as cadeias tendem a ser curtas. Quando a distribuição não é uniforme, o algoritmo ainda funciona, mas pode atingir o tempo quadrático. Se for possível, use algo mais eficiente do que cadeias (e pague um pouco mais pelo armazenamento).2kN

O armazenamento necessário é de no máximo bits para A e 2 2 k bits para as cadeias (assumindo m ≤ k ). Este é exatamente o espaço necessário para armazenar 2 k valores de n bits cada. Se você está confiante na uniformidade, pode subalocar o armazenamento para as cadeias. Se a não uniformidade for uma possibilidade, convém aumentar k e defender totalmente o armazenamento em cadeia.2nA22km≤k2knk

Uma forma alternativa de pensar acerca desta solução é que ela é uma tabela hash com uma função hash particularmente agradável (tomar a bits mais significativos) e, por causa disso, precisamos apenas de armazenar os menos significativos m = n - k bits a mesa.km=n−k

Existem maneiras de sobrepor o armazenamento das cadeias com o armazenamento de mas isso não parece incomodar, porque não economizaria muito (supondo que m seja muito menor que k ) de espaço e dificultaria o desenvolvimento do código, depurar e manter.Amk


11
Penso que o penúltimo parágrafo é o ponto central aqui e provavelmente deve estar no topo (como ideia). Não conheço o termo "bin and chain" (embora faça sentido depois de ler o post). Essa idéia pode ser estendida para tentativas .
— Raphael

Θ(n2)

@einpoklum Esta resposta descreve explicitamente as condições em que a solução é eficiente.
— Whuber
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.