Quais são as estruturas de dados menos conhecidas, mas úteis?


795

Existem algumas estruturas de dados que são realmente úteis, mas são desconhecidas para a maioria dos programadores. Quais são eles?

Todo mundo sabe sobre listas vinculadas, árvores binárias e hashes, mas e as listas Skip e Bloom, por exemplo. Gostaria de conhecer mais estruturas de dados que não são tão comuns, mas valem a pena conhecer, porque elas se baseiam em grandes idéias e enriquecem a caixa de ferramentas de um programador.

PS: Também estou interessado em técnicas como links de dança que fazem uso inteligente das propriedades de uma estrutura de dados comum.

EDIT : Tente incluir links para páginas que descrevem as estruturas de dados em mais detalhes. Além disso, tente adicionar algumas palavras sobre por que uma estrutura de dados é legal (como Jonas Kölker já apontou). Além disso, tente fornecer uma estrutura de dados por resposta . Isso permitirá que as melhores estruturas de dados flutuem para o topo com base apenas em seus votos.


Respostas:


271

As tentativas , também conhecidas como árvores prefixadas ou críticas , existem há mais de 40 anos, mas ainda são relativamente desconhecidas. Um uso muito interessante das tentativas é descrito em " TRASH - Uma estrutura dinâmica de dados LC-trie e hash ", que combina uma tentativa com uma função hash.


12
muito utilizada por ortográfica de damas
Steven A. Lowe

As tentativas de burst também são uma variante interessante, na qual você usa apenas um prefixo das strings como nós e, de outra forma, armazena listas de strings nos nós.
211 Torsten Marek

O mecanismo de expressão regular no Perl 5.10 cria tentativas automaticamente.
23411 Brad Gilbert

Na minha experiência, as tentativas são dolorosamente caras, dado que um ponteiro geralmente é mais longo que um caractere, o que é uma vergonha. Eles são adequados apenas para determinados conjuntos de dados.
Joe

18
Como nenhuma pergunta do SO, independentemente do tópico, está completa sem que alguém mencione o jQuery .... John Resig, criador do jQuery, possui uma interessante série de posts sobre estrutura de dados, onde analisa diversas implementações, entre outras: ejohn.org/blog/ revised-javascript-dictionary-search
Oskar Austegard 24/03

231

Filtro Bloom : matriz de bits de m bits, inicialmente definida como 0.

Para adicionar um item, execute-o por meio de funções k hash que fornecerão k índices na matriz que você definirá como 1.

Para verificar se um item está no conjunto, calcule os índices k e verifique se estão todos definidos como 1.

Obviamente, isso dá alguma probabilidade de falsos positivos (de acordo com a Wikipedia é de cerca de 0,61 ^ (m / n), em que n é o número de itens inseridos). Falsos negativos não são possíveis.

A remoção de um item é impossível, mas você pode implementar o filtro de contagem florido , representado pela matriz de entradas e incremento / decremento.


20
Você se esqueça de mencionar o seu uso com dicionários :) você pode espremer um dicionário completo em um filtro bloom com cerca de 512k, como um hashtable sem os valores
Chris S

8
O Google cita o uso de filtros Bloom na implementação do BigTable.
Brian Gianforcaro

16
@FreshCode Ele realmente permite que você teste mais barato para a ausência de um elemento no conjunto desde que você pode obter falsos positivos, mas nunca falsos negativos
Tom Savage

26
@FreshCode Como o Tom Savage disse, é mais útil ao procurar negativos. Por exemplo, você pode usá-lo como um verificador ortográfico rápido e pequeno (em termos de uso de memória). Adicione todas as palavras e tente procurar as palavras que o usuário digita. Se você receber um negativo, significa que está incorreto. Em seguida, você pode executar uma verificação mais cara para encontrar as correspondências mais próximas e oferecer correções.
lacop 25/05

5
@ abhin4v: Os filtros Bloom são frequentemente usados ​​quando a maioria das solicitações provavelmente retorna uma resposta "não" (como o caso aqui), o que significa que o pequeno número de respostas "sim" pode ser verificado com um teste exato mais lento. Isso ainda resulta em uma grande redução no tempo médio de resposta à consulta. Não sei se a Navegação segura do Chrome faz isso, mas esse seria meu palpite.
Jrandom_hacker

