Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Скачиваний:
22
Добавлен:
26.03.2015
Размер:
231.42 Кб
Скачать
    1. Интерполяционный поиск

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

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

Например, предположим, что имеется список значений, показанный на рис. 2. Этот список содержит 16 элементов со значениями между 2 и 37. Предположим теперь, что требуется найти элемент в списке, имеющий значение 22. Значение 22 составляет 63 процента расстояния между 2 и 37 на шкале чисел. Если считать, что значения элементов распределены равномерно, то можно предположить, что искомый элемент расположен примерно в точке, которая составляет 63 процента от размера списка, и занимает позицию 10.

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

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

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

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

Этот оператор помещает значение 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”.

Похожая проблема возникает, если значения элементов распределены между 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)уже были исключены.

Учитывая все эти результаты, получаем, что новое значение переменной 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 элементов. Эта разница могла бы быть еще больше, если бы данные находились на диске или каком-либо другом медленном устройстве. Хотя при интерполяционном поиске на вычисления уходит больше времени, чем в случае двоичного поиска, за счет меньшего числа обращений к диску мы сэкономили бы гораздо больше времени.