Possível duplicado:
rolando o algoritmo mediano em C
Dado que números inteiros são lidos de um fluxo de dados. Encontre a mediana dos elementos lidos até agora de maneira eficiente.
Solução que li: Podemos usar um heap máximo no lado esquerdo para representar elementos menores que a mediana efetiva e um heap mínimo no lado direito para representar elementos que são maiores que a mediana efetiva.
Após o processamento de um elemento recebido, o número de elementos nos heaps diferem no máximo por 1 elemento. Quando os dois heaps contêm o mesmo número de elementos, encontramos a média dos dados raiz do heap como mediana efetiva. Quando os heaps não estão equilibrados, selecionamos a mediana efetiva da raiz do heap que contém mais elementos.
Mas como construiríamos um heap máximo e um heap mínimo, como saberíamos a mediana efetiva aqui? Eu acho que inseriríamos 1 elemento no max-heap e, em seguida, o próximo 1 elemento no min-heap, e assim por diante para todos os elementos. Corrija-me Se eu estiver errado aqui.
Respostas:
Existem várias soluções diferentes para encontrar a mediana em execução a partir de dados transmitidos; falarei brevemente sobre elas no final da resposta.
A pergunta é sobre os detalhes de uma solução específica (max heap / min heap solution) e como a solução baseada em heap funciona é explicada abaixo:
Para os dois primeiros elementos, adicione um menor ao maxHeap à esquerda e outro maior ao minHeap à direita. Em seguida, processe os dados do fluxo um por um,
Então, a qualquer momento, você pode calcular a mediana assim:
Agora vou falar sobre o problema em geral, como prometido no início da resposta. Encontrar mediana em execução a partir de um fluxo de dados é um problema difícil, e encontrar uma solução exata com restrições de memória com eficiência é provavelmente impossível para o caso geral. Por outro lado, se os dados têm algumas características que podemos explorar, podemos desenvolver soluções especializadas eficientes. Por exemplo, se sabemos que os dados são do tipo integral, podemos usar a classificação de contagem, que pode fornecer um algoritmo de tempo constante de memória constante. A solução baseada em heap é uma solução mais geral, porque também pode ser usada para outros tipos de dados (duplos). E, finalmente, se a mediana exata não for necessária e uma aproximação for suficiente, você pode apenas tentar estimar uma função de densidade de probabilidade para os dados e estimar a mediana usando isso.
fonte
Se você não conseguir armazenar todos os itens na memória de uma só vez, esse problema se tornará muito mais difícil. A solução de heap requer que você mantenha todos os elementos na memória de uma só vez. Isso não é possível na maioria das aplicações do mundo real desse problema.
Em vez disso, como você vê os números, acompanhe a contagem do número de vezes que vê cada número inteiro. Assumindo números inteiros de 4 bytes, são 2 ^ 32 buckets, ou no máximo 2 ^ 33 inteiros (chave e contagem para cada int), que são 2 ^ 35 bytes ou 32GB. Provavelmente será muito menor do que isso, porque você não precisa armazenar a chave ou contar para as entradas que são 0 (ou seja, como um padrão no python). Isso leva tempo constante para inserir cada novo número inteiro.
Então, a qualquer momento, para encontrar a mediana, basta usar as contagens para determinar qual número inteiro é o elemento do meio. Isso leva tempo constante (embora uma constante grande, mas constante, no entanto).
fonte
Se a variação da entrada for distribuída estatisticamente (por exemplo, normal, log-normal, etc.), a amostragem de reservatório é uma maneira razoável de estimar percentis / medianas a partir de um fluxo arbitrariamente longo de números.
"reservatório" é então uma amostra uniforme e regular de todas as entradas - independentemente do tamanho. Encontrar a mediana (ou qualquer percentil) é uma questão direta de classificar o reservatório e pesquisar o ponto interessante.
Como o reservatório é de tamanho fixo, o tipo pode ser considerado efetivamente O (1) - e esse método é executado com tempo constante e consumo de memória.
fonte
A maneira mais eficiente de calcular um percentil de um fluxo que encontrei é o algoritmo P²: Raj Jain, Imrich Chlamtac: o algoritmo P² para cálculo dinâmico de quantiis e histogramas sem armazenar observações. Comum. ACM 28 (10): 1076-1085 (1985)
O algoritmo é simples de implementar e funciona extremamente bem. É uma estimativa, no entanto, tenha isso em mente. Do resumo:
fonte
Se quisermos encontrar a mediana dos n elementos vistos mais recentemente, esse problema tem uma solução exata que só precisa que os n elementos vistos mais recentemente sejam mantidos na memória. É rápido e escala bem.
Um skiplist indexável suporta a inserção, remoção e pesquisa indexada de O (ln n) de elementos arbitrários enquanto mantém a ordem classificada. Quando acoplado a uma fila FIFO que rastreia a n-ésima entrada mais antiga, a solução é simples:
Aqui estão os links para concluir o código de trabalho (uma versão de classe fácil de entender e uma versão otimizada do gerador com o código do skiplist indexável inline):
http://code.activestate.com/recipes/576930-efficient-running-median-using-an-indexable-skipli/
http://code.activestate.com/recipes/577073 .
fonte
Uma maneira intuitiva de pensar sobre isso é que, se você tivesse uma árvore de pesquisa binária equilibrada completa, a raiz seria o elemento mediano, pois haveria o mesmo número de elementos menores e maiores. Agora, se a árvore não estiver cheia, esse não será o caso, pois haverá elementos ausentes no último nível.
Portanto, o que podemos fazer é ter a mediana e duas árvores binárias balanceadas, uma para elementos menores que a mediana e outra para elementos maiores que a mediana. As duas árvores devem ser mantidas no mesmo tamanho.
Quando obtemos um novo número inteiro do fluxo de dados, o comparamos com a mediana. Se for maior que a mediana, nós a adicionamos à árvore correta. Se os dois tamanhos de árvore diferirem mais de 1, removeremos o elemento min da árvore direita, tornaremos a nova mediana e colocaremos a mediana antiga na árvore esquerda. Da mesma forma para menores.
fonte
Eficiente é uma palavra que depende do contexto. A solução para esse problema depende da quantidade de consultas realizadas em relação à quantidade de inserções. Suponha que você esteja inserindo N números e K vezes no final do seu interesse pela mediana. A complexidade do algoritmo baseado em heap seria O (N log N + K).
Considere a seguinte alternativa. Plunk os números em uma matriz e, para cada consulta, execute o algoritmo de seleção linear (usando o pivô quicksort, por exemplo). Agora você tem um algoritmo com tempo de execução O (KN).
Agora, se K é suficientemente pequeno (consultas pouco frequentes), o último algoritmo é realmente mais eficiente e vice-versa.
fonte
Você não pode fazer isso com apenas uma pilha? Atualização: não. Veja o comentário.
Invariante: Depois de ler as
2*n
entradas, o min-heap mantém an
maior delas.Loop: Leia 2 entradas. Adicione os dois à pilha e remova o mínimo da pilha. Isso restabelece o invariante.
Portanto, quando as
2n
entradas são lidas, o mínimo da pilha é o enésimo maior. Será necessário um pouco de complicação extra para calcular a média dos dois elementos em torno da posição mediana e lidar com consultas após um número ímpar de entradas.fonte