Estrutura de dados para interseção de conjunto?


19

Existe alguma estrutura de dados que mantenha uma coleção de conjunto (de conjunto de terreno finito) suportando as seguintes operações? Algum tempo de execução sublinear será apreciado?

  1. Inicie um conjunto vazio.
  2. Adicione um elemento a um conjunto.
  3. Com dois conjuntos, relate se eles se cruzam.

1
Essa é uma pergunta muito geral, porque qualquer estrutura de dados pode suportar essas operações com domínio finito. Você poderia ser um pouco mais especifico ? Por exemplo. O que a complexidade que você precisa, o que você está disposto a sacrificar para obter operações de conjunto etc.
Bartosz Przybylski

Respostas:


12

Se cada conjunto mantiver um registro de quais outros conjuntos existem e você tiver um total de s>0 , poderá facilmente transformar qualquer estrutura de dados de uma coleção ( por exemplo , árvores de pesquisa binária, etc. ) em uma onde você possa recuperar um elemento da interseção de dois conjuntos no tempo O(logs) .

  • Cada conjunto deve ter um identificador exclusivo de um conjunto totalmente ordenado. Se você nomear explicitamente seus conjuntos S1,S2, , o identificador poderá ser apenas o índice.

  • Você deve implementar um "registro" dos conjuntos; uma estrutura de dados que mantém uma coleção de todos os conjuntos que você definiu. O registro deve ser implementado como uma estrutura de dados da árvore de pesquisa, para facilitar a recuperação ( por exemplo,  se você deseja excluir o conjunto) e a travessia em tempo linear dos conjuntos.

  • Cada conjunto Sj também mantém um "índice" de cada um dos outros conjuntos - não uma cópia deles, mas uma estrutura de dados que é indexada pelos rótulos dos outros conjuntos. Este índice será usado para manter, para cada conjunto Sk , uma árvore de pesquisa binária de todos os elementos de SjSk . (Os dois conjuntos Sj e Sk compartilham uma cópia dessa árvore de pesquisa.)

Inicialização

A inicialização de um conjunto consiste em operações para inicializar a árvore de seus elementos, operações conforme você inicializa (copiando do registro) o índice do conjunto e operações à medida que você percorre o registro para adicionar nos índices de cada um dos outros conjuntos . No índice de , criamos árvores de pesquisa representando para os outros conjuntos ; o mesmo ponteiro para o índice de .O ( 1 ) O ( s ) T O ( s log s ) T S j T T S j = S j S jT=O(1)O(s)TO(slogs)TSjTTSj=SjSj

Adicionando um elemento a um conjuntoT

Adicionar ao conjunto leva o tempo como de costume, onde. Também testamos a associação de em cada um dos outros conjuntos , o que leva tempo ondeé o tamanho do universo (ou do maior conjunto ) e é o número de conjuntos no registro. Para cada conjunto tais que , também inserção no índice para o conjunto . Para cada um desses conjuntos,T O ( log n T ) n T = | T | x S 1 , S 2 , O ( log n S 1 + log n S 2 + ) O ( s log n ) , n = | V | S j x S jT S jxVTO(lognT)nT=|T|xS1,S2,

O(lognS1+lognS2+)O(slogn),
n=|V|SjS j x S jsSjxSjxSjTSj, leva para procurar no índice de e inserir em ; em todos os conjuntos isso leva tempo . Se supusermos que o número de conjuntos seja muito menor que o tamanho do universo (ou seja, se supusermos ), o tempo total para inserção do elemento será .S j T x S jO(logs+lognT)SjTxS 1 , S 2 , O ( s log s + s log n T ) S j VSjTS1,S2,O(slogs+slognT)SjVO ( s log n )snO(slogn)

Se você não permitir duplicatas em conjuntos, podemos poupar tempo no caso em que já renunciando o teste de associação e inserções para os outros conjuntos . "Inserção", caso já esteja presente, leva tempo apenas .TxSTO ( log n T )xO(lognT)

Teste de interseção

O índice de cada conjunto é mantido precisamente para permitir uma avaliação rápida da interseção entre dois conjuntos e . Para um conjunto , simplesmente verificando seu índice para o conjunto , não podemos apenas determinar no tempo se cruza ou não , mas também podemos recuperar uma árvore binária que contém todo o conjunto .S K S j S K S ( log s ) S jSjSkSjSkO(logs)SjS jS kSkSjSk

