В ряде случаев (в частности, в задачах интерполяции) приходится выяснять, где по отношению к заданному упорядоченному массиву действительных чисел располагается заданное действительное число. В отличие от поиска в массиве целых чисел, заданное число в этом случае чаще всего не совпадает ни с одним из чисел массива, и требуется найти номера элементов, между которыми это число заключено.
Один из наиболее быстрых способов этого является бинарный поиск, характерное число операций которого имеет порядок log2(n), где n -- число элементов массива. При многочисленных обращениях к этой процедуре число операций будет равно m log2(n) (m -- число обращений). Ускорения этой процедуры можно добиться за счет сохранения предыдущего результата операции и попыток поиска при новом обращении в ближайших узлах массива с дальнейшим расширением области поиска в случае неуспеха.
При этом в наихудших случаях число операций будет больше (примерно в 2 раза) по сравнению с бинарным поиском, но обычно при m значительно большим, чем log2(n) удается довести порядок числа операций до m, то есть сделать его почти независимым от размера массива.
Важным примером применения подобного алгоритма является картирование области: по заданному массиву RGB-палитры, соответствующему 'уровням' карты, раскрасить эту область, определив цвет каждого пиксела. Значение, соответствующее пикселу, считается заданным (найденным каким-либо внешним по отношению к программе раскрашивания способом); по нему определяется, между какими уровнями карты оно находится, а по этим номерам определяется его цвет, получаемый из палитры.
Число уровней при этом может быть достаточно большим, изменение же уровня между соседними пикселами -- чаще всего слабым. В этом случае ускорение, порождаемое алгоритмом по сравнению со стандартным бинарным поиском, является весьма существенным.
Задача ставится так. Дан упорядоченный массив действительных чисел array размерности n, проверяемое значение value и начальное приближение узла old. Требуется найти номер узла res массива array, такой, что array[res]<=value<array[res+1]
Алгоритм работает следующим образом.
Определяется, лежит ли проверяемое value за пределами массива array. В случае value<array[0] возвращается -1, в случае value>array[n-1] возвращается n-1.
Иначе проверяется: если значение old лежит за пределами индексов массива (т.е. old<0 или old>=n, то переходим к обычному бинарному поиску, установив левую границу left=0, правую right=n-1.
Иначе переходим к выяснению границ поиска. Устанавливается left=right=old, inc=1 -- инкремент поиска.
Проверяется неравенство value>=array[old]. При его выполнении переходим к следующему пункту (5), иначе к пункту (7).
Правая граница поиска отводится дальше: right=right+inc. Если right>=n-1, то устанавливается right=n-1 и переходим к бинарному поиску.
Проверяется value>=array[right]. Если это неравенство выполняется, то левая граница перемещается на место правой: left=right, inc умножается на 2, и переходим назад на (5). Иначе переходим к бинарному поиску.
Левая граница отводится: left=left-inc. Если left<=0, то устанавливаем left=0 и переходим к бинарному поиску.
Проверяется value<array[left]. При выполнении правая граница перемещается на место левой: right=left, inc умножается на 2, переходим к пункту (7). Иначе к бинарному поиску.
Проводится бинарный поиск в массиве с ограничением индексов left и right. При этом каждый раз интервал сокращается примерно в 2 раза (в зачисимости от четности разности), пока разность между left и right не достигнет 1. После этого возвращаем left как результат, одновременно присваивая old=left.
Ниже приведена программа на C, реализующая алгоритм поиска в упорядоченном массиве действительных чисел.
/* Search within a real ordered array. Parameters: value -- the sample, old -- previous result, array,n -- the array and its dimension. Returns: Left node of the array segment matching the sample. In case the sample is out of the array, returns -1 (less than left boundary) or (n-1) (more than the right boundary). */
int fbin_search(float value,int *old,float *array,int n) { register int left,right; /* проверка позиции за пределами массива */ if(value < array[0]) return(-1); if(value >= array[n-1]) return(n-1); /* процесс расширения области поиска. Вначале проверяется валидность начального приближения */ if(*old>=0 && *old<n-1) { register int inc=1; left = right = *old; if(value < array[*old]) { /* область расширять влево */ while(1) { left -= inc; if(left <= 0) {left=0;break;} if(array[left] <= value) break; right=left; inc<<=1; } } else { /* область расширять вправо */ while(1) { right += inc; if(right >= n-1) {right=n-1;break;} if(array[right] > value) break; left=right; inc<<=1; } } } /* начальное приближение плохое - за область поиска принимается весь массив */ else {left=0;right=n-1;} /* ниже алгоритм бинарного поиска требуемого интервала */ while(left<right-1) { register int node=(left+right)>>1; if(value>=array[node]) left=node; else right=node; } /* возвращаем найденную левую границу, обновив старое значение результата */ return(*old=left); }
Замечание: для успешной работы алгоритма целесообразно при первичном запуске положить *old=-1 или другое число, гарантирующее бинарный поиск на первом проходе.
8 8 8
| |