140

Corda : é uma string que permite prepends, substrings, inserções intermediárias e anexos baratos. Eu realmente só o usei uma vez, mas nenhuma outra estrutura seria suficiente. Anexos regulares de strings e matrizes eram muito caros para o que precisávamos fazer, e reverter tudo estava fora de questão.


Eu pensei em algo assim para meus próprios usos. É bom saber que já foi implementado em outro lugar.
Kibbee

15
Há uma implementação no SGI STL (1998): sgi.com/tech/stl/Rope.html
quark

2
Sem saber o que foi chamado, escrevi recentemente algo muito semelhante a isso para Java - o desempenho tem sido excelente: code.google.com/p/mikeralib/source/browse/trunk/Mikera/src/…
mikera


6
O link do Mikera está obsoleto, aqui está o atual .
Aptwebapps 24/03

128

As listas de pulos são bem legais.

Wikipedia
Uma lista de ignorados é uma estrutura de dados probabilística, baseada em várias listas vinculadas paralelas e ordenadas, com eficiência comparável a uma árvore de pesquisa binária (log de pedidos n tempo médio para a maioria das operações).

Eles podem ser usados ​​como uma alternativa às árvores balanceadas (usando o balanceamento probalístico em vez da imposição rigorosa do balanceamento). Eles são fáceis de implementar e mais rápidos do que, digamos, uma árvore vermelha-preta. Eu acho que eles deveriam estar em todos os bons programas de ferramentas de programação.

Se você deseja obter uma introdução aprofundada às listas de pulos, aqui está um link para um vídeo da palestra Introdução aos algoritmos do MIT sobre eles.

Além disso, aqui está um applet Java demonstrando visualmente as Listas de ignoradas.


O +1 Qt usa listas de ignorados em vez de árvores RB para seus mapas e conjuntos classificados. Sim, eles são bacanas (em línguas imperativas, de qualquer maneira).
Michael Ekstrand

2
O Redis usa as listas de pulos para implementar "Conjuntos classificados".
antirez 24/03

As listas de ignorados são provavelmente minha estrutura de dados favorita a ser usada quando preciso de uma boa estrutura de dados e não tenho garantias quanto à ordem dos dados, e quero uma implementação mais simples do que outras estruturas de dados "equilibradas". Que coisa boa.
Earino

Nota lateral interessante: se você adicionar níveis suficientes às suas listas de pulos, você basicamente terá uma árvore B.
Riyad Kalla

92

Os índices espaciais , em particular as árvores R e KD , armazenam dados espaciais com eficiência. Eles são bons para dados de coordenadas de mapas geográficos e algoritmos de local e rota VLSI e, às vezes, para pesquisa de vizinhos mais próximos.

Matrizes de bits armazenam bits individuais compactamente e permitem operações rápidas de bits.


6
Os índices espaciais também são úteis para simulações de corpos N envolvendo forças de longo alcance, como a gravidade.
Justin Peel

87

Zíperes - derivados de estruturas de dados que modificam a estrutura para ter uma noção natural de 'cursor' - local atual. Eles são realmente úteis, pois garantem que as indicações não podem estar fora do limite - usadas, por exemplo, no gerenciador de janelas xmonad para rastrear qual janela foi focada.

Surpreendentemente, você pode derivá-las aplicando técnicas de cálculo ao tipo da estrutura de dados original!


2
isso é útil apenas em programação funcional (em linguagens imperativas, você apenas mantém um ponteiro ou um índice). Também tbh ainda não entendo como os Zippers realmente funcionam.
Stefan Monov

4
@ Stefan, o ponto é que você não precisa manter um índice ou ponteiro separado agora.
Don Stewart

69

