Мегаобучалка Главная | О нас | Обратная связь


Интерполяционный поиск



2019-07-03 353 Обсуждений (0)
Интерполяционный поиск 0.00 из 5.00 0 оценок




Двоичный поиск обеспечивает значительное увеличение скорости поиска по сравнению с полным перебором, так как он исключает большие части списка, не проверяя при этом значения исключаемых элементов. Если, кроме того, известно, что значения элементов распределены достаточно равномерно, то можно исключать на каждом шаге еще больше элементов, используя интерполяционный поиск (interpolation search).

Интерполяцией называется процесс предсказания неизвестных значений на основе имеющихся. В данном случае, индексы известных значений в списке используются для определения возможного положения искомого элемента в списке.

Например, предположим, что имеется тот же самый список значений, показанный на рис. 10.2. Этот список содержит 20 элементов со значениями между 1 и 70. Предположим теперь, что требуется найти элемент в списке, имеющий значение 44. Значение 44 составляет 64 процента расстояния между 1 и 70 на шкале чисел. Если считать, что значения элементов распределены равномерно, то можно предположить, что искомый элемент расположен примерно в точке, которая составляет 64 процента от размера списка, и занимает позицию 13.

Если позиция, выбранная при помощи интерполяции, оказывается неправильной, то алгоритм сравнивает искомое значение со значением элемента в выбранной позиции. Если искомое значение меньше, то поиск продолжается в первой части списка, если больше — во второй части. На рис. 10.3 графически изображен интерполяционный поиск.

При двоичном поиске список последовательно разбивается посередине на две части. Интерполяционный поиск каждый раз разбивает список, пытаясь найти ближайший к искомому элемент в списке, при этом точка разбиения определяется следующим кодом:

 

middle = min + (target - List(min)) * _

 

   ((max - min) / (List(max) - List(min)))

 

 

========270-271

 

@Рис. 10.3 Интерполяционный поиск значения 44

 

Этот оператор помещает значение middle между min и max в таком же соотношении, в каком искомое значение находится между List(min) и List(max). Если искомый элемент находится рядом с List(min), то разность target – List(min) почти равна нулю. Тогда все соотношение целиком выглядит почти как middle = min + 0, поэтому значение переменной middle почти равно min. Смысл этого заключается в том, что если индекс элемента почти равен min, то его значение почти равно List(min).

Аналогично, если искомый элемент находится рядом с List(max), то разность target – List(min) почти равна разности List(max) – List(min). Их частное почти равно единице, и соотношение выглядит почти как middle = min + (max – min), или middle = max, если упростить выражение. Смысл этого соотношения заключается в том, что если значение элемента близко к List(max), то его индекс почти равен max.

После того, как программа вычислит значение middle, она сравнивает значение элемента в этой позиции с искомым так же, как и в алгоритме двоичного поиска. Если эти значения совпадают, то искомый элемент найден и процесс закончен. Если значение искомого элемента меньше, чем значение найденного, то программа устанавливает значение max равным middle – 1 и продолжает поиск элементов списка с меньшими значениями. Если значение искомого элемента больше, чем значение найденного, то программа устанавливает значение min равным middle + 1 и продолжает поиск элементов списка с большими значениями.

Заметьте, что в знаменателе соотношения, которое находит новое значение переменной middle, находится разность (List(max) – Lsit(min)). Если значения List(max) и List(min) одинаковы, то произойдет деление на ноль и программа аварийно завершит работу. Такое может произойти, если два элемента в списке имеют одинаковые значения. Так как алгоритм поддерживает соотношение min <= target index <= max, то эта проблема может также возникнуть, если min будет расти, а max уменьшаться до тех пор, пока их значения не сравняются.

Чтобы справиться с этой проблемой, программа перед выполнением операции деления проверяет, не равны ли List(max) и List(min). Если это так, значит осталось проверить только одно значение. При этом программа просто проверяет, совпадает ли оно с искомым.

Еще одна тонкость заключается в том, что вычисленное значение middle не всегда лежит между min и max. В простейшем случае это может быть так, если значение искомого элемента выходит за пределы диапазона значений элементов в списке. Предположим, что мы пытаемся найти значение 300 в списке из элементов 100, 150 и 200. На первом шаге вычислений min = 1 и max = 3. Тогда middle = 1 + (300 – List(1)) * (3 – 1) / (List(3) – List(1)) = 1 + (300 – 100) * 2 / (200 – 100) = 5. Индекс 5 не только не находится в диапазоне между min и max, он также выходит за границы массива. Если программа попытается обратиться к элементу массива List(5), то она аварийно завершит работу с сообщением об ошибке “Subscript out of range”.

 

===========272

 

Похожая проблема возникает, если значения элементов распределены между min и max очень неравномерно. Предположим, что мы хотим найти значение 100 в списке 0, 1, 2, 199, 200. При первом вычислении значения переменной middle, мы получим в программе middle = 1 + (100 – 0) * (5 – 1) / (200 – 0) = 3. Затем программа сравнивает значение элемента List(3) с искомым значением 100. Так как List(3) = 2, что меньше 100, она задает min = middle + 1, то есть min = 4.

