입력 값을 브라케팅하는 수레 쌍을 찾기 위해 정렬 된 수레 배열을 검색하는 빠른 알고리즘


10

가장 작은 것에서 가장 큰 것까지 정렬 된 float 배열이 있으며 전달 된 입력 값보다 크거나 작은 가장 가까운 float를 선택할 수 있어야합니다. 이 입력 값이 반드시 배열에 값으로 존재하는 것은 아닙니다.

순진한 접근 방식은 배열을 통해 간단한 선형 검색을 수행하는 것입니다. 다음과 같이 보일 수 있습니다.

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];
        }
    }
}

그러나 어레이가 커질수록이 속도는 느려지고 느려집니다.

누구 든지이 데이터를보다 최적으로 찾을 수있는 알고리즘에 대해 알고 있습니까? 이미 바이너리 검색으로 전환했는데 문제가 다소 개선되었지만 여전히 원하는 것보다 훨씬 느리며 실제로 배열에 존재하는 특정 값을 찾지 않기 때문에 결코 종료 할 수 없습니다 이른.

추가 정보 : 배열의 부동 소수점 값이 반드시 균등하게 분배되는 것은 아닙니다. 즉, 배열은 "1.f, 2.f, 3.f, 4.f, 100.f, 1200.f 값으로 구성 될 수 있습니다. , 1203.f, 1400.f ".

이 작업을 수십만 번 수행하고 있지만 조회 시간을 향상시킬 경우 수레 배열에 대해 사전 처리를 얼마든지 수행 할 수 있습니다. 도움이된다면 벡터 이외의 다른 것을 사용하도록 변경할 수 있습니다.


바이너리 검색이 조기에 종료되지 않는다고 생각하는 이유는 무엇입니까? 확실히 i와 i + 1의 요소를 테스트하여 대상 값을 괄호로 묶고 있는지 확인하고 그럴 경우 종료 할 수 있습니까?
Paul R

또는 i 및 i-1의 요소를 테스트하여 대상 값을 괄호로 묶었는지 확인할 수 있습니다. 또한 'i'가> = array.size ()-1인지 테스트하여 테스트를 피할 수 있고 테스트가 <= 0인지 여부를 테스트하여 테스트를 피할 수 있습니다 ... 실제로 많은 초기 단계를 확인하기 위해 각 단계에서 수행 할 추가 조건. 나는 그들이 실제로 프로파일 링하지 않았다고 고백하지만 알고리즘을 많이 느리게 할 것이라고 생각합니다.
트레버 파월

3
너무 복잡 할 필요는 없습니다. 배열의 크기가 N 인 경우 배열의 크기를 N-1 인 것처럼 취급하면됩니다. 이렇게하면 항상 i + 1에 유효한 요소가 있습니다. 대상 값보다 작은 요소 i에 대해 N-1 요소를 이진 검색하고 요소 i + 1이 대상 값보다 큽니다.
Paul R

답변:


11

올바르게 지적했듯이 문제의 코드 (선형 검색)는 큰 float 배열의 경우 느려질 것입니다. 기술적으로 그것은 O (n)입니다. 여기서 n은 배열의 부동 소수점 수입니다.

일반적으로 정렬 된 배열에서 값을 찾기 위해 할 수있는 최선의 방법은 어떤 종류의 재귀 트리 검색 (예 : 이진 검색)입니다.이 경우 요소 수에서 O (log n) 조회 시간을 얻을 수 있습니다 배열에서. 큰 값의 n에 대해 O (log n)이 O (n)보다 훨씬 낫습니다.

따라서 제안 된 접근법은 배열간단한 이진 검색입니다 .

  1. 전체 float 배열을 포함하도록 최소 / 최대 정수 색인 설정
  2. 검색 값 x에 대해 인덱스 mid = (min + max / 2) 범위의 중간 값을 테스트
  3. x가이 값보다 작 으면 max를 mid로 설정하고, 그렇지 않으면 min을 mid로 설정하십시오.
  4. 올바른 값을 찾을 때까지 (2-4)를 반복하십시오.

이것은 거의 모든 상황에서 충분히 빠른 O (log n) 알고리즘입니다. 직관적으로, 정확한 값을 찾을 때까지 각 단계에서 검색 할 범위를 절반으로 줄임으로써 작동합니다.