Aqui estão alguns:

  • Sufixo tenta. Útil para quase todos os tipos de pesquisa de string (http://en.wikipedia.org/wiki/Suffix_trie#Functionality ). Veja também matrizes de sufixo; eles não são tão rápidos quanto árvores de sufixo, mas muito menores.

  • Espalhe as árvores (como mencionado acima). A razão pela qual eles são legais é triplo:

    • Eles são pequenos: você só precisa dos ponteiros esquerdo e direito, como em qualquer árvore binária (nenhuma informação de cor ou tamanho do nó precisa ser armazenada)
    • Eles são (comparativamente) muito fáceis de implementar
    • Eles oferecem complexidade amortizada ideal para toda uma série de "critérios de medição" (o tempo de pesquisa é o que todos sabem). Vejohttp://en.wikipedia.org/wiki/Splay_tree#Performance_theorems
  • Árvores de pesquisa ordenadas por heap: você armazena vários pares (chave, prio) em uma árvore, de modo que seja uma árvore de pesquisa em relação às chaves e ordenada por heap em relação às prioridades. Pode-se mostrar que essa árvore tem uma forma única (e nem sempre é totalmente empacotada para a esquerda). Com prioridades aleatórias, fornece o tempo esperado de pesquisa de O (log n), IIRC.

  • Um nicho é a lista de adjacências para gráficos planares não direcionados com O (1) consultas de vizinhos. Essa não é uma estrutura de dados, mas uma maneira específica de organizar uma estrutura de dados existente. Aqui está como você faz isso: todo gráfico plano tem um nó com no máximo 6. Escolha um nó, coloque seus vizinhos na lista de vizinhos, remova-o do gráfico e recorra até que o gráfico esteja vazio. Quando receber um par (u, v), procure u na lista de vizinhos de v e na lista de vizinhos de v. Ambos têm tamanho no máximo 6, então é O (1).

Pelo algoritmo acima, se u e v forem vizinhos, você não terá u na lista de v e na lista de u. Se você precisar disso, basta adicionar os vizinhos ausentes de cada nó à lista de vizinhos desse nó, mas armazene quanto da lista de vizinhos você precisa procurar rapidamente.


A árvore de pesquisa ordenada por heap é chamada treap. Um truque que você pode fazer com isso é alterar a prioridade de um nó para empurrá-lo para a parte inferior da árvore, onde é mais fácil excluir.
paperhorse

1
"A árvore de pesquisa ordenada pela pilha é chamada treap." - Na definição que ouvi, IIRC, um treap é uma árvore de pesquisa ordenada por heap com prioridades aleatórias . Você pode escolher outras prioridades, dependendo da aplicação ...
Jonas Kolker

2
Um trie de sufixo é quase, mas não é o mesmo que a árvore de sufixos muito mais legal , que possui seqüências de caracteres e não letras individuais nas bordas e pode ser construída em tempo linear (!). Além disso, apesar de serem assintoticamente mais lentos, na prática, as matrizes de sufixos geralmente são muito mais rápidas que as árvores de sufixos para muitas tarefas, devido ao seu tamanho menor e menos indiretos de ponteiro. Ame a pesquisa de gráfico planar O (1) BTW!
Jrandom_hacker

@j_random_hacker: matrizes de sufixo não são assintoticamente mais lentas. Aqui é ~ 50 linhas de código para a construção variedade sufixo linear: cs.helsinki.fi/u/tpkarkka/publications/icalp03.pdf
Edward KMETT

1
@ Edward Kmett: Na verdade, eu li esse artigo, foi um grande avanço na construção de matrizes de sufixos . (Embora já se soubesse que a construção do tempo linear era possível através do "via" uma árvore de sufixos, este era o primeiro algoritmo "direto" inegavelmente prático.) Mas algumas operações fora da construção ainda são assintoticamente mais lentas em uma matriz de sufixos, a menos que um LCA mesa também é construída. Isso também pode ser feito em O (n), mas você perde os benefícios de tamanho e localidade da matriz de sufixos puros ao fazê-lo.
Jrandom_hacker

65

Eu acho que as alternativas livres de bloqueio às estruturas de dados padrão, ou seja, fila, pilha e lista sem bloqueio, são muito negligenciadas.
Eles são cada vez mais relevantes, pois a concorrência se torna uma prioridade mais alta e é um objetivo muito mais admirável do que usar Mutexes ou bloqueios para lidar com leitura / gravação simultânea.

Aqui estão alguns links
http://www.cl.cam.ac.uk/research/srg/netos/lock-free/
http://www.research.ibm.com/people/m/michael/podc-1996.pdf [Links para PDF]
http://www.boyet.com/Articles/LockfreeStack.html

O blog de Mike Acton (muitas vezes provocador) tem excelentes artigos sobre design e abordagens sem bloqueio


Alternativas livres de bloqueio são tão importantes no actual multi-core, muito paralelo escalabilidade viciado mundo, :-)
Earino