Remoção de elemento

Para excluir um elemento de um conjunto , nós o removemos não apenas da árvore de pesquisa de si, mas de cada uma das interseções dos conjuntos em seu índice. Isso leva tempo , onde.T T S jT S j O ( s log n T ) n T = | T |xTTSjTSjO(slognT)nT=|T|

Definir exclusão

Devido à sobrecarga de pesquisar no registro, se você tiver muitos conjuntos, talvez seja desejável excluir conjuntos quando eles não forem mais necessários. Ao percorrer todo o registro, podemos excluir do índice de todos os outros conjuntos no tempo , dominado pelo custo de excluir a árvore de pesquisa que representa para cada um dos outros conjuntos , em que.S j O ( s n T ) S jT S j n T = | T |SSjO(snT)SjTSjnT=|T|

Observações

Se você espera implementar apenas um número constante de conjuntos, os tempos de execução acima são reduzidos para:

  • inicialização:O(1)

  • inserção de elemento:O(logn)

  • teste de interseção (e recuperação da interseção):O(1)

  • remoção de elemento:O(lognT)

  • definir exclusão:O(nS)

onde é o tamanho do maior conjunto no registro epara o conjunto qual você está operando.nTnT=|T|T

Se você espera ter conjuntos , onde é o seu universo, pode ser necessária uma estrutura de dados diferente se desejar que essas operações operem em tempo sublinear. No entanto, se você tiver pares de conjuntos cuja interseção sabe que nunca testará, poderá reduzir o tamanho do índice dos conjuntos (não incluindo quaisquer conjuntos cuja interseção você testará) ou usar mais de um registro ( um para cada coleção de conjuntos cuja interseção você pode testar). De fato, um registro é útil apenas se você deseja um controle centralizado de garantir que cada par de conjuntos tenha um registro um do outro no índice: pode ser prático em alguns casos, na inicialização de um conjunto , simplesmente para registrar um anúncio. hocV S T SO(|V|)VScada novo conjunto nos índices dos outros conjuntos cuja interseção com você está interessado.TS


5

Existem estruturas de dados que permitem fazer isso em menos de tempo linear, mesmo para as entradas dos piores casos. Consulte http://research.microsoft.com/pubs/173795/vldb11intersection.pdf (e as referências de documentos aqui).

Se seus dois conjuntos S e T tiverem uma interseção grande e você tiver um dicionário para S, procurar elementos de T em ordem aleatória deve fornecer rapidamente um elemento comum. O caso mais difícil é quando o tamanho da interseção é 0 ou 1.


3

Normalmente, sua linguagem de programação preferida suporta uma estrutura de dados com elementos únicos. Em geral, existem três abordagens populares: Árvores, Hashes e Bitmasks. Os elementos da árvore devem ser comparáveis, os elementos Hash devem ser hasháveis ​​e os elementos Bitmask devem ter alguma forma de conversão para números inteiros.

Um conjunto de árvores suporta a inserção em O (log n) e teste de interseção no pior caso O (n log n).

Um conjunto de hash suportará a inserção em Amortizado O (1 * h), onde 'h' é o tempo de execução do algoritmo de hash e teste de interseção no Pior Caso O (n).

Os conjuntos de máscaras de bits geralmente não são usados ​​como conjuntos de árvore e hash.


2
Essa seria uma resposta decente do Stack Overflow , mas aqui gostaríamos de alguns detalhes sobre como e por que isso funciona.
Raphael

3

Se o seu caso permitir respostas falsas positivas, eu usaria o Bloom Filter com uma única função de hash.

Você pode implementá-lo da seguinte maneira:

Iniciar um conjunto vazio

  • B = matriz de bits de bits, tudo definido como 0. ( deve ser escolhido de acordo com o número de elementos possíveis)nnn

Adicione um elemento a um conjunto.

  • B[hash(element)]=1

Dados dois conjuntos (B1, B2), relate se eles se cruzam.

  • verifique seA N D BB1 AND B2 0= 0

Complexidade

  • Se não for muito grande, todas as operações serão .O ( 1 )nO(1)
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.