Подобно бинарному поиску, jump поиск позволяет находить элемент в отсортированном массиве. Основная идея заключается в проверке меньшего количества элементов (чем при линейном поиске), прыгая фиксированными шагами, то есть пропуская некоторые блоки элементов.

Описание алгоритма

К примеру, пусть имеется массив $$arr[]$$ размера $$n$$ отсортированный по возрастанию и блоки размером $$m$$ каждый. Мы просматриваем элементы массива с индексами $$a[0], a[m], a[2m], ..., a[km]$$ в поисках такого $$k$$, что выполняется неравенство $$arr[km] < x < arr[(k+1)m]$$. После нахождения нужного индекса, мы осуществляем линейный поиск в данном блоке начиная с индекса $$km$$, чтобы найти нужный элемент.

Визуализация работы алгоритма

Пусть у нас имеется массивследующий массив (0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610) длиной 16. Найдем с помощью Jump search элемент со значением 55. Размер блока для прыжка возьмем равным 4.
Шаг 1: Прыжок от индекса 0 до индекса 4;
Шаг 2: Прыжок от индекса 4 до индекса 8;
Шаг 3: Прыжок от индекса 8 до индекса 16;
Шаг 4: Поскольку элемент с индексом 16 больше чем 55, прыгаем обратно на элемент с индексом 9.
Шаг 5: Проиводим линейны поиск с индекса 9, чтобы найти искомый элемент 55.

Размер оптимального блока

В худшем случае, нам потребуется осуществить $$\dfrac{n}{m}$$ прыжков и в случае если последний проверяемый элемент окажется больше искомого элемента, мы сделаем $$m-1$$ сравнение. Поэтому общее количество сравнений в худшем случае равно $$\dfrac{n}{m} + m-1$$. Функция $$F(n, m) = \dfrac{n}{m} + m-1$$ принимает свое наименьшее значение при $$m=\sqrt{n}$$. Таким образом наилучший размер прыжка равен $$m = \sqrt{n}$$, тогда общее количество сравнений будет равно $$F(n) = 2\sqrt{n} - 1 = \mathcal{O}(\sqrt{n})$$.

Реализация алгоритма

public static int jumpSearch(int[] arr, int x)
    {
        int n = arr.length;

        // Finding block size to be jumped
        int step = (int)Math.floor(Math.sqrt(n));

        // Finding the block where element is
        // present (if it is present)
        int prev = 0;
        while (arr[Math.min(step, n)-1] < x)
        {
            prev = step;
            step += (int)Math.floor(Math.sqrt(n));
            if (prev >= n)
                return -1;
        }

        // Doing a linear search for x in block
        // beginning with prev.
        while (arr[prev] < x)
        {
            prev++;

            // If we reached next block or end of
            // array, element is not present.
            if (prev == Math.min(step, n))
                return -1;
        }

        // If element is found
        if (arr[prev] == x)
            return prev;

        return -1;
    }

Оптимизации

При больших размерах массива, размер одного блока $$m = \sqrt{n}$$ может оказать достаточно большим. Для улучшения эффективности мы можем вместо линейного поиска, рекурсивно применить jump search на блоке в котором содержится искомый элемент. В таком случае сложность алгоритма будет равна

$$ \sqrt{n} + \sqrt{\sqrt{n}} +\sqrt{\sqrt{\sqrt{n}}} + \ldots +1

$$

Такая оптимизация улучшает скорость работы алгоритма, но в худшем случае его сложностьпо прежнему равна $$\mathcal{O}(\sqrt{n})$$Так же поиск можно начинать не с нулевого элемента массива, а сразу с $$k = \sqrt{n}$$.

