Algoritmo rápido para pesquisar uma matriz classificada de carros alegóricos para encontrar o par de carros alegóricos entre colchetes com um valor de entrada

10

Eu tenho uma matriz de flutuadores, classificados do menor para o maior, e preciso poder escolher o flutuador mais próximo maior ou menor que um valor de entrada passado. Este valor de entrada não está necessariamente presente como um valor na matriz.

Uma abordagem ingênua seria fazer uma pesquisa linear simples através da matriz. Isso pode ser assim:

void FindClosestFloatsInArray( float input, std::vector<float> array, 
                               float *min_out, float *max_out )
{
    assert( input >= array[0] && input < array[ array.size()-1 ] );
    for( int i = 1; i < array.size(); i++ )
    {
        if ( array[i] >= input )
        {
            *min = array[i-1];
            *max = array[i];
        }
    }
}

Mas, obviamente, à medida que a matriz aumenta, isso se torna cada vez mais lento.

Alguém tem uma idéia sobre um algoritmo que me permita encontrar esses dados de maneira ideal? Eu já mudei para uma pesquisa binária, que melhorou um pouco as coisas, mas ainda é muito mais lenta do que gostaria, e como não estou procurando um valor específico que exista na matriz, ele nunca pode terminar cedo.

Mais informações: Os valores de ponto flutuante na matriz não são necessariamente distribuídos uniformemente (ou seja, a matriz pode consistir nos valores "1.f, 2.f, 3.f, 4.f, 100.f, 1200.f 1203.f, 1400.f ".

Estou fazendo essa operação centenas de milhares de vezes, mas posso realizar qualquer quantidade de pré-processamento na matriz de flutuadores, se isso melhorar o tempo de pesquisa. Eu absolutamente posso mudar para usar algo diferente de um vetor para armazená-los, se isso ajudar.

Trevor Powell
fonte
O que faz você pensar que sua pesquisa binária não pode terminar mais cedo? Certamente você pode apenas testar os elementos em i e i + 1 para ver se eles suportam o valor-alvo e terminar se o fizerem.
Paul R
Como alternativa, eu poderia testar os elementos em i e i-1 para ver se eles se enquadram no valor-alvo. Eu também precisaria testar se 'i' era> = array.size () - 1 para evitar o teste e se era <= 0 para evitar o teste ... na verdade, é um monte de condicionais extras a serem executados em cada etapa, para verificar se há uma saída antecipada. Eu imagino que eles desacelerariam muito o algoritmo, embora eu confesse que ainda não fiz o perfil.
Trevor Powell
3
Não precisa ser tão complicado - se sua matriz é do tamanho N, basta tratá-la como se fosse do tamanho N - 1. Dessa forma, sempre há um elemento válido em i + 1. Você faz um pesquisa binária no elemento N - 1 pelo elemento i que é menor que o seu valor-alvo, com o elemento i + 1 sendo maior que o valor-alvo.
Paul R

Respostas:

11

O código da pergunta (uma pesquisa linear), como você aponta corretamente, ficará lento para grandes matrizes flutuantes. Tecnicamente, é O (n) onde n é o número de valores flutuantes em sua matriz.

Em geral, o melhor que você pode fazer para encontrar um valor em uma matriz ordenada é algum tipo de pesquisa em árvore recursiva (por exemplo, pesquisa binária); nesse caso, você pode obter um tempo de pesquisa O (log n) no número de elementos na sua matriz. O (log n) é muito melhor que O (n) para grandes valores de n.

Minha abordagem sugerida seria, portanto, uma simples busca binária da matriz , ou seja:

  1. Defina índices mínimos / máximos para cobrir todo o seu conjunto flutuante
  2. teste o valor no meio do intervalo no índice médio = (min + máx / 2) em relação ao valor de pesquisa x
  3. se x for menor que esse valor, defina max para mid, ou min para mid
  4. repita (2-4) até encontrar o valor correto

Este é um algoritmo O (log n) que deve ser rápido o suficiente para quase todas as situações. Intuitivamente, ele funciona pela metade do intervalo a ser pesquisado em cada etapa até encontrar o valor correto.

É realmente difícil definir a pesquisa binária simples; portanto, se você já a implementou corretamente, pode estar bem próximo do ideal. No entanto, se você conhece as distribuições dos dados e / ou possui um intervalo limitado de valores de pesquisa (x), ainda existem outros truques mais avançados que você pode tentar:

  • Balde - crie baldes (por exemplo, para cada intervalo entre dois números inteiros), cada um dos quais contém uma lista classificada menor dos valores flutuantes entre os dois números inteiros delimitadores mais dois valores imediatamente abaixo e imediatamente acima de cada intervalo. Você pode iniciar sua pesquisa em (trunc (x) +0,5). Isso deve proporcionar uma boa aceleração se você escolher caçambas de tamanho apropriado (isso aumenta efetivamente o fator de ramificação da árvore ...). Se números inteiros não funcionarem para você, você pode tentar baldes com alguma outra precisão de ponto fixo (por exemplo, múltiplos de 1/16).
  • Mapeamento de bits - se o intervalo de possíveis valores de pesquisa for pequeno o suficiente, você pode tentar criar uma grande tabela de pesquisa indexada pelo valor bit de x de x. Será O (1), mas você pode precisar de muita memória, que será muito hostil no seu cache ... portanto, use com cuidado. Isso é especialmente desagradável porque você está pesquisando valores flutuantes; portanto, você pode precisar de vários GBs para contabilizar todos os bits menos significativos ......
  • Arredondamento e hash - as tabelas de hash provavelmente não são a melhor estrutura de dados para esse problema, mas se você sobreviver perdendo um pouco de precisão, elas podem funcionar - basta arredondar os bits mais baixos dos seus valores de pesquisa e usar um mapa de hash para procurar diretamente o valor correto. Você terá que experimentar a troca certa entre tamanho e precisão do mapa de hash e também garantir que todos os valores possíveis de hash sejam preenchidos para que isso possa ser um pouco complicado ......
  • Equilíbrio de árvores - sua árvore ideal deve ter 50% de chance de ir para a esquerda ou direita. Portanto, se você criar uma árvore com base na distribuição dos valores de pesquisa (x), poderá otimizar a árvore para produzir respostas com a quantidade mínima de testes. É provável que seja uma boa solução se muitos valores em sua matriz flutuante estiverem muito próximos, pois permitirá evitar a pesquisa nessas ramificações com muita frequência.
  • Árvores de bits críticos - ainda são árvores (o que ainda é O (log n) ...), mas em alguns casos: você precisará converter seus carros alegóricos em algum formato de ponto fixo para fazer as comparações funcionarem

No entanto, a menos que você esteja em uma situação muito especial, provavelmente recomendo continuar com a pesquisa binária simples. Razões:

  • é muito mais fácil de implementar
  • é muito rápido para os casos mais comuns
  • a sobrecarga extra das abordagens mais complexas (por exemplo, maior uso de memória / pressão do cache) geralmente supera os pequenos ganhos teóricos
  • será mais robusto para futuras alterações nas distribuições de dados ....
Mikera
fonte
1

Isso parece bastante simples:

Faça uma pesquisa binária do flutuador que você deseja limitar - tempo O (log n).

Então o elemento à esquerda é o limite inferior e o elemento à direita é o limite superior.

Ankit Soni
fonte
0

A resposta óbvia é armazenar os carros alegóricos em uma árvore . O suporte às operações 'previous' e 'next' são triviais em uma árvore. Portanto, basta fazer um 'próximo' no seu valor e, em seguida, um 'anterior' no valor que você encontrar na primeira etapa.

David Schwartz
fonte
11
Isso é essencialmente o mesmo que uma pesquisa binária.
Kevin cline
-1

Este artigo ("pesquisa sublogarítmica sem multiplicações") pode ser interessante; ele ainda contém algum código fonte. Para fins de comparação, você pode tratar um número flutuante como um número inteiro com o mesmo padrão de bits; esse era um dos objetivos de design do padrão de ponto flutuante IEEE.

zvrba
fonte