Por que alguém usaria set em vez de unordered_set?

145

O C ++ 0x está apresentando o unordered_setque está disponível em boostmuitos outros lugares. O que eu entendo é que unordered_seté uma tabela de hash com O(1)complexidade de pesquisa. Por outro lado, setnada mais é do que uma árvore com log(n)complexidade de pesquisa. Por que diabos alguém usaria em setvez de unordered_set? ou seja, é necessário setmais?

AraK
fonte
22
Sua pergunta é fundamentalmente perguntar se há mais necessidade de uma árvore.
Vinko Vrsalovic 28/08/09
2
Acho que afirmei claramente na primeira linha, que essa é uma pergunta estúpida. Eu estava faltando alguma coisa e agora eu tenho a resposta :)
Arak
2
A verdadeira razão é que as coisas não são tão em preto e branco como parecem. Existem muitos tons de cinza e outras cores no meio. Você precisa se lembrar que esses contêineres são ferramentas. Às vezes, o desempenho não é crucial e a conveniência é muito mais significativa. Se todas as pessoas procuraram a solução mais eficiente nós "d nunca use C ++ (para não mencionar Python) em primeiro lugar e de forma contínua escrever e código de otimizar em linguagem de máquina.
AturSams
(Por que diabos alguém iria usar um nome genérico para uma implementação / Interface com promessas além daqueles implícitos por esse nome, criando uma situação embaraçosa para aqueles sem?)
greybeard

Respostas:

219

Quando, para alguém que deseja iterar nos itens do conjunto, o pedido é importante.

sombra da Lua
fonte
É pedido de acordo com o pedido de inserção ou com comparação real usando operadores < >?
SomethingSomething
2
É ordenado usando std :: less por padrão; você pode substituir isso e fornecer seu próprio operador de comparação. cplusplus.com/reference/set/set
moonshadow
Ou, às vezes, quando você deseja iterar, mesmo que o pedido não importe.
mfnx 01/01
319

Os conjuntos não ordenados precisam pagar pelo tempo médio de acesso de O (1) de algumas maneiras:

  • setusa menos memória queunordered_set para armazenar o mesmo número de elementos.
  • Para um pequeno número de elementos , as pesquisas em um setpodem ser mais rápidas que as pesquisas em umunordered_set .
  • Mesmo que muitas operações são mais rápidas no caso médio para unordered_set, muitas vezes são garantidos para ter melhores piores complexidades de casos para set(por exemploinsert ).
  • Isso set classifica os elementos é útil se você deseja acessá-los em ordem.
  • Você pode lexicographically comparar diferentes sets com <, <=, >e >=. unordered_sets não são necessários para suportar essas operações.

sth
fonte
9
+1, todos os pontos excelentes. As pessoas tendem a ignorar o fato de que as tabelas de hash têm O (1) tempo médio de acesso a casos , o que significa que ocasionalmente podem ter grandes atrasos. A distinção pode ser importante para sistemas em tempo real.
j_random_hacker 3/09/09
Bons pontos, no entanto aqui ( en.cppreference.com/w/cpp/container/unordered_set/operator_cmp ), afirma-se que podemos comparar unordered_sets.
Michiel no Broek
5
Definir um "pequeno número de elementos"
Sunjay Varma
4
@ SunjayVarma geralmente 100 elementos é um bom ponto de corte entre os dois. Na dúvida, nada pode substituir o desempenho de teste dos dois em seu caso de uso específico.
Nate
3
@MichieluithetBroek Somente a comparação de igualdade é declarada, não ordenando ( <).
lisyarus
26

Sempre que você prefere uma árvore a uma tabela de hash.

Por exemplo, tabelas de hash são "O (n)" na pior das hipóteses. O (1) é o caso médio. As árvores são "O ( log n)" na pior das hipóteses.

