2.2.3.5 — между линейным поиском и бинарным, когда шаг назад дороже сравнения2.2.3.5 — between linear and binary search, when stepping back costs more than a comparison
Бинарный поиск (2.2.3.1) минимизирует число сравнений, но постоянно "прыгает" по массиву в разных направлениях — на структуре, где движение вперёд дёшево, а произвольный переход дорог (лента, диск с последовательным чтением), это может быть не идеально. Jump search движется только вперёд блоками фиксированного размера √n, находит блок, где может быть ответ, и только внутри него ищет линейно.Binary search (2.2.3.1) minimizes comparison count, but keeps "jumping" around the array in different directions — on a structure where moving forward is cheap but an arbitrary jump is expensive (tape, disk with sequential reads), this may not be ideal. Jump search moves only forward, in fixed-size √n blocks, finds the block that could contain the answer, and only searches linearly within it.
Выбирается фиксированный размер блока step (оптимальное значение — √n, обоснование ниже). Указатель прыгает по массиву с этим шагом (0, step, 2·step, ...), пока не встретит блок, чей последний элемент ≥ x, или не дойдёт до конца массива. На этом этапе известно: если x есть в массиве, он лежит в диапазоне (предыдущая позиция прыжка, текущая позиция] — потому что все более ранние блоки целиком меньше x (иначе прыжок остановился бы раньше). Второй этап — обычный линейный поиск внутри этого диапазона размера не больше step.A fixed block size step is chosen (the optimal value is √n, justified below). A pointer jumps through the array with this stride (0, step, 2·step, ...) until it hits a block whose last element is ≥ x, or reaches the array's end. At this point it's known: if x is in the array, it lies within (previous jump position, current position] — because every earlier block was entirely less than x (otherwise the jump would have stopped sooner). The second stage is ordinary linear search within that range of size at most step.
O(√n) операций вместо O(log n) — хуже по числу сравнений, но лучше по паттерну доступа к памяти/дискуO(√n) operations instead of O(log n) — worse in comparison count, better in memory/disk access pattern
int jump_search(const std::vector<int>& a, int x) {
int n = (int)a.size();
if (n == 0) return -1;
int step = std::max(1, (int)std::sqrt(n));
int prev = 0, curr = step;
while (curr < n && a[curr - 1] < x) { // прыгаем, пока блок целиком меньше x// jump while the block is entirely less than x
prev = curr;
curr += step;
}
int hi = std::min(curr, n);
for (int i = prev; i < hi; ++i) // линейный поиск внутри найденного блока// linear search inside the found block
if (a[i] == x) return i;
return -1;
}
Столбики сгруппированы в блоки размера step = √n (жёлтая рамка обозначает границы блоков). Оранжевая подсветка — текущий проверяемый последний элемент блока во время прыжков; зелёная — линейный поиск внутри найденного блока.Bars are grouped into blocks of size step = √n (a yellow outline marks block boundaries). Orange highlights the block's last element being checked during the jump phase; green marks linear search inside the found block.
нажмите «Вперёд» или «Играть», чтобы начатьpress "Forward" or "Play" to begin
Число прыжков в худшем случае — n/step (нужно пройти весь массив блоками), число сравнений линейного поиска в найденном блоке — до step. Суммарная стоимость f(step) = n/step + step. Чтобы её минимизировать, берём производную по step и приравниваем к нулю: f'(step) = -n/step² + 1 = 0 → step² = n → step = √n. При этом выборе f(√n) = √n + √n = 2√n — то есть общее число операций в худшем случае Θ(√n), что и является доказанной, а не эмпирически подобранной, оптимальной точкой компромисса между "много мелких прыжков" и "один большой линейный поиск".The worst-case number of jumps is n/step (the whole array must be covered in blocks), and linear-search comparisons inside the found block go up to step. The total cost is f(step) = n/step + step. To minimize it, take the derivative with respect to step and set it to zero: f'(step) = -n/step² + 1 = 0 → step² = n → step = √n. With this choice, f(√n) = √n + √n = 2√n — so the total worst-case operation count is Θ(√n), a proven (not empirically tuned) optimal trade-off point between "many small jumps" and "one large linear search."
На ленте перемотка назад значительно дороже перемотки вперёд — бинарный поиск с его постоянными переходами туда-обратно неэффективен, а jump search, двигаясь только вперёд, соответствует физическим ограничениям устройства.On tape, rewinding is significantly more expensive than winding forward — binary search's constant back-and-forth jumps are inefficient, while jump search, moving only forward, matches the device's physical constraints.
Когда данные физически хранятся блоками фиксированного размера (страницы диска, блоки индекса), пропуск целых блоков без их чтения (по значению на границе блока) — идея, родственная jump search, экономящая операции ввода-вывода, а не только сравнения.When data is physically stored in fixed-size blocks (disk pages, index blocks), skipping whole blocks without reading them (via the value at the block boundary) is an idea related to jump search, saving I/O operations rather than just comparisons.