Bem, um disruptor realmente faz um trabalho melhor na maioria dos casos.
Deadalnix 13/10/11

55

Acho que Disjoint Set é bastante bacana para os casos em que você precisa dividir um monte de itens em conjuntos distintos e associar a consulta. A boa implementação das operações Union e Find resulta em custos amortizados efetivamente constantes (inverso da Função de Ackermnan, se bem me lembro da classe de estruturas de dados).


8
Isso também é chamado de "estrutura de dados de localização de união". Fiquei admirado quando aprendi sobre essa estrutura de dados inteligente na classe de algoritmos ... #
21460 BlueRaja #

As extensões union-find-delete também permitem uma exclusão em tempo constante.
quer

4
Eu usei um Set Disjoint para o meu gerador de Dungeon, para garantir que todos os quartos são acessíveis por passagens :)
proporção áurea

52

Montes de Fibonacci

Eles são usados ​​em alguns dos algoritmos mais rápidos conhecidos (assintoticamente) para muitos problemas relacionados a gráficos, como o problema de Caminho Mais Curto. O algoritmo de Dijkstra é executado no tempo O (E log V) com pilhas binárias padrão; o uso de pilhas de Fibonacci melhora isso para O (E + V log V), que é uma enorme aceleração para gráficos densos. Infelizmente, porém, eles têm um alto fator constante, tornando-os impraticáveis ​​na prática.


Alto fator constante, como você disse, e difícil de implementar bem, de acordo com um amigo que precisava. Fianally não é tão legal, mas ainda assim, talvez valha a pena conhecer.
P4bl0 23/05

Esses caras aqui os fizeram correr competitivos em comparação com outros tipos de heap: cphstl.dk/Presentation/SEA2010/SEA-10.pdf Há uma estrutura de dados relacionada chamada Pairing Heaps que é mais fácil de implementar e oferece desempenho prático bastante bom. No entanto, a análise teórica é parcialmente aberta.
Manuel

Pela minha experiência com heaps de Fibonacci, descobri que a operação cara das alocações de memória a torna menos eficiente do que uma simples pilha binária suportada por uma matriz.
jutky

44

Qualquer pessoa com experiência em renderização em 3D deve estar familiarizada com as árvores BSP . Geralmente, é o método estruturando uma cena 3D para ser gerenciável para renderização, sabendo as coordenadas e o rumo da câmera.

O particionamento de espaço binário (BSP) é um método para subdividir recursivamente um espaço em conjuntos convexos por hiperplanos. Essa subdivisão dá origem a uma representação da cena por meio de uma estrutura de dados em árvore conhecida como árvore BSP.

Em outras palavras, é um método de dividir polígonos de forma complexa em conjuntos convexos ou polígonos menores, consistindo inteiramente de ângulos não reflexos (ângulos menores que 180 °). Para uma descrição mais geral do particionamento de espaço, consulte Particionamento de Espaço.

Originalmente, essa abordagem foi proposta em computação gráfica em 3D para aumentar a eficiência da renderização. Algumas outras aplicações incluem a execução de operações geométricas com formas (geometria sólida construtiva) em CAD, detecção de colisão em robótica e jogos de computador em 3D e outros aplicativos que envolvem o manuseio de cenas espaciais complexas.


... e as árvores e kd-trees relacionados.
Lloeki


38

Dê uma olhada no Finger Trees , especialmente se você é um fã das estruturas de dados puramente funcionais mencionadas anteriormente . Eles são uma representação funcional de sequências persistentes que dão suporte ao acesso aos fins em tempo constante amortizado e concatenação e divisão logarítmica no tempo no tamanho da peça menor.

