Eu já vi algumas afirmações interessantes sobre os hashmaps SO re Java e seu O(1)
tempo de pesquisa. Alguém pode explicar por que isso é assim? A menos que esses hashmaps sejam muito diferentes de qualquer um dos algoritmos de hash em que eu comprei, sempre deve existir um conjunto de dados que contenha colisões.
Nesse caso, a pesquisa seria O(n)
melhor que O(1)
.
Alguém pode explicar se eles são O (1) e, em caso afirmativo, como conseguem isso?
java
hashmap
big-o
time-complexity
paxdiablo
fonte
fonte
Respostas:
Uma característica específica de um HashMap é que, diferentemente das árvores balanceadas, seu comportamento é probabilístico. Nesses casos, geralmente é mais útil falar sobre complexidade em termos da probabilidade de ocorrência de um evento de pior caso. Para um mapa de hash, é claro que esse é o caso de uma colisão com relação ao quão cheio o mapa está. É fácil estimar uma colisão.
Portanto, é provável que um mapa de hash com até um número modesto de elementos sofra pelo menos uma colisão. A notação Big O nos permite fazer algo mais atraente. Observe que para qualquer constante fixa e arbitrária k.
Podemos usar esse recurso para melhorar o desempenho do mapa de hash. Em vez disso, poderíamos pensar na probabilidade de no máximo 2 colisões.
Isso é muito menor. Como o custo de lidar com uma colisão extra é irrelevante para o desempenho do Big O, encontramos uma maneira de melhorar o desempenho sem realmente alterar o algoritmo! Podemos generalizar isso para
E agora podemos desconsiderar um número arbitrário de colisões e acabar com uma probabilidade muito pequena de mais colisões do que estamos contabilizando. Você pode obter a probabilidade para um nível arbitrariamente pequeno escolhendo o k correto, tudo sem alterar a implementação real do algoritmo.
Falamos sobre isso dizendo que o mapa de hash tem acesso O (1) com alta probabilidade
fonte
Você parece misturar o pior comportamento com o tempo médio de execução (esperado). O primeiro é realmente O (n) para tabelas de hash em geral (isto é, não usando um hash perfeito), mas isso raramente é relevante na prática.
Qualquer implementação de tabela de hash confiável, juntamente com um hash meio decente, tem um desempenho de recuperação de O (1) com um fator muito pequeno (2, de fato) no caso esperado, dentro de uma margem de variação muito estreita.
fonte
Em Java, o HashMap funciona usando o hashCode para localizar um bucket. Cada balde é uma lista de itens que residem nesse balde. Os itens são digitalizados, usando iguais para comparação. Ao adicionar itens, o HashMap é redimensionado quando uma certa porcentagem de carga é atingida.
Portanto, às vezes, ele deve ser comparado a alguns itens, mas geralmente é muito mais próximo de O (1) do que de O (n). Para fins práticos, é tudo o que você precisa saber.
fonte
Lembre-se de que o (1) não significa que cada pesquisa examina apenas um único item - significa que o número médio de itens verificados permanece constante em relação ao número de itens no contêiner. Portanto, se levar em média 4 comparações para encontrar um item em um contêiner com 100 itens, também será necessário em média 4 comparações para encontrar um item em um contêiner com 10000 itens e para qualquer outro número de itens (sempre há um pouca variação, especialmente em torno dos pontos em que a tabela de hash é repetida e quando há um número muito pequeno de itens).
Portanto, colisões não impedem que o contêiner tenha o (1) operações, desde que o número médio de chaves por bucket permaneça dentro de um limite fixo.
fonte
Eu sei que essa é uma pergunta antiga, mas na verdade há uma nova resposta para ela.
Você está certo que um mapa de hash não é realmente
O(1)
, estritamente falando, porque como o número de elementos aumenta arbitrariamente, eventualmente você não poderá pesquisar em tempo constante (e a notação O é definida em termos de números que podem arbitrariamente grande).Mas não se segue que a complexidade em tempo real seja -
O(n)
porque não há regra que diga que os buckets precisam ser implementados como uma lista linear.De fato, o Java 8 implementa os buckets
TreeMaps
assim que eles excedem um limite, o que torna o tempo realO(log n)
.fonte
Se o número de buckets (denominado b) for mantido constante (o caso usual), a pesquisa será realmente O (n).
À medida que n aumenta, o número de elementos em cada intervalo é em média n / b. Se a resolução de colisão for feita de uma das formas usuais (lista vinculada, por exemplo), a pesquisa será O (n / b) = O (n).
A notação O é sobre o que acontece quando n fica cada vez maior. Pode ser enganoso quando aplicado a certos algoritmos, e as tabelas de hash são um exemplo disso. Escolhemos o número de buckets com base em quantos elementos esperamos lidar. Quando n é aproximadamente do mesmo tamanho que b, a pesquisa é aproximadamente constante, mas não podemos chamá-lo de O (1) porque O é definido em termos de limite como n → ∞.
fonte
O(1+n/k)
ondek
é o número de baldes.Se a implementação é
k = n/alpha
definida, éO(1+alpha) = O(1)
porquealpha
é uma constante.fonte
Estabelecemos que a descrição padrão das pesquisas de tabela de hash sendo O (1) refere-se ao tempo médio esperado, e não ao desempenho estrito do pior caso. Para uma tabela de hash que resolve colisões com encadeamento (como o mapa de hash de Java), isso é tecnicamente O (1 + α) com uma boa função de hash , onde α é o fator de carga da tabela. Ainda constante, desde que o número de objetos que você esteja armazenando não seja mais que um fator constante maior que o tamanho da tabela.
Também foi explicado que, estritamente falando, é possível construir entradas que exijam O ( n ) pesquisas para qualquer função hash determinística. Mas também é interessante considerar o pior tempo esperado , que é diferente do tempo médio de pesquisa. Usando encadeamento, isso é O (1 + o comprimento da cadeia mais longa), por exemplo Θ (log n / log log n ) quando α = 1.
Se você estiver interessado em maneiras teóricas para obter pesquisas de pior caso esperadas em tempo constante, pode ler sobre o hash perfeito dinâmico, que resolve colisões recursivamente com outra tabela de hash!
fonte
É O (1) somente se sua função de hash for muito boa. A implementação da tabela de hash Java não protege contra funções de hash incorretas.
Se você precisa aumentar a tabela quando adiciona itens ou não, isso não é relevante para a pergunta, porque se trata de tempo de pesquisa.
fonte
Os elementos dentro do HashMap são armazenados como uma matriz de lista vinculada (nó), cada lista vinculada na matriz representa um intervalo para o valor de hash exclusivo de uma ou mais chaves.
Ao adicionar uma entrada no HashMap, o código de hash da chave é usado para determinar a localização do bucket na matriz, algo como:
Aqui, o & representa o operador AND bit a bit.
Por exemplo:
100 & "ABC".hashCode() = 64 (location of the bucket for the key "ABC")
Durante a operação get, ele usa a mesma maneira para determinar a localização do balde para a chave. No melhor dos casos, cada chave possui um código de hash exclusivo e resulta em um depósito exclusivo para cada chave; nesse caso, o método get gasta tempo apenas para determinar a localização do depósito e recuperar o valor que é constante O (1).
Na pior das hipóteses, todas as chaves têm o mesmo código de hash e armazenadas no mesmo bucket, o que resulta em percorrer toda a lista que leva a O (n).
No caso do java 8, o bucket da Lista Vinculada é substituído por um TreeMap se o tamanho aumentar para mais de 8, isso reduz a pior eficiência da pesquisa de casos para O (log n).
fonte
Isso basicamente vale para a maioria das implementações de tabelas de hash na maioria das linguagens de programação, pois o próprio algoritmo não muda realmente.
Se não houver colisões presentes na tabela, você só precisará fazer uma única pesquisa, portanto, o tempo de execução é O (1). Se houver colisões presentes, é necessário fazer mais de uma pesquisa, o que reduz o desempenho em direção a O (n).
fonte
Depende do algoritmo que você escolher para evitar colisões. Se a sua implementação usar encadeamento separado, o pior cenário acontecerá quando cada elemento de dados tiver um hash com o mesmo valor (má escolha da função de hash, por exemplo). Nesse caso, a pesquisa de dados não difere de uma pesquisa linear em uma lista vinculada, ou seja, O (n). No entanto, a probabilidade de que isso aconteça é insignificante e as pesquisas de casos melhores e médios permanecem constantes, ou seja, O (1).
fonte
Além dos acadêmicos, do ponto de vista prático, o HashMaps deve ser aceito como tendo um impacto inconseqüente no desempenho (a menos que seu criador de perfis diga o contrário).
fonte
Somente no caso teórico, quando os códigos de hash são sempre diferentes e o intervalo para cada código de hash também é diferente, o O (1) existirá. Caso contrário, é de ordem constante, ou seja, no incremento do hashmap, sua ordem de pesquisa permanece constante.
fonte
Obviamente, o desempenho do hashmap dependerá da qualidade da função hashCode () do objeto especificado. No entanto, se a função for implementada de modo que a possibilidade de colisões seja muito baixa, ela terá um desempenho muito bom (isso não é estritamente O (1) em todos os casos possíveis, mas na maioria dos casos).
Por exemplo, a implementação padrão no Oracle JRE é usar um número aleatório (que é armazenado na instância do objeto para que não seja alterado - mas também desabilita o bloqueio parcial, mas isso é outra discussão), portanto a chance de colisões é maior. muito baixo
fonte
hashCode % tableSize
meio do que significa que certamente pode haver colisões. Você não está obtendo pleno uso dos 32 bits. Esse é o ponto das tabelas de hash ... você reduz um grande espaço de indexação para um pequeno.