Dicionários e Conjuntos
Um conjunto pode ser definido como uma coleção desordenada de items (os quais denominamos elementos.
Um conjunto específico pode ser formado por uma lista explícita de elementos ou por meio de propriedades que todos os sues elementos devem possuir.
Operações
Verificar se um elemento faz parte do conjunto
União de dois conjuntos
Interseção de dois conjuntos
Existem duas maneiras básicas para implementar de forma computacional um conjunto.
Como strings binárias
Por meio de uma string binária, caso o conjunto seja um subconjunto de um conjunto maior, normalmente denominado conjunto Universal; por exemplo: dado o conjunto U = {1, 2, 3, 4, 5, 6, 7, 8, 9} e o subconjunto de U chamado S, onde S = {2, 3, 5, 7}, podemos representar o conjunto S pela string "011010100" nos quais os 1's representam aqueles elementos que estão tanto em U quanto em S, e o 0 os que estão ausentes em ambos os conjuntos.
Esta é uma abordagem que possibilita executar operações de maneira extremamente eficaz, porém traz custos consideráveis para armazenar todos os dados quando o conjunto universal é muito grande.
Como uma lista estruturada
Esta normalmente é a abordagem mais utilizada para esse tipo de atividade. Mas assim como a outra, esta abordagem também possibilita suporte apenas para conjuntos finitos, mesmo que tenha vantagens quanto a quantidade de armazenamento.
Existem algumas distinções teóricas entre conjuntos e listas, por exemplo: conjuntos possuem elementos únicos, a lista não (elementos com o mesmo valor podem ser duplicados e serão considerados como distintos); trocar a ordem de elementos de um conjunto não altera aquele conjunto, mas a lista sim. Contudo, esses conceitos não terão aplicações práticas na maioria das vezes.
Dicionários podem ser entendidos como a estrutura de dados que implementa aquelas operações básicas dos conjuntos.
Tempo e Espaço
O design de algoritmos que tenham uma melhor conpensação entre essas duas variáveis são basicamente os problemas encarados tantos por teóricos quanto por empiristas da computação.
Aprimoramento de entrada: esta é uma técnica, em termos gerais, onde uma entrada para um algorítmo é pré-processada (no todo ou em partes), e estas informações são armazenadas para agilizar a resolução do problema posteriormente.
Pré-extruturação: outra técnica utilizada para esse tipo de problema é separar um espaço maior para o armazenamento dos dados de forma que estes sejam processados de maneira mais rápida.
Programação dinâmica: esta técnica é baseada no armazenamento da soluções para instâncias menores do problema, para que se
Hashing
A ideia desta forma de implementação é distribuir chaves entre um array unidimenssional, chamada de hash table (ou tabela de hash). Para cada chave é definida uma função denominada hash functions (funções de hash). E por fim, cada chave é assinada com um inteiro entre 0 e m - 1 (sendo m o valor total).
Em geral, funções de hash devem satisfazer algumas requisitos conflitantes:
Uma tabela de hash não pode ser excessivamente larga quando comparada com o número de chaves, mas precisa ter tamanho suficiente para que o tempo de eficiência do algoritmo não seja comprometido.
Uma função de hash precisa distribuir suas chaves entre as células da tabela de hash de forma mais uniforme quanto puder.
Uma função de hash deve ser fácil de ser computada.
Se escolhermos uma tabela de hash de tamanho menor do que o número de chaves necessárias, ocorrerá um evento de colisão (que ocorre quando duas funções são armazenadas na mesma célula).
Entretanto, algumas colisões ainda são esperadas mesmo quando a tabela possui tamanho adequado. Para esse caso, devem existir mecanismos para resolver esse problema, e as duas principais versões para esta solução são:
Open Hashing
Closed Hashing
Também conhecida como Encadeamento Separado, as chaves são armazenadas em listas ligadas nesta versão, e estas listas são anexadas as células da tabela de hash
Também conhecida como Endereçamento aberto, nesta versão todas as chaves são armazenadas diretamente na tabela de hash (o que também implica que a tabela deve possuir por tamanho mínimo a quantidade de chaves existentes).