Conforme artigo original :

Nossas árvores funcionais de 2-3 dedos são um exemplo de uma técnica geral de projeto introduzida por Okasaki (1998), chamada desaceleração recursiva implícita . Já observamos que essas árvores são uma extensão de sua estrutura implícita de deque, substituindo pares por 2-3 nós para fornecer a flexibilidade necessária para concatenação e divisão eficientes.

Uma Árvore dos Dedos pode ser parametrizada com um monóide , e o uso de diferentes monoides resultará em comportamentos diferentes para a árvore. Isso permite que as Finger Trees simulem outras estruturas de dados.



Dê uma olhada nesta resposta duplicada , vale a pena ler!
François G


33

Estou surpreso que ninguém tenha mencionado as árvores Merkle (ou seja, Hash Trees ).

Utilizado em muitos casos (programas P2P, assinaturas digitais) em que você deseja verificar o hash de um arquivo inteiro quando tiver apenas parte do arquivo disponível.


32

Árvores <zvrba> Van Emde-Boas

Eu acho que seria útil saber por que eles são legais. Em geral, a pergunta "por que" é a mais importante a ser feita;)

Minha resposta é que eles fornecem dicionários O (log log n) com as teclas {1..n}, independentemente de quantas delas estão em uso. Assim como metade repetida fornece O (log n), sqrting repetido fornece O (log log n), que é o que acontece na árvore do vEB.


Eles são legais do ponto de vista teórico. Na prática, porém, é bastante difícil obter desempenho competitivo deles. O artigo que eu conheço fez com que funcionassem bem com chaves de até 32 bits ( citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.2.7403 ), mas a abordagem não será dimensionada para mais do que talvez 34-35 bits ou então e não há implementação disso.
Manuel

Outra razão pela qual eles são legais é que eles são um elemento essencial para vários algoritmos que não fazem cache.
Edward KMETT


29

Uma variante interessante da tabela de hash é chamada Cuckoo Hashing . Ele usa várias funções de hash em vez de apenas 1 para lidar com colisões de hash. As colisões são resolvidas removendo o objeto antigo do local especificado pelo hash primário e movendo-o para um local especificado por uma função de hash alternativa. O Cuckoo Hashing permite um uso mais eficiente do espaço na memória, porque você pode aumentar seu fator de carga em até 91% com apenas 3 funções de hash e ainda ter um bom tempo de acesso.


5
Verifique se o hash de amarelinha é mais rápido.
Chmike 23/05

27

Um heap min-max é uma variação de um heap que implementa uma fila de prioridade com extremidade dupla. Isso é alcançado com uma simples alteração na propriedade heap: uma árvore é dita como ordem min-max se todos os elementos nos níveis pares (ímpares) forem menores (maiores) do que todas as crianças e netos. Os níveis são numerados a partir de 1.

http://internet512.chonbuk.ac.kr/datastructure/heap/img/heap8.jpg


Complicado para implementar. Até os melhores programadores podem errar.
finnw

26

Eu gosto de estruturas de dados alheias ao cache . A idéia básica é colocar uma árvore em blocos recursivamente menores, para que caches de tamanhos diferentes aproveitem os blocos que cabem neles. Isso leva ao uso eficiente de armazenamento em cache em tudo, desde o cache L1 na RAM até grandes pedaços de dados lidos no disco sem precisar conhecer as especificidades dos tamanhos de qualquer uma dessas camadas de armazenamento em cache.


Transcrição interessante desse link: "A chave é o layout da van Emde Boas, nomeado após a estrutura de dados em árvore da van Emde Boas, concebida em 1977 por Peter van Emde Boas"
sergiol 23/02/12

23

Esquerda, inclinando-se árvores vermelho-pretas . Uma implementação significativamente simplificada de árvores vermelho-pretas por Robert Sedgewick publicada em 2008 (~ metade das linhas de código a serem implementadas). Se você já teve problemas para entender a implementação de uma árvore Vermelho-Preto, leia sobre essa variante.