При следующем вычисления значения переменной middle, программа находит middle = 4 + (100 – 199) * (5 – 4) / (200 – 199) = -98. Значение –98 не попадает в диапазон min <= target index <= max и также далеко выходит за границы массива.

Если рассмотреть процесс вычисления переменной middle, то можно увидеть, что существуют два варианта, при которых новое значение может оказаться меньше, чем min или больше, чем max. Вначале предположим, что middle меньше, чем min.

 

min + (target - List(min)) * ((max - min) / (List(max) - List(min))) < min

 

После вычитания min из обеих частей уравнения, получим:

 

(target - List(min)) * ((max - min) / (List(max) - List(min))) < 0

 

Так как max >= min, то разность (max – min) должна быть больше нуля. Так как List(max) >= List(min), то разность (List(max) – List(min)) также должна быть больше нуля. Тогда все значение может быть меньше нуля, только если (target – List(min)) меньше нуля. Это означает, что искомое значение меньше, чем значение элемента List(min). В этом случае, искомый элемент не может находиться в списке, так как все элементы списка со значением меньшим, чем List(min) уже были исключены.

Теперь предположим, что middle больше, чем max.

 

min + (target - List(min)) * ((max - min) / (List(max) - List(min))) > max

 

После вычитания min из обеих частей уравнения, получим:

 

(target - List(min)) * ((max - min) / (List(max) - List(min))) > 0

 

Умножение обеих частей на (List(max) – List(min)) / (max – min) приводит соотношение к виду:

 

target – List(min) > List(max) – List(min)

 

И, наконец, прибавив к обеим частям List(min), получим:

 

target > List(max)

 

Это означает, что искомое значение больше, чем значение элемента List(max). В этом случае, искомое значение не может находиться в списке, так как все элементы списка со значениями большими, чем List(max) уже были исключены.

 

==========273

 

Учитывая все эти результаты, получаем, что новое значение переменной middle может выйти из диапазона между min и max только в том случае, если искомое значение выходит за пределы диапазона от List(min) до List(max). Алгоритм может использовать этот факт при вычислении нового значения переменной middle. Он вначале проверяет, находится ли новое значение между min и max. Если нет, то искомого элемента нет в списке и работа алгоритма завершена.

Следующий код демонстрирует реализацию интерполяционного поиска в программе Search:

 

Public Function InterpSearch(target As Long) As Long

Dim min As Long

Dim max As Long

Dim middle As Long

 

 

min = 1

max = NumItems

Do While min <= max

   ' Избегаем деления на ноль.

   If List(min) = List(max) Then

      ' Это искомый элемент (если он есть в списке).

      If List(min) = target Then

          InterpSearch = min

      Else

          InterpSearch = 0

      End If

      Exit Function

   End If

 

   ' Найти точку разбиения списка.

   middle = min + (target - List(min)) * _

      ((max - min) / (List(max) - List(min)))

 

   ' Проверить, не вышли ли мы за границы.

   If middle < min Or middle > max Then

      ' Искомого элемента нет в списке.

      InterpSearch = 0

      Exit Function

   End If

 

   NumSearches = NumSearches + 1

   If target = List(middle) Then ' Искомый элемент найден.

      InterpSearch = middle

      Exit Function

   ElseIf target < List(middle) Then ' Поиск в левой части.

      max = middle - 1

   Else                        ' Поиск в правой части.

      min = middle + 1

   End If

Loop

 

' Если мы дошли до этой точки, то элемента нет в списке.

InterpSearch = 0

End Function

 

Двоичный поиск выполняется очень быстро, а интерполяционный еще быстрее. В одном из тестов, двоичный поиск потребовал в 7 раз больше времени для поиска значений в списке из 100.000 элементов. Эта разница могла бы быть еще больше, если бы данные находились на диске или каком‑либо другом медленном устройстве. Хотя при интерполяционном поиске на вычисления уходит больше времени, чем в случае двоичного поиска, за счет меньшего числа обращений к диску мы сэкономили бы гораздо больше времени.

Строковые данные

Если данные в списке представляют собой строки, можно применить два различных подхода. Более простой состоит в применении двоичного поиска. При двоичном поиске значения элементов сравниваются непосредственно, поэтому этот метод может легко работать со строковыми данными.

С другой стороны, интерполяционный поиск использует численные значения элементов данных для вычисления возможного положения искомого элемента в списке. Если элементы представляют собой строки, то этот алгоритм не может непосредственно использовать значения данных для вычисления предполагаемого положения искомого элемента.

Если строки достаточно короткие, то можно закодировать их при помощи целых чисел или чисел формата long или double, используя методы, которые были описаны в 9 главе. После этого можно использовать для нахождения элементов в списке интерполяционный поиск.