Реализация оптимизации.

 public static int jumpSearch(int[] arr, int val) {
        int low = 0, high = arr.length - 1;

        while (high - low > 1) {
            Pair p = jumpSearchUtil(arr, val, low, high);

            if (p == null) {
                return -1;
            }

            low = p.left;
            high = p.right;
        }

        return arr[low] == val ? low : arr[high] == val ? high : -1;
    }

    private static Pair jumpSearchUtil(int[] arr, int val, int low, int high) {
        int left = low;
        int step = (int) Math.sqrt(high - low);
        int right = 0;

        while (left < high && arr[left] <= val) {
            right = Math.min(left + step, high);

            if (arr[left] <= val && arr[right] >= val) {
                return new Pair(left, right);
            }

            left = right;
        }

        return null;
    }

}

class Pair {
    int left, right;

    public Pair(int left, int right) {
        this.left = left;
        this.right = right;
    }
}

Сложность алгоритма

Поскольку наилучший размер прыжка равен $$m = \sqrt{n}$$, то общее количество сравнений будет равно $$F(n) = 2\sqrt{n} - 1 = \mathcal{O}(\sqrt{n})$$. Такой алгоритм работает быстрее линейного поиска, но медленнее бинарного поиска.

В случае оптимизированной версии алгоритма, сложность по прежнему равна $$\mathcal{O}(\sqrt{n})$$. Однако теперь его сложность приближается к логарифмическому значению. Проблема состоит в том, что реализация этого поиска с опимизацией считается более сложной, чем двоичный поиск, где сложность также равна $$\mathcal{O} (\log n)$$.

Таблица сравнения сложностей:

$$10$$ $$10^3$$ $$10^6$$ $$10^9$$
$$n$$ 10
$$\sqrt{n}$$ 4 1000
$$\log n$$ 3

Характеристики алгоритма

Класс алгоритма Алгоритм поиска
Структура данных отсортированный массив
Худший случай $$\mathcal{O}(\sqrt{n})$$
Лучший случай $$\mathcal{O}(\sqrt{n})$$
Средний случай $$\mathcal{O}(\sqrt{n})$$
Затраты на память $$\mathcal{O}(1)$$

Приложения

Как и любой алгоритм, Jump Search очень удобен для определенного вида задач. Несмотря на то, что бинарный поиск имеет более простую реализацию, и его сложность $$\mathcal{O}(\log n)$$ лучше чем $$\mathcal{O}(\sqrt{n})$$, в случае очень большого массива прямой переход к середине может быть плохой идеей. Далее мы будем вынуждены много раз отматывать назад, если искомый элемент находится в начале массива.

Каждый из нас совершал в своей жизни примитивный Jump Search поиск, даже не подозревая об этом. Вы помните кассетные магнитофоны? Мы использовали клавишу «перемотка вперед» и периодически проверяли, была ли лента в нашей любимой песне. Как только мы остановились в середине песни, мы использовали кнопку «назад», чтобы найти точное начало песни.

Этот пример, показывает нам когда jump search может быть эффективнее двоичного поиска. Преимущество jump search заключается в том, что вам нужно отскакивать назад только один раз (в случае базовой реализации).

Jump search очень полезен, когда отскакивание назад происходит значительно медленнее, чем вперед!

Особенности

  • Works only sorted arrays.
  • The optimal size of a block to be jumped is O(√ n). This makes the time complexity of Jump Search O(√ n).
  • The time complexity of Jump Search is between Linear Search ( ( O(n) ) and Binary Search ( O (Log n) ).
  • Binary Search is better than Jump Search, but Jump search has an advantage that we traverse back only once (Binary Search may require up to O(Log n) jumps, consider a situation where the element to be search is the smallest element or smaller than the smallest). So in a systems where jumping back is costly, we use Jump Search.

Ссылки

https://www.2braces.com/data-structures/jump-search

http://www.stoimen.com/blog/2011/12/12/computer-algorithms-jump-search/

http://theoryofprogramming.com/2016/11/10/jump-search-algorithm/

https://en.wikipedia.org/wiki/Jump_search

results matching ""

    No results matching ""