Muito semelhante (se não idêntico) ao Andersson Trees.



19

Bootstrap montões de inclinação-binomial por Gerth Stølting Brodal e Chris Okasaki:

Apesar do nome longo, eles fornecem operações de heap assintoticamente ideais, mesmo em uma configuração de função.

  • O(1)tamanho, união , inserção, mínimo
  • O(log n) deleteMin

Observe que a união leva, O(1)ao invés de O(log n)tempo, ao contrário dos heaps mais conhecidos que são comumente abordados nos manuais de estrutura de dados, como os de esquerda . E, ao contrário dos montes de Fibonacci , esses assintóticos são os piores casos, em vez de amortizados, mesmo se usados ​​persistentemente!

Existem várias implementações no Haskell.

Eles foram derivados em conjunto por Brodal e Okasaki, depois que Brodal criou uma pilha imperativa com os mesmos assintóticos.


18
  • Kd-Trees , estrutura de dados espaciais usada (entre outros) no Raytracing em tempo real, tem a desvantagem de que os triângulos que se cruzam cruzam os diferentes espaços precisam ser cortados. Geralmente os BVHs são mais rápidos porque são mais leves.
  • Os Quad-MX MX-CIF armazenam caixas delimitadoras em vez de conjuntos de pontos arbitrários, combinando uma quadtree comum com uma árvore binária nas bordas dos quads.
  • HAMT , mapa de hash hierárquico com tempos de acesso que geralmente excedem O (1) hash-maps devido às constantes envolvidas.
  • Índice invertido , bastante conhecido nos círculos dos mecanismos de pesquisa, porque é usado para recuperação rápida de documentos associados a diferentes termos de pesquisa.

Muitos, se não todos, estão documentados no Dicionário NIST de Algoritmos e Estruturas de Dados



17

Não é realmente uma estrutura de dados; É mais uma maneira de otimizar matrizes alocadas dinamicamente, mas os buffers de lacunas usados ​​no Emacs são bem legais.


1
Definitivamente, consideraria isso uma estrutura de dados.
Christopher Barber

Para qualquer pessoa interessada, é exatamente assim que os modelos de documento (por exemplo, PlainDocument) que suportam os componentes de texto Swing também são implementados; antes da versão 1.2, acredito que os modelos de documentos eram matrizes diretas, o que leva a um desempenho horrível de inserção para documentos grandes; Assim que se mudaram para a Gap Buffers, tudo estava certo com o mundo novamente.
Riyad Kalla

16

Árvore de Fenwick. É uma estrutura de dados para manter a contagem da soma de todos os elementos em um vetor, entre dois subíndices iej. A solução trivial, pré-calcular a soma desde o início, não permite atualizar um item (você precisa fazer O (n) trabalho para acompanhar).

As árvores Fenwick permitem atualizar e consultar em O (log n), e como ele funciona é muito legal e simples. É realmente bem explicado no artigo original de Fenwick, disponível gratuitamente aqui:

http://www.cs.ubc.ca/local/reading/proceedings/spe91-95/spe/vol24/issue3/spe884.pdf

Seu pai, a árvore RQM, também é muito legal: permite manter informações sobre o elemento mínimo entre dois índices do vetor e também funciona na atualização e consulta de O (log n). Eu gosto de ensinar primeiro o RQM e depois a Fenwick Tree.


Receio que seja uma duplicata . Talvez você queira adicionar à resposta anterior?
François G

Também estão relacionadas as árvores de segmentos, que são úteis para realizar todos os tipos de consultas de intervalo.
dhruvbird


13

Conjuntos aninhados são bons para representar árvores nos bancos de dados relacionais e executar consultas neles. Por exemplo, o ActiveRecord (ORM padrão do Ruby on Rails) vem com um plug-in de conjunto aninhado muito simples , que torna o trabalho com árvores trivial.


12

É bastante específico do domínio, mas a estrutura de dados de meia borda é bem organizada. Ele fornece uma maneira de iterar sobre malhas poligonais (faces e arestas), o que é muito útil em gráficos de computador e geometria computacional.

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.