Mehrdad Afshari
fonte
18
/ Árvores balanceadas / são O (ln n) no pior caso. Você pode acabar com O (n) árvores (listas essencialmente vinculadas).
strager
5
Se você pode escrever uma função hash razoavelmente inteligente, quase sempre é possível obter O (1) perf de uma hashtable. Se você não conseguir escrever uma função de hash, se precisar iterar "em ordem" sobre o seu conjunto, use uma árvore. Mas você não deve usar uma árvore porque tem medo de "O (n) pior desempenho".
237/09 Justin L.
6
stager: Para ser pedante, sim. No entanto, estamos falando de um conjunto em C ++, que normalmente é implementado como um árvore de pesquisa binária equilibrada . Deveríamos especificar a operação real para falar sobre complexidade. Nesse contexto, é óbvio que estamos falando de pesquisa.
Mehrdad Afshari 28/08/09
1
Justin L: É apenas uma das razões pelas quais você pode preferir uma árvore. O núcleo da minha resposta é a primeira linha. Sempre que você preferir uma estrutura de dados em árvore a uma tabela de hash. Existem muitos casos em que as árvores são preferidas às tabelas de hash. As tabelas de hash são péssimas em coisas como "interseções de intervalo".
Mehrdad Afshari 28/08/09
2
As árvores stl são árvores vermelho-preto quase universalmente implementadas, uma árvore avançada de auto-equilíbrio. Realmente existem casos em que O (n) pesquisa na pior das hipóteses não é aceitável. Um serviço web que forneça e faça interface para armazenar valores do usuário não deve usar um mapa de hash, pois um usuário mal-intencionado pode efetivamente criar um DoS armazenando valores especialmente criados. Sistemas críticos e sensíveis ao tempo também podem não permitir a busca de O (n), controle de tráfego aéreo etc.
Deft_code 2/09/09
14

Use set quando:

  1. Precisamos de dados ordenados (elementos distintos).
  2. Teríamos que imprimir / acessar os dados (em ordem classificada).
  3. Precisamos do predecessor / sucessor dos elementos.

Use unordered_set quando:

  1. Precisamos manter um conjunto de elementos distintos e nenhuma ordem é necessária.
  2. Precisamos de acesso a um único elemento, ou seja, sem passagem.

Exemplos:

conjunto:

Entrada: 1, 8, 2, 5, 3, 9

Saída: 1, 2, 3, 5, 8, 9

Unordered_set:

Entrada: 1, 8, 2, 5, 3, 9

Saída: 9 3 1 8 2 5 (talvez esta ordem, influenciada pela função hash)

Principalmente diferença:

insira a descrição da imagem aqui

Nota: (em alguns casos, seté mais conveniente), por exemplo, usando vectorcomo chave

set<vector<int>> s;
s.insert({1, 2});
s.insert({1, 3});
s.insert({1, 2});

for(const auto& vec:s)
    cout<<vec<<endl;   // I have override << for vector
// 1 2
// 1 3 

A razão pela qual vector<int>pode ser a chave, setporque vectorsubstituioperator< .

Mas se você usar, unordered_set<vector<int>>precisará criar uma função de hash para vector<int>, porque o vetor não tem uma função de hash, então você deve definir uma como:

struct VectorHash {
    size_t operator()(const std::vector<int>& v) const {
        std::hash<int> hasher;
        size_t seed = 0;
        for (int i : v) {
            seed ^= hasher(i) + 0x9e3779b9 + (seed<<6) + (seed>>2);
        }
        return seed;
    }
};

vector<vector<int>> two(){
    //unordered_set<vector<int>> s; // error vector<int> doesn't  have hash function
    unordered_set<vector<int>, VectorHash> s;
    s.insert({1, 2});
    s.insert({1, 3});
    s.insert({1, 2});

    for(const auto& vec:s)
        cout<<vec<<endl;
    // 1 2
    // 1 3
}

você pode ver que em alguns casos unordered_set é mais complicado.

Citado principalmente em: https://www.geeksforgeeks.org/set-vs-unordered_set-c-stl/ https://stackoverflow.com/a/29855973/6329006

Jayhello
fonte
6

Porque std :: set faz parte do C ++ padrão e unordered_set não. C ++ 0x NÃO é um padrão, nem o Boost. Para muitos de nós, a portabilidade é essencial, e isso significa manter o padrão.


fonte
2
Se eu o entendi corretamente, ele não está perguntando por que as pessoas ainda usam o set. Ele está se informando sobre C ++ 0x.
Johannes Schaub - litb 28/08/09
2
Talvez. Eu pensei que todo mundo sabia que tabelas e árvores de hash resolviam problemas diferentes.
21
Bem, é um padrão agora (só levou alguns anos)
Clayton Hughes
6