Если строки слишком длинные, и их нельзя закодировать даже числами в формате double, то все еще можно использовать для интерполяции значения строк. Вначале найдем первый отличающийся символ для строк List(min) и List(max). Затем закодируем его и следующие два символа в каждой строке при помощи методов из 9 главы. Затем можно использовать эти значения для выполнения интерполяционного поиска.

Например, предположим, что мы ищем строку TARGET в списке TABULATE, TANTRUM, TARGET, TATTERED, TAXATION. Если min = 1 и max = 5, то проверяются значения TABULATE и THEATER. Эти строки отличаются во втором символе, поэтому нужно рассматривать три символа, начинающиеся со второго. Это будут символы ABU для List(1), AXA для List(5) и ARG для искомой строки.

Эти значения кодируются числами 804, 1378 и 1222 соответственно. Подставляя эти значения в формулу для переменной middle, получим:

 

middle = min + (target - List(min)) * ((max - min) / (List(max) - List(min)))

= 1 + (1222 – 804) * ((5 – 1) / (1378 – 804))

= 2,91

 

 

=========275

 

Это примерно равно 3, поэтому следующее значение переменной middle равно 3. Это положение строки TARGET в списке, поэтому поиск при этом заканчивается.

Следящий поиск

Чтобы начать двоичный следящий поиск (binary hunt and search), сравним искомое значение из предыдущего поиска с новым искомым значением. Если новое значение меньше, начнем слежение влево, если больше — вправо.

Для выполнения слежения влево, установим значения переменных min и max равными индексу, полученному во время предыдущего поиска. Затем уменьшим значение min на единицу и сравним искомое значение со значением элемента List(min). Если искомое значение меньше, чем значение List(min), установим max = min и min = min –2, и сделаем еще одну проверку. Если искомое значение все еще меньше, установим max = min и min = min –4, если это не поможет, установим max = min и min = min –8 и так далее. Продолжим устанавливать значение переменной max равным значению переменной min и вычитать очередные степени двойки из значения переменной min до тех пор, пока не найдется значение min, для которого значение элемента List(min) будем меньше искомого значения.

Необходимо следить за тем, чтобы не выйти за границы массива, если min меньше, чем нижняя граница массива. Если в какой‑то момент это окажется так, то min нужно присвоить значение нижней границы массива. Если при этом значение элемента List(min) все еще больше искомого, значит искомого элемента нет в списке. На рис. 10.4 показан следящий поиск элемента со значением 17 влево от предыдущего искомого элемента со значением 44.

Слежение вправо выполняется аналогично. Вначале значения переменных min и max устанавливаются равными значению индекса, полученного во время предыдущего поиска. Затем последовательно устанавливается min = max и max = max + 1, min = max и max = max + 2, min = max и max = max + 4, и так далее до тех пор, пока в какой‑то точке значение элемента массива List(max) не станет больше искомого. И снова необходимо следить за тем, чтобы не выйти за границу массива.

После завершения фазы слежения известно, что индекс искомого элемента находится между min и max. После этого можно использовать обычный двоичный поиск для нахождения точного положения искомого элемента.

 

@Рис. 10.4. Следящий поиск значения 17 из значения 44

 

===============276

 

Если новый искомый элемент находится недалеко от предыдущего, то алгоритм следящего поиска очень быстро найдет значения max и min. Если новый и старый искомые элементы отстоят друг от друга на P позиций, то потребуется порядка log(P) шагов для следящего поиска новых значений переменных min и max.

Предположим, что мы начали обычный двоичный поиск без фазы слежения. Тогда потребуется порядка log(NumItems) – log(P) шагов для того, чтобы значения min и max были на расстоянии не больше, чем P позиций друг от друга. Это означает, что следящий поиск будет быстрее обычного двоичного поиска, если log(P) < log(NumItems) – log(P). Прибавив к обеим частям уравнения log(P), получим 2 * log(P) > log(NumItems). Если возвести обе части уравнения в степень двойки, получим 22*log(P) < 2log(NumItems) или (2log(P))2 < NumItems, или после упрощения P2 < NumItems.

Из этого соотношения видно, что следящий поиск будет выполняться быстрее, если расстояние между последовательными искомыми элементами будет меньше, чем квадратный корень из числа элементов в списке. Если следующие друг за другом искомые элементы расположены далеко друг от друга, то лучше использовать обычный двоичный поиск.



2019-07-03 353 Обсуждений (0)
Интерполяционный поиск 0.00 из 5.00 0 оценок









Обсуждение в статье: Интерполяционный поиск

Обсуждений еще не было, будьте первым... ↓↓↓

Отправить сообщение

Популярное:



©2015-2024 megaobuchalka.ru Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. (353)

Почему 1285321 студент выбрали МегаОбучалку...

Система поиска информации

Мобильная версия сайта

Удобная навигация

Нет шокирующей рекламы



(0.008 сек.)