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?
- Inicie um conjunto vazio.
- Adicione um elemento a um conjunto.
- Com dois conjuntos, relate se eles se cruzam.
data-structures
sets
Dawei Huang
fonte
fonte
Respostas:
Se cada conjunto mantiver um registro de quais outros conjuntos existem e você tiver um total des > 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 conjuntosS1, 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 conjuntoSj 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 Sj∩ Sk . (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 ) T O ( s logs ) T Sj T T∩ Sj= ∅ Sj Sj
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 j ∩ T S jx ∈ V T O ( lognT) nT=|T| x S1, S2, …
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 .Tx ∈ S T O ( log n T )x O ( 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 jSj Sk Sj Sk O(logs) Sj S j ∩ S kSk Sj∩Sk
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 j ∩ T S j O ( s log n T ) n T = | T |x T T Sj∩T Sj O(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 j ∩ T S j n T = | T |S Sj O(snT) Sj∩T Sj nT=|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.n TnT=|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|) V S cada novo conjunto nos índices dos outros conjuntos cuja interseção com você está interessado.T S
fonte
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.
fonte
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.
fonte
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
Adicione um elemento a um conjunto.
Dados dois conjuntos (B1, B2), relate se eles se cruzam.
Complexidade
fonte