Considere algoritmos de varredura. Esses algoritmos falhariam totalmente com tabelas de hash, mas funcionam lindamente com árvores balanceadas. Para dar um exemplo concreto de um algoritmo de varredura, considere o algoritmo da fortuna. http://en.wikipedia.org/wiki/Fortune%27s_algorithm

ldog
fonte
1
Eu acho que essa referência é complexa demais, dada a pergunta. (Eu tive que procurá-lo)
hectorpal
3

Mais uma coisa, além do que outras pessoas já mencionaram. Embora a complexidade amortizada esperada para inserir um elemento em um conjunto não ordenado seja O (1), de vez em quando ele será necessário O (n) porque a tabela de hash precisa ser reestruturada (o número de buckets precisa mudar) - mesmo com uma função hash 'boa'. Assim como a inserção de um elemento em um vetor recebe O (n) de vez em quando, porque a matriz subjacente precisa ser realocada.

A inserção em um conjunto sempre leva no máximo O (log n). Isso pode ser preferível em alguns aplicativos.

Blargle
fonte
3

Perdoe-me, mais uma coisa que vale a pena notar sobre a propriedade classificada:

Se você deseja um intervalo de dados no contêiner, por exemplo: Você armazenou o tempo no conjunto e deseja o tempo de 01-01-2013 a 01-01-2014.

Para unordered_set , é impossível.

Obviamente, este exemplo seria mais convincente para casos de uso entre map e unordered_map .

Espectral
fonte
3

g++ 6.4 stdlibc ++ ordenado vs benchmark de conjunto não ordenado

Comparei essa implementação dominante do Linux C ++ para ver a diferença:

insira a descrição da imagem aqui

Os detalhes e análises completos do benchmark foram fornecidos em: Qual é a estrutura de dados subjacente de um conjunto de STL em C ++?e não vou repeti-los aqui.

"BST" significa "testado com std::sete" mapa de hash "significa" testado com std::unordered_set. "Heap" é para o std::priority_queuequal eu analisei em: Heap vs Binary Search Tree (BST)

Como um resumo rápido:

  • o gráfico mostra claramente que, nessas condições, a inserção de hashmap sempre foi muito mais rápida quando há mais de 100 mil itens, e a diferença aumenta à medida que o número de itens aumenta

    O custo desse aumento de velocidade é que você não é capaz de percorrer com eficiência em ordem.

  • as curvas sugerem claramente que o pedido std::seté baseado no BST e o std::unordered_sethashmap. Na resposta de referência, confirmei ainda que, por meio do GDB, depure o código.

Pergunta semelhante para mapvs unordered_map: Existe alguma vantagem em usar o mapa sobre unordered_map no caso de chaves triviais?

Ciro Santilli adicionou uma nova foto
fonte
1

Por outro lado, eu diria que é conveniente ter coisas em um relacionamento se você deseja convertê-lo em um formato diferente.

Também é possível que, embora o acesso seja mais rápido, o tempo para criar o índice ou a memória usada ao criar e / ou acessá-lo seja maior.

Rushyo
fonte
+1, a notação Big Oh oculta os fatores constantes e, para tamanhos de problemas típicos, geralmente são os fatores constantes que mais importam.
j_random_hacker 3/09/09
1

Se você deseja que as coisas sejam classificadas, use set em vez de unordered_set. unordered_set é usado sobre o conjunto quando o pedido armazenado não importa.

leiz
fonte
1

Embora essa resposta possa demorar 10 anos, vale ressaltar que std::unordered_settambém tem desvantagens na segurança.

Se a função hash for previsível (normalmente, a menos que aplique medidas contrárias, como um sal aleatório), os atacantes podem criar dados manualmente que produzem colisões de hash e fazem com que todas as inserções e pesquisas levem tempo O (n) .

Isso pode ser usado para ataques de negação de serviço muito eficientes e elegantes.

Muitas (a maioria?) Implementações de idiomas que empregam internamente mapas de hash se deparam com isso:

ratos
fonte