간단한 이진 검색을 수행하는 것은 실제로 어렵 기 때문에 이미 올바르게 구현 한 경우 이미 최적에 가깝습니다. 그러나 데이터 분포를 알고 있거나 제한된 범위의 조회 값 (x)이있는 경우 시도해 볼 수있는 다른 고급 트릭이 여전히 있습니다.

  • 버킷 -버킷을 생성합니다 (예 : 두 정수 사이의 각 간격에 대해). 각 버킷에는 두 경계 정수 사이의 플로트 값의 작은 정렬 된 목록과 각 범위 바로 아래 및 바로 위의 두 값이 포함됩니다. 그런 다음 (trunc (x) +0.5)에서 검색을 시작할 수 있습니다. 적절한 크기의 버킷을 선택하면 속도가 향상됩니다 (트리의 분기 계수를 효과적으로 증가시킵니다 .....). 정수가 작동하지 않으면 다른 고정 소수점 정밀도 (예 : 1/16의 배수)의 버킷을 사용해 볼 수 있습니다.
  • 비트 매핑 -가능한 조회 값의 범위가 충분히 작 으면 x의 비트 단위 값으로 색인이 생성 된 큰 조회 테이블을 만들 수 있습니다. 이것은 O (1)이지만 캐시에서 매우 친숙하지 않은 많은 메모리가 필요할 수 있습니다. 따라서주의해서 사용하십시오. 부동 소수점 값을 찾고 있기 때문에 이것은 특히 불쾌합니다. 따라서 덜 중요한 비트를 모두 처리하려면 몇 GB가 필요할 수 있습니다 ...
  • 반올림 및 해싱 -해시 테이블은 아마도이 문제에 가장 적합한 데이터 구조는 아니지만 약간의 정확성을 잃어도 살아남는다면 작동 할 수 있습니다. 조회 값의 가장 낮은 비트를 반올림하고 해시 맵을 사용하여 직접 검색하십시오. 올바른 값. 해시 맵 크기와 정밀도 사이의 올바른 균형을 실험하고 가능한 모든 해시 값이 채워져 약간 까다로울 수 있는지 확인해야합니다.
  • 나무 균형 -이상적인 나무는 50 %의 확률로 왼쪽이나 오른쪽으로 갈 것입니다. 따라서 조회 값 (x)의 분포를 기반으로 트리를 만들면 최소한의 테스트로 답변을 생성하도록 트리를 최적화 할 수 있습니다. float 배열의 많은 값이 서로 매우 가까이 있으면 이러한 분기를 너무 자주 검색하지 않아도되므로 이것이 좋은 솔루션 일 것입니다.
  • Crit-bit tree- 여전히 트리입니다 (그래서 O (log n) ...). 그러나 일부 경우 : 비교를 수행하려면 float를 고정 소수점 형식으로 변환해야합니다.

그러나 매우 특별한 상황이 아니면 간단한 이진 검색을 사용하는 것이 좋습니다. 원인:

  • 구현하기가 훨씬 쉽다
  • 가장 일반적인 경우에 매우 빠릅니다.
  • 보다 복잡한 접근 방식의 추가 오버 헤드 (예 : 높은 메모리 사용량 / 캐시 압력)는 종종 이론상 미미한 장점을 능가합니다.
  • 그것은 미래의 데이터 분배 변화에 더 강력 할 것입니다 ....

1

이것은 충분히 간단 해 보입니다.

-O (log n) 시간을 바인딩하려는 부동 소수점에 대한 이진 검색을 수행하십시오.

그런 다음 왼쪽에있는 요소가 하한이고 오른쪽에있는 요소가 상한입니다.


0

확실한 대답은 수레를 나무 에 저장하는 것 입니다 . '이전'및 '다음'작업을 지원하는 것은 간단합니다. 따라서 가치에 대해 '다음'을 수행 한 다음 첫 번째 단계에서 찾은 값에 대해 '이전'을 수행하십시오.


1
이것은 본질적으로 이진 검색과 동일합니다.
kevin cline

-1

이 논문 ( "곱셈없는 블로그 검색")이 흥미로울 수 있습니다. 심지어 소스 코드도 포함되어 있습니다. 비교를 위해 부동 소수점 숫자를 동일한 비트 패턴을 가진 정수로 취급 할 수 있습니다. 이것이 IEEE 부동 소수점 표준의 설계 목표 중 하나였습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.